@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.cjs CHANGED
@@ -565,6 +565,22 @@ function parseHtmlAttrs(attrsString) {
565
565
  }
566
566
 
567
567
  // core/parser.ts
568
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
569
+ "area",
570
+ "base",
571
+ "br",
572
+ "col",
573
+ "embed",
574
+ "hr",
575
+ "img",
576
+ "input",
577
+ "link",
578
+ "meta",
579
+ "param",
580
+ "source",
581
+ "track",
582
+ "wbr"
583
+ ]);
568
584
  function parseMarkdown(markdown2) {
569
585
  if (!markdown2) return [];
570
586
  const lines = markdown2.replace(/\r\n/g, "\n").replace(/\r/g, "").split("\n");
@@ -578,8 +594,19 @@ function parseMarkdown(markdown2) {
578
594
  if (match = trimmed.match(/^(#{1,6})\s+(.+)$/)) {
579
595
  const level = match[1].length;
580
596
  const rawText = match[2];
581
- const { text: text2, classes: classes2, id: customId } = extractAttributes(rawText);
582
- const baseId = customId || generateId(text2.replace(/->|<-/g, ""));
597
+ const { text: rawParsedText, classes: classes2, id: customId } = extractAttributes(rawText);
598
+ let text2 = rawParsedText;
599
+ let align;
600
+ const alignCenter = text2.match(/^->\s*(.+?)\s*<-\s*$/);
601
+ const alignRight = text2.match(/^->\s*(.+?)\s*->\s*$/);
602
+ if (alignCenter) {
603
+ text2 = alignCenter[1];
604
+ align = "center";
605
+ } else if (alignRight) {
606
+ text2 = alignRight[1];
607
+ align = "right";
608
+ }
609
+ const baseId = customId || generateId(text2);
583
610
  let id2 = baseId;
584
611
  let n = 1;
585
612
  while (usedIds.has(id2)) {
@@ -587,7 +614,7 @@ function parseMarkdown(markdown2) {
587
614
  id2 = `${baseId}-${n}`;
588
615
  }
589
616
  usedIds.add(id2);
590
- result.push({ type: "header", level, text: text2, id: id2, classes: classes2 || void 0 });
617
+ result.push({ type: "header", level, text: text2, id: id2, classes: classes2 || void 0, align });
591
618
  i++;
592
619
  continue;
593
620
  }
@@ -763,29 +790,13 @@ function parseMarkdown(markdown2) {
763
790
  let tagStartMatch = trimmed.match(/^<([a-zA-Z][\w-]*)/);
764
791
  if (tagStartMatch) {
765
792
  const tagName = tagStartMatch[1].toLowerCase();
766
- const voidElements = /* @__PURE__ */ new Set([
767
- "area",
768
- "base",
769
- "br",
770
- "col",
771
- "embed",
772
- "hr",
773
- "img",
774
- "input",
775
- "link",
776
- "meta",
777
- "param",
778
- "source",
779
- "track",
780
- "wbr"
781
- ]);
782
793
  const remainingText = lines.slice(i).join("\n");
783
794
  const openTagRegex = new RegExp(`^\\s*<${tagName}\\b([^>]*?)>`, "i");
784
795
  const openTagMatch = remainingText.match(openTagRegex);
785
796
  if (openTagMatch) {
786
797
  const fullOpenTag = openTagMatch[0];
787
798
  const attrs = openTagMatch[1].replace(/\s+/g, " ").trim();
788
- const isSelfClosing = fullOpenTag.endsWith("/>") || voidElements.has(tagName);
799
+ const isSelfClosing = fullOpenTag.endsWith("/>") || VOID_ELEMENTS.has(tagName);
789
800
  if (isSelfClosing) {
790
801
  const blockText = remainingText.substring(0, openTagMatch.index + fullOpenTag.length);
791
802
  const consumedLines = blockText.split("\n").length;
@@ -1308,11 +1319,77 @@ function parseInlinePart(part) {
1308
1319
  return document.createTextNode(part);
1309
1320
  }
1310
1321
 
1322
+ // vanilla/utils.ts
1323
+ var THEME_TOKENS = /* @__PURE__ */ new Set([
1324
+ "primary",
1325
+ "secondary",
1326
+ "accent",
1327
+ "neutral",
1328
+ "info",
1329
+ "success",
1330
+ "warning",
1331
+ "error"
1332
+ ]);
1333
+ function isThemeToken(color) {
1334
+ return !!color && THEME_TOKENS.has(color);
1335
+ }
1336
+ function isArbitraryColor(value) {
1337
+ if (THEME_TOKENS.has(value)) return false;
1338
+ if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
1339
+ if (/^[a-zA-Z]+$/.test(value)) return true;
1340
+ return false;
1341
+ }
1342
+ function applyBaseProps(el, props) {
1343
+ if (props.class) {
1344
+ el.classList.add(...props.class.split(/\s+/).filter(Boolean));
1345
+ }
1346
+ if (props.style) {
1347
+ const styles = parseCssString(props.style);
1348
+ for (const [key, value] of Object.entries(styles)) {
1349
+ el.style.setProperty(key, String(value));
1350
+ }
1351
+ }
1352
+ }
1353
+ function applyFloatStyle(el, float, width) {
1354
+ if (!float) return;
1355
+ if (float === "left" || float === "right") {
1356
+ el.style.float = float;
1357
+ if (!width) el.style.maxWidth = "50%";
1358
+ el.style.marginInlineStart = float === "right" ? "1rem" : "";
1359
+ el.style.marginInlineEnd = float === "left" ? "1rem" : "";
1360
+ } else if (float === "center") {
1361
+ el.style.marginInline = "auto";
1362
+ }
1363
+ }
1364
+ function applyColor(el, color, classSuffix) {
1365
+ if (!color) return "";
1366
+ if (isThemeToken(color)) {
1367
+ return ` ${classSuffix}--${color}`;
1368
+ }
1369
+ if (isArbitraryColor(color)) {
1370
+ el.style.background = color;
1371
+ el.style.color = "white";
1372
+ }
1373
+ return "";
1374
+ }
1375
+ function openModal(dialog) {
1376
+ if (!dialog.open) {
1377
+ document.body.appendChild(dialog);
1378
+ dialog.showModal();
1379
+ dialog.addEventListener("close", () => dialog.remove(), { once: true });
1380
+ }
1381
+ }
1382
+ var IMG_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
1383
+ function parseIntProp(value, defaultValue) {
1384
+ if (!value) return defaultValue;
1385
+ const n = parseInt(value, 10);
1386
+ return Number.isNaN(n) ? defaultValue : n;
1387
+ }
1388
+
1311
1389
  // vanilla/directives/admonition.ts
1312
1390
  var admonitionDirective = ({ directiveType, props, renderSlot }) => {
1313
1391
  const el = createAdmonition(directiveType, props.title, props.icon);
1314
- if (props.class) el.classList.add(...props.class.split(/\s+/).filter(Boolean));
1315
- if (props.style) el.setAttribute("style", props.style);
1392
+ applyBaseProps(el, props);
1316
1393
  const body = el.querySelector(".nr-admonition__body");
1317
1394
  if (body) {
1318
1395
  body.appendChild(renderSlot("default"));
@@ -1328,8 +1405,7 @@ var detailsDirective = ({ props, renderSlot }) => {
1328
1405
  props.icon,
1329
1406
  props.defaultOpen === "true"
1330
1407
  );
1331
- if (props.class) el.classList.add(...props.class.split(/\s+/).filter(Boolean));
1332
- if (props.style) el.setAttribute("style", props.style);
1408
+ applyBaseProps(el, props);
1333
1409
  const body = el.querySelector(".nr-details__body");
1334
1410
  if (body) {
1335
1411
  body.appendChild(renderSlot("default"));
@@ -1342,12 +1418,12 @@ var details_default = detailsDirective;
1342
1418
  var modalDirective = ({ props, renderSlot }) => {
1343
1419
  const label = props.label || props.title || "Open";
1344
1420
  const modalTitle = props.title || "Modal";
1345
- const customClass = props.class || "";
1421
+ const align = props.align || "left";
1346
1422
  const wrapper = document.createElement("div");
1347
- wrapper.className = "nr-modal-trigger";
1423
+ wrapper.className = `nr-modal-trigger${align === "center" ? " nr-modal-trigger--center" : align === "right" ? " nr-modal-trigger--right" : ""}`;
1348
1424
  const btn = document.createElement("button");
1349
1425
  btn.className = `nr-button nr-button--default`;
1350
- if (customClass) btn.classList.add(...customClass.split(/\s+/).filter(Boolean));
1426
+ applyBaseProps(btn, props);
1351
1427
  const icon = props.icon || "open_in_new";
1352
1428
  if (icon) btn.appendChild(createIcon(icon));
1353
1429
  btn.appendChild(document.createTextNode(label));
@@ -1359,15 +1435,7 @@ var modalDirective = ({ props, renderSlot }) => {
1359
1435
  prose.appendChild(renderSlot("default"));
1360
1436
  body.appendChild(prose);
1361
1437
  }
1362
- btn.addEventListener("click", () => {
1363
- if (!dialog.open) {
1364
- document.body.appendChild(dialog);
1365
- dialog.showModal();
1366
- dialog.addEventListener("close", () => {
1367
- dialog.remove();
1368
- }, { once: true });
1369
- }
1370
- });
1438
+ btn.addEventListener("click", () => openModal(dialog));
1371
1439
  wrapper.appendChild(btn);
1372
1440
  wrapper.appendChild(dialog);
1373
1441
  return wrapper;
@@ -1377,19 +1445,20 @@ var modal_default = modalDirective;
1377
1445
  // vanilla/directives/button.ts
1378
1446
  var buttonDirective = ({ props, renderSlot }) => {
1379
1447
  const url = props.url || props.href || "#";
1380
- const label = props.label;
1448
+ const label = props.label || props.title;
1381
1449
  const icon = props.icon || "near_me";
1382
1450
  const target = props.target || "_blank";
1383
1451
  const customClass = props.class || "";
1452
+ const align = props.align || "left";
1384
1453
  const wrapper = document.createElement("div");
1385
- wrapper.className = "nr-button-wrap";
1454
+ wrapper.className = `nr-button-wrap${align === "center" ? " nr-button-wrap--center" : align === "right" ? " nr-button-wrap--right" : ""}`;
1386
1455
  if (label) {
1387
1456
  const a = document.createElement("a");
1388
1457
  a.href = url;
1389
1458
  a.target = target;
1390
1459
  a.rel = "noopener noreferrer";
1391
1460
  a.className = "nr-button nr-button--default";
1392
- if (customClass) a.classList.add(...customClass.split(/\s+/).filter(Boolean));
1461
+ applyBaseProps(a, props);
1393
1462
  a.appendChild(createIcon(icon));
1394
1463
  a.appendChild(document.createTextNode(label));
1395
1464
  wrapper.appendChild(a);
@@ -1400,7 +1469,7 @@ var buttonDirective = ({ props, renderSlot }) => {
1400
1469
  if (links.length > 0) {
1401
1470
  links.forEach((link) => {
1402
1471
  link.classList.add("nr-button", "nr-button--default");
1403
- if (customClass) link.classList.add(...customClass.split(/\s+/).filter(Boolean));
1472
+ applyBaseProps(link, props);
1404
1473
  });
1405
1474
  wrapper.appendChild(slotContent);
1406
1475
  } else {
@@ -1409,7 +1478,7 @@ var buttonDirective = ({ props, renderSlot }) => {
1409
1478
  a.target = target;
1410
1479
  a.rel = "noopener noreferrer";
1411
1480
  a.className = "nr-button nr-button--default";
1412
- if (customClass) a.classList.add(...customClass.split(/\s+/).filter(Boolean));
1481
+ applyBaseProps(a, props);
1413
1482
  a.appendChild(createIcon(icon));
1414
1483
  a.appendChild(slotContent);
1415
1484
  wrapper.appendChild(a);
@@ -1432,12 +1501,9 @@ var cardDirective = ({
1432
1501
  const { isSingleCard } = options || {};
1433
1502
  const isModal = directiveType === "card-m";
1434
1503
  const isLink = directiveType === "card-b";
1435
- const inlineStyles = props.style ? parseCssString(props.style) : {};
1436
1504
  const card = document.createElement("div");
1437
1505
  card.className = `nr-card${isModal || isLink ? " nr-card--interactive" : ""} ${customClass}`.trim();
1438
- for (const [key, value] of Object.entries(inlineStyles)) {
1439
- card.style.setProperty(key, String(value));
1440
- }
1506
+ applyBaseProps(card, props);
1441
1507
  if (image) {
1442
1508
  const imgWrap = document.createElement("div");
1443
1509
  imgWrap.className = `nr-card__image${isSingleCard ? " nr-card__image--tall" : ""}`;
@@ -1516,13 +1582,7 @@ var cardDirective = ({
1516
1582
  prose.appendChild(renderSlot("content") || renderSlot("default"));
1517
1583
  modalBody.appendChild(prose);
1518
1584
  }
1519
- card.addEventListener("click", () => {
1520
- if (!dialog.open) {
1521
- document.body.appendChild(dialog);
1522
- dialog.showModal();
1523
- dialog.addEventListener("close", () => dialog.remove(), { once: true });
1524
- }
1525
- });
1585
+ card.addEventListener("click", () => openModal(dialog));
1526
1586
  const frag = document.createDocumentFragment();
1527
1587
  frag.appendChild(card);
1528
1588
  frag.appendChild(dialog);
@@ -1570,8 +1630,8 @@ var slideDirective = ({
1570
1630
  if (lines.length === 0) {
1571
1631
  return document.createDocumentFragment();
1572
1632
  }
1573
- const interval = parseInt(props.interval || "3000", 10);
1574
- const speed = parseInt(props.speed || "500", 10);
1633
+ const interval = parseIntProp(props.interval, 3e3);
1634
+ const speed = parseIntProp(props.speed, 500);
1575
1635
  const rawClass = props.class || "";
1576
1636
  const inlineStyle = props.style ? parseCssString(props.style) : {};
1577
1637
  const scopeClass = `sld-${++slideCounter}`;
@@ -1622,10 +1682,11 @@ var slideDirective = ({
1622
1682
  }
1623
1683
  });
1624
1684
  if (lines.length > 1) {
1625
- setInterval(() => {
1685
+ const id = setInterval(() => {
1626
1686
  current = (current + 1) % lines.length;
1627
1687
  track.style.transform = `translateY(${-current * maxH}px)`;
1628
1688
  }, interval);
1689
+ container.dataset.nrIntervalId = String(id);
1629
1690
  }
1630
1691
  return container;
1631
1692
  };
@@ -1635,8 +1696,7 @@ var slide_default = slideDirective;
1635
1696
  var keysDirective = ({ props, slots }) => {
1636
1697
  const wrap = document.createElement("div");
1637
1698
  wrap.className = "nr-keys";
1638
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
1639
- if (props.style) wrap.setAttribute("style", props.style);
1699
+ applyBaseProps(wrap, props);
1640
1700
  const sizeClass = props.size ? ` nr-kbd--${props.size}` : "";
1641
1701
  const parts = (slots.default || "").split("+").map((p) => p.trim()).filter(Boolean);
1642
1702
  parts.forEach((part, i) => {
@@ -1660,8 +1720,7 @@ var accordionCounter = 0;
1660
1720
  var accordionItemDirective = ({ props, renderSlot }) => {
1661
1721
  const item = document.createElement("div");
1662
1722
  item.className = "nr-accordion__item";
1663
- if (props.class) item.classList.add(...props.class.split(/\s+/).filter(Boolean));
1664
- if (props.style) item.setAttribute("style", props.style);
1723
+ applyBaseProps(item, props);
1665
1724
  const input = document.createElement("input");
1666
1725
  input.type = "radio";
1667
1726
  input.className = "nr-accordion__input";
@@ -1680,8 +1739,7 @@ var accordionItemDirective = ({ props, renderSlot }) => {
1680
1739
  var accordionDirective = ({ props, renderSlot }) => {
1681
1740
  const wrap = document.createElement("div");
1682
1741
  wrap.className = "nr-accordion";
1683
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
1684
- if (props.style) wrap.setAttribute("style", props.style);
1742
+ applyBaseProps(wrap, props);
1685
1743
  wrap.appendChild(renderSlot("default"));
1686
1744
  const mode = props.mode === "checkbox" ? "checkbox" : "radio";
1687
1745
  const group = `nr-acc-${++accordionCounter}`;
@@ -1694,7 +1752,6 @@ var accordionDirective = ({ props, renderSlot }) => {
1694
1752
  var accordion_default = accordionDirective;
1695
1753
 
1696
1754
  // vanilla/directives/carousel.ts
1697
- var IMG_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
1698
1755
  var carouselDirective = ({ props, slots }) => {
1699
1756
  const images = [];
1700
1757
  const raw = slots.default || "";
@@ -1710,19 +1767,9 @@ var carouselDirective = ({ props, slots }) => {
1710
1767
  wrap.className = "nr-carousel";
1711
1768
  wrap.tabIndex = 0;
1712
1769
  wrap.setAttribute("aria-label", "Image carousel");
1713
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
1714
- if (props.style) wrap.setAttribute("style", props.style);
1770
+ applyBaseProps(wrap, props);
1715
1771
  if (props.width) wrap.style.width = props.width;
1716
- if (props.float) {
1717
- if (props.float === "left" || props.float === "right") {
1718
- wrap.style.float = props.float;
1719
- if (!props.width) wrap.style.maxWidth = "50%";
1720
- wrap.style.marginInlineStart = props.float === "right" ? "1rem" : "";
1721
- wrap.style.marginInlineEnd = props.float === "left" ? "1rem" : "";
1722
- } else if (props.float === "center") {
1723
- wrap.style.marginInline = "auto";
1724
- }
1725
- }
1772
+ applyFloatStyle(wrap, props.float, props.width);
1726
1773
  const viewport = document.createElement("div");
1727
1774
  viewport.className = "nr-carousel__viewport";
1728
1775
  if (props.height) viewport.style.height = props.height;
@@ -1792,11 +1839,10 @@ var DEFAULT_LABELS = ["days", "hours", "min", "sec"];
1792
1839
  var countdownDirective = ({ props }) => {
1793
1840
  const wrap = document.createElement("div");
1794
1841
  wrap.className = "nr-countdown";
1795
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
1796
- if (props.style) wrap.setAttribute("style", props.style);
1842
+ applyBaseProps(wrap, props);
1797
1843
  const labelParts = (props.labels || "").split("|").map((s) => s.trim());
1798
1844
  const labels = DEFAULT_LABELS.map((label, i) => labelParts[i] || label);
1799
- const digits = parseInt(props.digits || "2", 10);
1845
+ const digits = parseIntProp(props.digits, 2);
1800
1846
  const targetTime = props.target ? new Date(props.target).getTime() : NaN;
1801
1847
  const hasTarget = !Number.isNaN(targetTime);
1802
1848
  const blocks = [];
@@ -1843,13 +1889,15 @@ var countdownDirective = ({ props }) => {
1843
1889
  blocks.push({ value });
1844
1890
  });
1845
1891
  render();
1846
- if (hasTarget) setInterval(render, 1e3);
1892
+ if (hasTarget) {
1893
+ const id = setInterval(render, 1e3);
1894
+ wrap.dataset.nrIntervalId = String(id);
1895
+ }
1847
1896
  return wrap;
1848
1897
  };
1849
1898
  var countdown_default = countdownDirective;
1850
1899
 
1851
1900
  // vanilla/directives/diff.ts
1852
- var IMG_RE2 = /!\[([^\]]*)\]\(([^)]+)\)/g;
1853
1901
  var diffDirective = ({ props, slots }) => {
1854
1902
  let before = (props.before || "").split("#")[0].trim();
1855
1903
  let after = (props.after || "").split("#")[0].trim();
@@ -1857,8 +1905,8 @@ var diffDirective = ({ props, slots }) => {
1857
1905
  const urls = [];
1858
1906
  const raw = slots.default || "";
1859
1907
  let m;
1860
- IMG_RE2.lastIndex = 0;
1861
- while ((m = IMG_RE2.exec(raw)) !== null) {
1908
+ IMG_RE.lastIndex = 0;
1909
+ while ((m = IMG_RE.exec(raw)) !== null) {
1862
1910
  urls.push(m[2].split("#")[0].trim());
1863
1911
  }
1864
1912
  if (!before && urls.length > 0) before = urls[0];
@@ -1871,21 +1919,11 @@ var diffDirective = ({ props, slots }) => {
1871
1919
  figure.className = "nr-diff";
1872
1920
  figure.tabIndex = 0;
1873
1921
  figure.setAttribute("aria-label", "Image comparison slider");
1874
- if (props.class) figure.classList.add(...props.class.split(/\s+/).filter(Boolean));
1875
- if (props.style) figure.setAttribute("style", props.style);
1922
+ applyBaseProps(figure, props);
1876
1923
  if (props.aspect) figure.style.aspectRatio = props.aspect;
1877
1924
  if (props.height) figure.style.height = props.height;
1878
1925
  if (props.width) figure.style.width = props.width;
1879
- if (props.float) {
1880
- if (props.float === "left" || props.float === "right") {
1881
- figure.style.float = props.float;
1882
- if (!props.width) figure.style.maxWidth = "50%";
1883
- figure.style.marginInlineStart = props.float === "right" ? "1rem" : "";
1884
- figure.style.marginInlineEnd = props.float === "left" ? "1rem" : "";
1885
- } else if (props.float === "center") {
1886
- figure.style.marginInline = "auto";
1887
- }
1888
- }
1926
+ applyFloatStyle(figure, props.float, props.width);
1889
1927
  const beforeItem = document.createElement("div");
1890
1928
  beforeItem.className = "nr-diff__item nr-diff__item--before";
1891
1929
  beforeItem.setAttribute("role", "img");
@@ -1948,8 +1986,7 @@ var diff_default = diffDirective;
1948
1986
  var hover3dDirective = ({ props, renderSlot }) => {
1949
1987
  const container = document.createElement("div");
1950
1988
  container.className = "nr-hover-3d";
1951
- if (props.class) container.classList.add(...props.class.split(/\s+/).filter(Boolean));
1952
- if (props.style) container.setAttribute("style", props.style);
1989
+ applyBaseProps(container, props);
1953
1990
  const stage = document.createElement("div");
1954
1991
  stage.className = "nr-hover-3d__stage";
1955
1992
  stage.appendChild(renderSlot("default"));
@@ -1962,14 +1999,13 @@ var hover3dDirective = ({ props, renderSlot }) => {
1962
1999
  var hover3d_default = hover3dDirective;
1963
2000
 
1964
2001
  // vanilla/directives/hovergallery.ts
1965
- var IMG_RE3 = /!\[([^\]]*)\]\(([^)]+)\)/g;
1966
2002
  var MAX_IMAGES = 10;
1967
2003
  var hovergalleryDirective = ({ props, slots }) => {
1968
2004
  const images = [];
1969
2005
  const raw = slots.default || "";
1970
2006
  let m;
1971
- IMG_RE3.lastIndex = 0;
1972
- while ((m = IMG_RE3.exec(raw)) !== null) {
2007
+ IMG_RE.lastIndex = 0;
2008
+ while ((m = IMG_RE.exec(raw)) !== null) {
1973
2009
  images.push({ src: m[2].split("#")[0].trim(), alt: m[1].trim() || "gallery image" });
1974
2010
  }
1975
2011
  if (images.length === 0) {
@@ -1978,9 +2014,8 @@ var hovergalleryDirective = ({ props, slots }) => {
1978
2014
  const count = Math.min(images.length, MAX_IMAGES);
1979
2015
  const figure = document.createElement("figure");
1980
2016
  figure.className = "nr-hover-gallery";
2017
+ applyBaseProps(figure, props);
1981
2018
  if (props.aspect) figure.style.aspectRatio = props.aspect;
1982
- if (props.class) figure.classList.add(...props.class.split(/\s+/).filter(Boolean));
1983
- if (props.style) figure.setAttribute("style", (figure.getAttribute("style") || "") + ";" + props.style);
1984
2019
  const imgEls = [];
1985
2020
  for (let i = 0; i < count; i++) {
1986
2021
  const el = document.createElement("img");
@@ -2030,28 +2065,11 @@ var hovergalleryDirective = ({ props, slots }) => {
2030
2065
  var hovergallery_default = hovergalleryDirective;
2031
2066
 
2032
2067
  // vanilla/directives/chat.ts
2033
- var CHAT_THEME_TOKENS = /* @__PURE__ */ new Set([
2034
- "primary",
2035
- "secondary",
2036
- "accent",
2037
- "neutral",
2038
- "info",
2039
- "success",
2040
- "warning",
2041
- "error"
2042
- ]);
2043
- function isArbitraryColor(value) {
2044
- if (CHAT_THEME_TOKENS.has(value)) return false;
2045
- if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
2046
- if (/^[a-zA-Z]+$/.test(value)) return true;
2047
- return false;
2048
- }
2049
2068
  var chatItemDirective = ({ props, renderSlot }) => {
2050
2069
  const side = props.side === "end" ? "end" : "start";
2051
2070
  const wrap = document.createElement("div");
2052
2071
  wrap.className = `nr-chat nr-chat--${side}`;
2053
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
2054
- if (props.style) wrap.setAttribute("style", props.style);
2072
+ applyBaseProps(wrap, props);
2055
2073
  const header = document.createElement("div");
2056
2074
  header.className = "nr-chat__header";
2057
2075
  if (props.name) {
@@ -2076,14 +2094,8 @@ var chatItemDirective = ({ props, renderSlot }) => {
2076
2094
  avatar.appendChild(img);
2077
2095
  wrap.appendChild(avatar);
2078
2096
  }
2079
- const isThemeToken = CHAT_THEME_TOKENS.has(props.color || "");
2080
- const colorClass = isThemeToken ? ` nr-chat__bubble--${props.color}` : "";
2081
2097
  const bubble = document.createElement("div");
2082
- bubble.className = `nr-chat__bubble${colorClass}`;
2083
- if (props.color && isArbitraryColor(props.color) && !isThemeToken) {
2084
- bubble.style.background = props.color;
2085
- bubble.style.color = "white";
2086
- }
2098
+ bubble.className = `nr-chat__bubble${applyColor(bubble, props.color, "nr-chat__bubble")}`;
2087
2099
  bubble.appendChild(renderSlot("default"));
2088
2100
  wrap.appendChild(bubble);
2089
2101
  if (props.footer) {
@@ -2097,8 +2109,7 @@ var chatItemDirective = ({ props, renderSlot }) => {
2097
2109
  var chatDirective = ({ props, renderSlot }) => {
2098
2110
  const wrap = document.createElement("div");
2099
2111
  wrap.className = "nr-chat";
2100
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
2101
- if (props.style) wrap.setAttribute("style", props.style);
2112
+ applyBaseProps(wrap, props);
2102
2113
  wrap.appendChild(renderSlot("default"));
2103
2114
  return wrap;
2104
2115
  };
@@ -2134,8 +2145,7 @@ function bindEventProp(el, eventProp) {
2134
2145
  var richlistItemDirective = ({ props, renderSlot }) => {
2135
2146
  const li = document.createElement("li");
2136
2147
  li.className = "nr-richlist__item";
2137
- if (props.class) li.classList.add(...props.class.split(/\s+/).filter(Boolean));
2138
- if (props.style) li.setAttribute("style", props.style);
2148
+ applyBaseProps(li, props);
2139
2149
  if (props.image) {
2140
2150
  const thumb = document.createElement("div");
2141
2151
  thumb.className = "nr-richlist__thumb";
@@ -2197,36 +2207,20 @@ var richlistItemDirective = ({ props, renderSlot }) => {
2197
2207
  var richlistDirective = ({ props, renderSlot }) => {
2198
2208
  const ul = document.createElement("ul");
2199
2209
  ul.className = "nr-richlist";
2200
- if (props.class) ul.classList.add(...props.class.split(/\s+/).filter(Boolean));
2201
- if (props.style) ul.setAttribute("style", props.style);
2210
+ applyBaseProps(ul, props);
2202
2211
  ul.appendChild(renderSlot("default"));
2203
2212
  return ul;
2204
2213
  };
2205
2214
  var richlist_default = richlistDirective;
2206
2215
 
2207
2216
  // vanilla/directives/stat.ts
2208
- var STAT_THEME_TOKENS = /* @__PURE__ */ new Set([
2209
- "primary",
2210
- "secondary",
2211
- "info",
2212
- "success",
2213
- "warning",
2214
- "error"
2215
- ]);
2216
- function isArbitraryColor2(value) {
2217
- if (STAT_THEME_TOKENS.has(value)) return false;
2218
- if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
2219
- if (/^[a-zA-Z]+$/.test(value)) return true;
2220
- return false;
2221
- }
2222
2217
  var statDirective = ({ props }) => {
2223
- const isThemeToken = STAT_THEME_TOKENS.has(props.color || "");
2224
- const colorClass = isThemeToken ? ` nr-stat--${props.color}` : "";
2218
+ const statIsThemeToken = isThemeToken(props.color);
2219
+ const colorClass = statIsThemeToken ? ` nr-stat--${props.color}` : "";
2225
2220
  const stat = document.createElement("div");
2226
2221
  stat.className = `nr-stat${colorClass}`;
2227
- if (props.class) stat.classList.add(...props.class.split(/\s+/).filter(Boolean));
2228
- if (props.style) stat.setAttribute("style", props.style);
2229
- const useInlineColor = props.color && isArbitraryColor2(props.color) && !isThemeToken;
2222
+ applyBaseProps(stat, props);
2223
+ const useInlineColor = props.color && isArbitraryColor(props.color) && !statIsThemeToken;
2230
2224
  if (props.icon) {
2231
2225
  const figure = document.createElement("div");
2232
2226
  figure.className = "nr-stat__figure";
@@ -2319,9 +2313,12 @@ function renderHtmlString(html) {
2319
2313
  processedContent = processedContent.replace(
2320
2314
  /<style(?:\s+[^>]*)?>([\s\S]*?)<\/style>/gi,
2321
2315
  (_match, cssContent) => {
2316
+ const trimmed = cssContent.trim();
2317
+ const existing = document.head.querySelector("style[data-nr-global]");
2318
+ if (existing && existing.textContent === trimmed) return "";
2322
2319
  const styleEl = document.createElement("style");
2323
2320
  styleEl.setAttribute("data-nr-global", "");
2324
- styleEl.textContent = cssContent;
2321
+ styleEl.textContent = trimmed;
2325
2322
  document.head.appendChild(styleEl);
2326
2323
  return "";
2327
2324
  }
@@ -2391,19 +2388,14 @@ function renderElement(element, ctx, allElements) {
2391
2388
  switch (element.type) {
2392
2389
  case "header": {
2393
2390
  const tag = `h${element.level}`;
2394
- let text = element.text;
2395
- const alignCenter = text.match(/^->\s*(.+?)\s*<-\s*$/);
2396
- const alignRight = text.match(/^->\s*(.+?)\s*->\s*$/);
2397
- if (alignCenter) text = alignCenter[1];
2398
- else if (alignRight) text = alignRight[1];
2399
2391
  const h = document.createElement(tag);
2400
2392
  h.id = element.id;
2401
2393
  let cls = `md-h${element.level}`;
2402
- if (alignCenter) cls += " text-center";
2403
- if (alignRight) cls += " text-right";
2394
+ if (element.align === "center") cls += " text-center";
2395
+ else if (element.align === "right") cls += " text-right";
2404
2396
  if (element.classes) cls += ` ${element.classes}`;
2405
2397
  h.className = cls;
2406
- h.appendChild(renderInline(text));
2398
+ h.appendChild(renderInline(element.text));
2407
2399
  return h;
2408
2400
  }
2409
2401
  case "paragraph": {
@@ -2659,7 +2651,7 @@ var guideData = [
2659
2651
  "title": "Card",
2660
2652
  "icon": "dashboard",
2661
2653
  "order": 1,
2662
- "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.'
2654
+ "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.'
2663
2655
  },
2664
2656
  {
2665
2657
  "id": "card-m",
@@ -2667,7 +2659,7 @@ var guideData = [
2667
2659
  "title": "Card Modal",
2668
2660
  "icon": "open_in_new",
2669
2661
  "order": 2,
2670
- "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` |'
2662
+ "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` |'
2671
2663
  },
2672
2664
  {
2673
2665
  "id": "keys",
@@ -2675,7 +2667,7 @@ var guideData = [
2675
2667
  "title": "Keys (teclas)",
2676
2668
  "icon": "keyboard",
2677
2669
  "order": 2,
2678
- "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 `+`.'
2670
+ "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 `+`.'
2679
2671
  },
2680
2672
  {
2681
2673
  "id": "accordion",
@@ -2691,7 +2683,7 @@ var guideData = [
2691
2683
  "title": "Card Link",
2692
2684
  "icon": "link",
2693
2685
  "order": 3,
2694
- "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` |'
2686
+ "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` |'
2695
2687
  },
2696
2688
  {
2697
2689
  "id": "carousel",
@@ -2699,7 +2691,7 @@ var guideData = [
2699
2691
  "title": "Carousel",
2700
2692
  "icon": "view_carousel",
2701
2693
  "order": 4,
2702
- "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.'
2694
+ "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.'
2703
2695
  },
2704
2696
  {
2705
2697
  "id": "countdown",
@@ -2715,7 +2707,7 @@ var guideData = [
2715
2707
  "title": "Diff (comparar im\xE1genes)",
2716
2708
  "icon": "compare",
2717
2709
  "order": 6,
2718
- "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).'
2710
+ "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).'
2719
2711
  },
2720
2712
  {
2721
2713
  "id": "hover-3d",
@@ -2739,7 +2731,7 @@ var guideData = [
2739
2731
  "title": "Chat",
2740
2732
  "icon": "chat_bubble",
2741
2733
  "order": 9,
2742
- "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.'
2734
+ "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.'
2743
2735
  },
2744
2736
  {
2745
2737
  "id": "richlist",
@@ -2755,7 +2747,7 @@ var guideData = [
2755
2747
  "title": "Stat",
2756
2748
  "icon": "insights",
2757
2749
  "order": 11,
2758
- "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.'
2750
+ "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.'
2759
2751
  },
2760
2752
  {
2761
2753
  "id": "details",
@@ -2763,7 +2755,7 @@ var guideData = [
2763
2755
  "title": "Details",
2764
2756
  "icon": "expand_more",
2765
2757
  "order": 1,
2766
- "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.'
2758
+ "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.'
2767
2759
  },
2768
2760
  {
2769
2761
  "id": "modal",
@@ -2771,7 +2763,7 @@ var guideData = [
2771
2763
  "title": "Modal",
2772
2764
  "icon": "open_in_full",
2773
2765
  "order": 2,
2774
- "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.'
2766
+ "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.'
2775
2767
  },
2776
2768
  {
2777
2769
  "id": "button",
@@ -2779,7 +2771,7 @@ var guideData = [
2779
2771
  "title": "Button",
2780
2772
  "icon": "touch_app",
2781
2773
  "order": 3,
2782
- "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 |'
2774
+ "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 |'
2783
2775
  },
2784
2776
  {
2785
2777
  "id": "slide",
@@ -2787,7 +2779,7 @@ var guideData = [
2787
2779
  "title": "Slide",
2788
2780
  "icon": "slideshow",
2789
2781
  "order": 4,
2790
- "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.'
2782
+ "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.'
2791
2783
  },
2792
2784
  {
2793
2785
  "id": "html-blocks",
@@ -2810,6 +2802,7 @@ var Guide = ({
2810
2802
  const [query, setQuery] = (0, import_react4.useState)("");
2811
2803
  const [selectedId, setSelectedId] = (0, import_react4.useState)(null);
2812
2804
  const [collapsed, setCollapsed] = (0, import_react4.useState)({});
2805
+ const [navOpen, setNavOpen] = (0, import_react4.useState)(false);
2813
2806
  const contentRef = (0, import_react4.useRef)(null);
2814
2807
  const groups = (0, import_react4.useMemo)(() => {
2815
2808
  const map = /* @__PURE__ */ new Map();
@@ -2837,6 +2830,7 @@ var Guide = ({
2837
2830
  (0, import_react4.useEffect)(() => {
2838
2831
  if (!open) return;
2839
2832
  setQuery("");
2833
+ setNavOpen(false);
2840
2834
  if (!selectedId) {
2841
2835
  const initial = guideData.find((e) => e.id === initialDirective) ?? guideData.find((e) => e.id === "introduccion") ?? guideData[0];
2842
2836
  setSelectedId(initial?.id ?? null);
@@ -2869,6 +2863,15 @@ var Guide = ({
2869
2863
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "nr-guide__overlay", onClick: onClose }),
2870
2864
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "nr-guide__panel", children: [
2871
2865
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("header", { className: "nr-guide__head", children: [
2866
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2867
+ "button",
2868
+ {
2869
+ className: "nr-guide__nav-toggle",
2870
+ onClick: () => setNavOpen(true),
2871
+ "aria-label": "Abrir navegaci\xF3n",
2872
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "material-icons-round", children: "menu" })
2873
+ }
2874
+ ),
2872
2875
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "material-icons-round", children: "menu_book" }),
2873
2876
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { children: "Gu\xEDa de sintaxis" }),
2874
2877
  search && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -2892,6 +2895,43 @@ var Guide = ({
2892
2895
  )
2893
2896
  ] }),
2894
2897
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "nr-guide__body", children: [
2898
+ navOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2899
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "nr-guide__nav-overlay", onClick: () => setNavOpen(false) }),
2900
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "nr-guide__nav-drawer nr-guide__nav-drawer--open", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("nav", { children: [
2901
+ filtered.map(([cat, entries]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "nr-guide__cat", children: [
2902
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2903
+ "button",
2904
+ {
2905
+ className: "nr-guide__cat-head",
2906
+ onClick: () => toggleCategory(cat),
2907
+ children: [
2908
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "material-icons-round nr-guide__cat-chevron", children: collapsed[cat] ? "chevron_right" : "expand_more" }),
2909
+ cat
2910
+ ]
2911
+ }
2912
+ ),
2913
+ !collapsed[cat] && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { className: "nr-guide__items", children: entries.map((e) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2914
+ "button",
2915
+ {
2916
+ className: `nr-guide__item${selectedId === e.id ? " nr-guide__item--active" : ""}`,
2917
+ onClick: () => {
2918
+ setSelectedId(e.id);
2919
+ setNavOpen(false);
2920
+ },
2921
+ children: [
2922
+ e.icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "material-icons-round nr-guide__item-icon", children: e.icon }),
2923
+ e.title
2924
+ ]
2925
+ }
2926
+ ) }, e.id)) })
2927
+ ] }, cat)),
2928
+ filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "nr-guide__empty", children: [
2929
+ "Sin resultados para \xAB",
2930
+ query,
2931
+ "\xBB."
2932
+ ] })
2933
+ ] }) })
2934
+ ] }),
2895
2935
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("nav", { className: "nr-guide__nav", children: [
2896
2936
  filtered.map(([cat, entries]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "nr-guide__cat", children: [
2897
2937
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(