@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/react.cjs CHANGED
@@ -1675,6 +1675,22 @@ function parseHtmlAttrs(attrsString) {
1675
1675
  }
1676
1676
 
1677
1677
  // core/parser.ts
1678
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
1679
+ "area",
1680
+ "base",
1681
+ "br",
1682
+ "col",
1683
+ "embed",
1684
+ "hr",
1685
+ "img",
1686
+ "input",
1687
+ "link",
1688
+ "meta",
1689
+ "param",
1690
+ "source",
1691
+ "track",
1692
+ "wbr"
1693
+ ]);
1678
1694
  function parseMarkdown(markdown2) {
1679
1695
  if (!markdown2) return [];
1680
1696
  const lines = markdown2.replace(/\r\n/g, "\n").replace(/\r/g, "").split("\n");
@@ -1688,8 +1704,19 @@ function parseMarkdown(markdown2) {
1688
1704
  if (match = trimmed.match(/^(#{1,6})\s+(.+)$/)) {
1689
1705
  const level = match[1].length;
1690
1706
  const rawText = match[2];
1691
- const { text: text2, classes: classes2, id: customId } = extractAttributes(rawText);
1692
- const baseId = customId || generateId(text2.replace(/->|<-/g, ""));
1707
+ const { text: rawParsedText, classes: classes2, id: customId } = extractAttributes(rawText);
1708
+ let text2 = rawParsedText;
1709
+ let align;
1710
+ const alignCenter = text2.match(/^->\s*(.+?)\s*<-\s*$/);
1711
+ const alignRight = text2.match(/^->\s*(.+?)\s*->\s*$/);
1712
+ if (alignCenter) {
1713
+ text2 = alignCenter[1];
1714
+ align = "center";
1715
+ } else if (alignRight) {
1716
+ text2 = alignRight[1];
1717
+ align = "right";
1718
+ }
1719
+ const baseId = customId || generateId(text2);
1693
1720
  let id2 = baseId;
1694
1721
  let n = 1;
1695
1722
  while (usedIds.has(id2)) {
@@ -1697,7 +1724,7 @@ function parseMarkdown(markdown2) {
1697
1724
  id2 = `${baseId}-${n}`;
1698
1725
  }
1699
1726
  usedIds.add(id2);
1700
- result.push({ type: "header", level, text: text2, id: id2, classes: classes2 || void 0 });
1727
+ result.push({ type: "header", level, text: text2, id: id2, classes: classes2 || void 0, align });
1701
1728
  i++;
1702
1729
  continue;
1703
1730
  }
@@ -1873,29 +1900,13 @@ function parseMarkdown(markdown2) {
1873
1900
  let tagStartMatch = trimmed.match(/^<([a-zA-Z][\w-]*)/);
1874
1901
  if (tagStartMatch) {
1875
1902
  const tagName = tagStartMatch[1].toLowerCase();
1876
- const voidElements = /* @__PURE__ */ new Set([
1877
- "area",
1878
- "base",
1879
- "br",
1880
- "col",
1881
- "embed",
1882
- "hr",
1883
- "img",
1884
- "input",
1885
- "link",
1886
- "meta",
1887
- "param",
1888
- "source",
1889
- "track",
1890
- "wbr"
1891
- ]);
1892
1903
  const remainingText = lines.slice(i).join("\n");
1893
1904
  const openTagRegex = new RegExp(`^\\s*<${tagName}\\b([^>]*?)>`, "i");
1894
1905
  const openTagMatch = remainingText.match(openTagRegex);
1895
1906
  if (openTagMatch) {
1896
1907
  const fullOpenTag = openTagMatch[0];
1897
1908
  const attrs = openTagMatch[1].replace(/\s+/g, " ").trim();
1898
- const isSelfClosing = fullOpenTag.endsWith("/>") || voidElements.has(tagName);
1909
+ const isSelfClosing = fullOpenTag.endsWith("/>") || VOID_ELEMENTS.has(tagName);
1899
1910
  if (isSelfClosing) {
1900
1911
  const blockText = remainingText.substring(0, openTagMatch.index + fullOpenTag.length);
1901
1912
  const consumedLines = blockText.split("\n").length;
@@ -11289,11 +11300,77 @@ function parseInlinePart(part) {
11289
11300
  return document.createTextNode(part);
11290
11301
  }
11291
11302
 
11303
+ // vanilla/utils.ts
11304
+ var THEME_TOKENS = /* @__PURE__ */ new Set([
11305
+ "primary",
11306
+ "secondary",
11307
+ "accent",
11308
+ "neutral",
11309
+ "info",
11310
+ "success",
11311
+ "warning",
11312
+ "error"
11313
+ ]);
11314
+ function isThemeToken(color) {
11315
+ return !!color && THEME_TOKENS.has(color);
11316
+ }
11317
+ function isArbitraryColor(value) {
11318
+ if (THEME_TOKENS.has(value)) return false;
11319
+ if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
11320
+ if (/^[a-zA-Z]+$/.test(value)) return true;
11321
+ return false;
11322
+ }
11323
+ function applyBaseProps(el, props) {
11324
+ if (props.class) {
11325
+ el.classList.add(...props.class.split(/\s+/).filter(Boolean));
11326
+ }
11327
+ if (props.style) {
11328
+ const styles = parseCssString(props.style);
11329
+ for (const [key, value] of Object.entries(styles)) {
11330
+ el.style.setProperty(key, String(value));
11331
+ }
11332
+ }
11333
+ }
11334
+ function applyFloatStyle(el, float, width) {
11335
+ if (!float) return;
11336
+ if (float === "left" || float === "right") {
11337
+ el.style.float = float;
11338
+ if (!width) el.style.maxWidth = "50%";
11339
+ el.style.marginInlineStart = float === "right" ? "1rem" : "";
11340
+ el.style.marginInlineEnd = float === "left" ? "1rem" : "";
11341
+ } else if (float === "center") {
11342
+ el.style.marginInline = "auto";
11343
+ }
11344
+ }
11345
+ function applyColor(el, color, classSuffix) {
11346
+ if (!color) return "";
11347
+ if (isThemeToken(color)) {
11348
+ return ` ${classSuffix}--${color}`;
11349
+ }
11350
+ if (isArbitraryColor(color)) {
11351
+ el.style.background = color;
11352
+ el.style.color = "white";
11353
+ }
11354
+ return "";
11355
+ }
11356
+ function openModal(dialog) {
11357
+ if (!dialog.open) {
11358
+ document.body.appendChild(dialog);
11359
+ dialog.showModal();
11360
+ dialog.addEventListener("close", () => dialog.remove(), { once: true });
11361
+ }
11362
+ }
11363
+ var IMG_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
11364
+ function parseIntProp(value, defaultValue) {
11365
+ if (!value) return defaultValue;
11366
+ const n = parseInt(value, 10);
11367
+ return Number.isNaN(n) ? defaultValue : n;
11368
+ }
11369
+
11292
11370
  // vanilla/directives/admonition.ts
11293
11371
  var admonitionDirective = ({ directiveType, props, renderSlot }) => {
11294
11372
  const el = createAdmonition(directiveType, props.title, props.icon);
11295
- if (props.class) el.classList.add(...props.class.split(/\s+/).filter(Boolean));
11296
- if (props.style) el.setAttribute("style", props.style);
11373
+ applyBaseProps(el, props);
11297
11374
  const body = el.querySelector(".nr-admonition__body");
11298
11375
  if (body) {
11299
11376
  body.appendChild(renderSlot("default"));
@@ -11309,8 +11386,7 @@ var detailsDirective = ({ props, renderSlot }) => {
11309
11386
  props.icon,
11310
11387
  props.defaultOpen === "true"
11311
11388
  );
11312
- if (props.class) el.classList.add(...props.class.split(/\s+/).filter(Boolean));
11313
- if (props.style) el.setAttribute("style", props.style);
11389
+ applyBaseProps(el, props);
11314
11390
  const body = el.querySelector(".nr-details__body");
11315
11391
  if (body) {
11316
11392
  body.appendChild(renderSlot("default"));
@@ -11323,12 +11399,12 @@ var details_default = detailsDirective;
11323
11399
  var modalDirective = ({ props, renderSlot }) => {
11324
11400
  const label = props.label || props.title || "Open";
11325
11401
  const modalTitle = props.title || "Modal";
11326
- const customClass = props.class || "";
11402
+ const align = props.align || "left";
11327
11403
  const wrapper = document.createElement("div");
11328
- wrapper.className = "nr-modal-trigger";
11404
+ wrapper.className = `nr-modal-trigger${align === "center" ? " nr-modal-trigger--center" : align === "right" ? " nr-modal-trigger--right" : ""}`;
11329
11405
  const btn = document.createElement("button");
11330
11406
  btn.className = `nr-button nr-button--default`;
11331
- if (customClass) btn.classList.add(...customClass.split(/\s+/).filter(Boolean));
11407
+ applyBaseProps(btn, props);
11332
11408
  const icon = props.icon || "open_in_new";
11333
11409
  if (icon) btn.appendChild(createIcon(icon));
11334
11410
  btn.appendChild(document.createTextNode(label));
@@ -11340,15 +11416,7 @@ var modalDirective = ({ props, renderSlot }) => {
11340
11416
  prose.appendChild(renderSlot("default"));
11341
11417
  body.appendChild(prose);
11342
11418
  }
11343
- btn.addEventListener("click", () => {
11344
- if (!dialog.open) {
11345
- document.body.appendChild(dialog);
11346
- dialog.showModal();
11347
- dialog.addEventListener("close", () => {
11348
- dialog.remove();
11349
- }, { once: true });
11350
- }
11351
- });
11419
+ btn.addEventListener("click", () => openModal(dialog));
11352
11420
  wrapper.appendChild(btn);
11353
11421
  wrapper.appendChild(dialog);
11354
11422
  return wrapper;
@@ -11358,19 +11426,20 @@ var modal_default = modalDirective;
11358
11426
  // vanilla/directives/button.ts
11359
11427
  var buttonDirective = ({ props, renderSlot }) => {
11360
11428
  const url = props.url || props.href || "#";
11361
- const label = props.label;
11429
+ const label = props.label || props.title;
11362
11430
  const icon = props.icon || "near_me";
11363
11431
  const target = props.target || "_blank";
11364
11432
  const customClass = props.class || "";
11433
+ const align = props.align || "left";
11365
11434
  const wrapper = document.createElement("div");
11366
- wrapper.className = "nr-button-wrap";
11435
+ wrapper.className = `nr-button-wrap${align === "center" ? " nr-button-wrap--center" : align === "right" ? " nr-button-wrap--right" : ""}`;
11367
11436
  if (label) {
11368
11437
  const a = document.createElement("a");
11369
11438
  a.href = url;
11370
11439
  a.target = target;
11371
11440
  a.rel = "noopener noreferrer";
11372
11441
  a.className = "nr-button nr-button--default";
11373
- if (customClass) a.classList.add(...customClass.split(/\s+/).filter(Boolean));
11442
+ applyBaseProps(a, props);
11374
11443
  a.appendChild(createIcon(icon));
11375
11444
  a.appendChild(document.createTextNode(label));
11376
11445
  wrapper.appendChild(a);
@@ -11381,7 +11450,7 @@ var buttonDirective = ({ props, renderSlot }) => {
11381
11450
  if (links.length > 0) {
11382
11451
  links.forEach((link) => {
11383
11452
  link.classList.add("nr-button", "nr-button--default");
11384
- if (customClass) link.classList.add(...customClass.split(/\s+/).filter(Boolean));
11453
+ applyBaseProps(link, props);
11385
11454
  });
11386
11455
  wrapper.appendChild(slotContent);
11387
11456
  } else {
@@ -11390,7 +11459,7 @@ var buttonDirective = ({ props, renderSlot }) => {
11390
11459
  a.target = target;
11391
11460
  a.rel = "noopener noreferrer";
11392
11461
  a.className = "nr-button nr-button--default";
11393
- if (customClass) a.classList.add(...customClass.split(/\s+/).filter(Boolean));
11462
+ applyBaseProps(a, props);
11394
11463
  a.appendChild(createIcon(icon));
11395
11464
  a.appendChild(slotContent);
11396
11465
  wrapper.appendChild(a);
@@ -11413,12 +11482,9 @@ var cardDirective = ({
11413
11482
  const { isSingleCard } = options || {};
11414
11483
  const isModal = directiveType === "card-m";
11415
11484
  const isLink = directiveType === "card-b";
11416
- const inlineStyles = props.style ? parseCssString(props.style) : {};
11417
11485
  const card = document.createElement("div");
11418
11486
  card.className = `nr-card${isModal || isLink ? " nr-card--interactive" : ""} ${customClass}`.trim();
11419
- for (const [key, value] of Object.entries(inlineStyles)) {
11420
- card.style.setProperty(key, String(value));
11421
- }
11487
+ applyBaseProps(card, props);
11422
11488
  if (image) {
11423
11489
  const imgWrap = document.createElement("div");
11424
11490
  imgWrap.className = `nr-card__image${isSingleCard ? " nr-card__image--tall" : ""}`;
@@ -11497,13 +11563,7 @@ var cardDirective = ({
11497
11563
  prose.appendChild(renderSlot("content") || renderSlot("default"));
11498
11564
  modalBody.appendChild(prose);
11499
11565
  }
11500
- card.addEventListener("click", () => {
11501
- if (!dialog.open) {
11502
- document.body.appendChild(dialog);
11503
- dialog.showModal();
11504
- dialog.addEventListener("close", () => dialog.remove(), { once: true });
11505
- }
11506
- });
11566
+ card.addEventListener("click", () => openModal(dialog));
11507
11567
  const frag = document.createDocumentFragment();
11508
11568
  frag.appendChild(card);
11509
11569
  frag.appendChild(dialog);
@@ -11551,8 +11611,8 @@ var slideDirective = ({
11551
11611
  if (lines.length === 0) {
11552
11612
  return document.createDocumentFragment();
11553
11613
  }
11554
- const interval = parseInt(props.interval || "3000", 10);
11555
- const speed = parseInt(props.speed || "500", 10);
11614
+ const interval = parseIntProp(props.interval, 3e3);
11615
+ const speed = parseIntProp(props.speed, 500);
11556
11616
  const rawClass = props.class || "";
11557
11617
  const inlineStyle = props.style ? parseCssString(props.style) : {};
11558
11618
  const scopeClass = `sld-${++slideCounter}`;
@@ -11603,10 +11663,11 @@ var slideDirective = ({
11603
11663
  }
11604
11664
  });
11605
11665
  if (lines.length > 1) {
11606
- setInterval(() => {
11666
+ const id = setInterval(() => {
11607
11667
  current = (current + 1) % lines.length;
11608
11668
  track.style.transform = `translateY(${-current * maxH}px)`;
11609
11669
  }, interval);
11670
+ container.dataset.nrIntervalId = String(id);
11610
11671
  }
11611
11672
  return container;
11612
11673
  };
@@ -11616,8 +11677,7 @@ var slide_default = slideDirective;
11616
11677
  var keysDirective = ({ props, slots }) => {
11617
11678
  const wrap = document.createElement("div");
11618
11679
  wrap.className = "nr-keys";
11619
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11620
- if (props.style) wrap.setAttribute("style", props.style);
11680
+ applyBaseProps(wrap, props);
11621
11681
  const sizeClass = props.size ? ` nr-kbd--${props.size}` : "";
11622
11682
  const parts = (slots.default || "").split("+").map((p) => p.trim()).filter(Boolean);
11623
11683
  parts.forEach((part, i) => {
@@ -11641,8 +11701,7 @@ var accordionCounter = 0;
11641
11701
  var accordionItemDirective = ({ props, renderSlot }) => {
11642
11702
  const item = document.createElement("div");
11643
11703
  item.className = "nr-accordion__item";
11644
- if (props.class) item.classList.add(...props.class.split(/\s+/).filter(Boolean));
11645
- if (props.style) item.setAttribute("style", props.style);
11704
+ applyBaseProps(item, props);
11646
11705
  const input = document.createElement("input");
11647
11706
  input.type = "radio";
11648
11707
  input.className = "nr-accordion__input";
@@ -11661,8 +11720,7 @@ var accordionItemDirective = ({ props, renderSlot }) => {
11661
11720
  var accordionDirective = ({ props, renderSlot }) => {
11662
11721
  const wrap = document.createElement("div");
11663
11722
  wrap.className = "nr-accordion";
11664
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11665
- if (props.style) wrap.setAttribute("style", props.style);
11723
+ applyBaseProps(wrap, props);
11666
11724
  wrap.appendChild(renderSlot("default"));
11667
11725
  const mode = props.mode === "checkbox" ? "checkbox" : "radio";
11668
11726
  const group = `nr-acc-${++accordionCounter}`;
@@ -11675,7 +11733,6 @@ var accordionDirective = ({ props, renderSlot }) => {
11675
11733
  var accordion_default = accordionDirective;
11676
11734
 
11677
11735
  // vanilla/directives/carousel.ts
11678
- var IMG_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
11679
11736
  var carouselDirective = ({ props, slots }) => {
11680
11737
  const images = [];
11681
11738
  const raw = slots.default || "";
@@ -11691,19 +11748,9 @@ var carouselDirective = ({ props, slots }) => {
11691
11748
  wrap.className = "nr-carousel";
11692
11749
  wrap.tabIndex = 0;
11693
11750
  wrap.setAttribute("aria-label", "Image carousel");
11694
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11695
- if (props.style) wrap.setAttribute("style", props.style);
11751
+ applyBaseProps(wrap, props);
11696
11752
  if (props.width) wrap.style.width = props.width;
11697
- if (props.float) {
11698
- if (props.float === "left" || props.float === "right") {
11699
- wrap.style.float = props.float;
11700
- if (!props.width) wrap.style.maxWidth = "50%";
11701
- wrap.style.marginInlineStart = props.float === "right" ? "1rem" : "";
11702
- wrap.style.marginInlineEnd = props.float === "left" ? "1rem" : "";
11703
- } else if (props.float === "center") {
11704
- wrap.style.marginInline = "auto";
11705
- }
11706
- }
11753
+ applyFloatStyle(wrap, props.float, props.width);
11707
11754
  const viewport = document.createElement("div");
11708
11755
  viewport.className = "nr-carousel__viewport";
11709
11756
  if (props.height) viewport.style.height = props.height;
@@ -11773,11 +11820,10 @@ var DEFAULT_LABELS = ["days", "hours", "min", "sec"];
11773
11820
  var countdownDirective = ({ props }) => {
11774
11821
  const wrap = document.createElement("div");
11775
11822
  wrap.className = "nr-countdown";
11776
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11777
- if (props.style) wrap.setAttribute("style", props.style);
11823
+ applyBaseProps(wrap, props);
11778
11824
  const labelParts = (props.labels || "").split("|").map((s) => s.trim());
11779
11825
  const labels = DEFAULT_LABELS.map((label, i) => labelParts[i] || label);
11780
- const digits = parseInt(props.digits || "2", 10);
11826
+ const digits = parseIntProp(props.digits, 2);
11781
11827
  const targetTime = props.target ? new Date(props.target).getTime() : NaN;
11782
11828
  const hasTarget = !Number.isNaN(targetTime);
11783
11829
  const blocks = [];
@@ -11824,13 +11870,15 @@ var countdownDirective = ({ props }) => {
11824
11870
  blocks.push({ value });
11825
11871
  });
11826
11872
  render();
11827
- if (hasTarget) setInterval(render, 1e3);
11873
+ if (hasTarget) {
11874
+ const id = setInterval(render, 1e3);
11875
+ wrap.dataset.nrIntervalId = String(id);
11876
+ }
11828
11877
  return wrap;
11829
11878
  };
11830
11879
  var countdown_default = countdownDirective;
11831
11880
 
11832
11881
  // vanilla/directives/diff.ts
11833
- var IMG_RE2 = /!\[([^\]]*)\]\(([^)]+)\)/g;
11834
11882
  var diffDirective = ({ props, slots }) => {
11835
11883
  let before = (props.before || "").split("#")[0].trim();
11836
11884
  let after = (props.after || "").split("#")[0].trim();
@@ -11838,8 +11886,8 @@ var diffDirective = ({ props, slots }) => {
11838
11886
  const urls = [];
11839
11887
  const raw = slots.default || "";
11840
11888
  let m;
11841
- IMG_RE2.lastIndex = 0;
11842
- while ((m = IMG_RE2.exec(raw)) !== null) {
11889
+ IMG_RE.lastIndex = 0;
11890
+ while ((m = IMG_RE.exec(raw)) !== null) {
11843
11891
  urls.push(m[2].split("#")[0].trim());
11844
11892
  }
11845
11893
  if (!before && urls.length > 0) before = urls[0];
@@ -11852,21 +11900,11 @@ var diffDirective = ({ props, slots }) => {
11852
11900
  figure.className = "nr-diff";
11853
11901
  figure.tabIndex = 0;
11854
11902
  figure.setAttribute("aria-label", "Image comparison slider");
11855
- if (props.class) figure.classList.add(...props.class.split(/\s+/).filter(Boolean));
11856
- if (props.style) figure.setAttribute("style", props.style);
11903
+ applyBaseProps(figure, props);
11857
11904
  if (props.aspect) figure.style.aspectRatio = props.aspect;
11858
11905
  if (props.height) figure.style.height = props.height;
11859
11906
  if (props.width) figure.style.width = props.width;
11860
- if (props.float) {
11861
- if (props.float === "left" || props.float === "right") {
11862
- figure.style.float = props.float;
11863
- if (!props.width) figure.style.maxWidth = "50%";
11864
- figure.style.marginInlineStart = props.float === "right" ? "1rem" : "";
11865
- figure.style.marginInlineEnd = props.float === "left" ? "1rem" : "";
11866
- } else if (props.float === "center") {
11867
- figure.style.marginInline = "auto";
11868
- }
11869
- }
11907
+ applyFloatStyle(figure, props.float, props.width);
11870
11908
  const beforeItem = document.createElement("div");
11871
11909
  beforeItem.className = "nr-diff__item nr-diff__item--before";
11872
11910
  beforeItem.setAttribute("role", "img");
@@ -11929,8 +11967,7 @@ var diff_default = diffDirective;
11929
11967
  var hover3dDirective = ({ props, renderSlot }) => {
11930
11968
  const container = document.createElement("div");
11931
11969
  container.className = "nr-hover-3d";
11932
- if (props.class) container.classList.add(...props.class.split(/\s+/).filter(Boolean));
11933
- if (props.style) container.setAttribute("style", props.style);
11970
+ applyBaseProps(container, props);
11934
11971
  const stage = document.createElement("div");
11935
11972
  stage.className = "nr-hover-3d__stage";
11936
11973
  stage.appendChild(renderSlot("default"));
@@ -11943,14 +11980,13 @@ var hover3dDirective = ({ props, renderSlot }) => {
11943
11980
  var hover3d_default = hover3dDirective;
11944
11981
 
11945
11982
  // vanilla/directives/hovergallery.ts
11946
- var IMG_RE3 = /!\[([^\]]*)\]\(([^)]+)\)/g;
11947
11983
  var MAX_IMAGES = 10;
11948
11984
  var hovergalleryDirective = ({ props, slots }) => {
11949
11985
  const images = [];
11950
11986
  const raw = slots.default || "";
11951
11987
  let m;
11952
- IMG_RE3.lastIndex = 0;
11953
- while ((m = IMG_RE3.exec(raw)) !== null) {
11988
+ IMG_RE.lastIndex = 0;
11989
+ while ((m = IMG_RE.exec(raw)) !== null) {
11954
11990
  images.push({ src: m[2].split("#")[0].trim(), alt: m[1].trim() || "gallery image" });
11955
11991
  }
11956
11992
  if (images.length === 0) {
@@ -11959,9 +11995,8 @@ var hovergalleryDirective = ({ props, slots }) => {
11959
11995
  const count = Math.min(images.length, MAX_IMAGES);
11960
11996
  const figure = document.createElement("figure");
11961
11997
  figure.className = "nr-hover-gallery";
11998
+ applyBaseProps(figure, props);
11962
11999
  if (props.aspect) figure.style.aspectRatio = props.aspect;
11963
- if (props.class) figure.classList.add(...props.class.split(/\s+/).filter(Boolean));
11964
- if (props.style) figure.setAttribute("style", (figure.getAttribute("style") || "") + ";" + props.style);
11965
12000
  const imgEls = [];
11966
12001
  for (let i = 0; i < count; i++) {
11967
12002
  const el = document.createElement("img");
@@ -12011,28 +12046,11 @@ var hovergalleryDirective = ({ props, slots }) => {
12011
12046
  var hovergallery_default = hovergalleryDirective;
12012
12047
 
12013
12048
  // vanilla/directives/chat.ts
12014
- var CHAT_THEME_TOKENS = /* @__PURE__ */ new Set([
12015
- "primary",
12016
- "secondary",
12017
- "accent",
12018
- "neutral",
12019
- "info",
12020
- "success",
12021
- "warning",
12022
- "error"
12023
- ]);
12024
- function isArbitraryColor(value) {
12025
- if (CHAT_THEME_TOKENS.has(value)) return false;
12026
- if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
12027
- if (/^[a-zA-Z]+$/.test(value)) return true;
12028
- return false;
12029
- }
12030
12049
  var chatItemDirective = ({ props, renderSlot }) => {
12031
12050
  const side = props.side === "end" ? "end" : "start";
12032
12051
  const wrap = document.createElement("div");
12033
12052
  wrap.className = `nr-chat nr-chat--${side}`;
12034
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
12035
- if (props.style) wrap.setAttribute("style", props.style);
12053
+ applyBaseProps(wrap, props);
12036
12054
  const header = document.createElement("div");
12037
12055
  header.className = "nr-chat__header";
12038
12056
  if (props.name) {
@@ -12057,14 +12075,8 @@ var chatItemDirective = ({ props, renderSlot }) => {
12057
12075
  avatar.appendChild(img);
12058
12076
  wrap.appendChild(avatar);
12059
12077
  }
12060
- const isThemeToken = CHAT_THEME_TOKENS.has(props.color || "");
12061
- const colorClass = isThemeToken ? ` nr-chat__bubble--${props.color}` : "";
12062
12078
  const bubble = document.createElement("div");
12063
- bubble.className = `nr-chat__bubble${colorClass}`;
12064
- if (props.color && isArbitraryColor(props.color) && !isThemeToken) {
12065
- bubble.style.background = props.color;
12066
- bubble.style.color = "white";
12067
- }
12079
+ bubble.className = `nr-chat__bubble${applyColor(bubble, props.color, "nr-chat__bubble")}`;
12068
12080
  bubble.appendChild(renderSlot("default"));
12069
12081
  wrap.appendChild(bubble);
12070
12082
  if (props.footer) {
@@ -12078,8 +12090,7 @@ var chatItemDirective = ({ props, renderSlot }) => {
12078
12090
  var chatDirective = ({ props, renderSlot }) => {
12079
12091
  const wrap = document.createElement("div");
12080
12092
  wrap.className = "nr-chat";
12081
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
12082
- if (props.style) wrap.setAttribute("style", props.style);
12093
+ applyBaseProps(wrap, props);
12083
12094
  wrap.appendChild(renderSlot("default"));
12084
12095
  return wrap;
12085
12096
  };
@@ -12115,8 +12126,7 @@ function bindEventProp(el, eventProp) {
12115
12126
  var richlistItemDirective = ({ props, renderSlot }) => {
12116
12127
  const li = document.createElement("li");
12117
12128
  li.className = "nr-richlist__item";
12118
- if (props.class) li.classList.add(...props.class.split(/\s+/).filter(Boolean));
12119
- if (props.style) li.setAttribute("style", props.style);
12129
+ applyBaseProps(li, props);
12120
12130
  if (props.image) {
12121
12131
  const thumb = document.createElement("div");
12122
12132
  thumb.className = "nr-richlist__thumb";
@@ -12178,36 +12188,20 @@ var richlistItemDirective = ({ props, renderSlot }) => {
12178
12188
  var richlistDirective = ({ props, renderSlot }) => {
12179
12189
  const ul = document.createElement("ul");
12180
12190
  ul.className = "nr-richlist";
12181
- if (props.class) ul.classList.add(...props.class.split(/\s+/).filter(Boolean));
12182
- if (props.style) ul.setAttribute("style", props.style);
12191
+ applyBaseProps(ul, props);
12183
12192
  ul.appendChild(renderSlot("default"));
12184
12193
  return ul;
12185
12194
  };
12186
12195
  var richlist_default = richlistDirective;
12187
12196
 
12188
12197
  // vanilla/directives/stat.ts
12189
- var STAT_THEME_TOKENS = /* @__PURE__ */ new Set([
12190
- "primary",
12191
- "secondary",
12192
- "info",
12193
- "success",
12194
- "warning",
12195
- "error"
12196
- ]);
12197
- function isArbitraryColor2(value) {
12198
- if (STAT_THEME_TOKENS.has(value)) return false;
12199
- if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
12200
- if (/^[a-zA-Z]+$/.test(value)) return true;
12201
- return false;
12202
- }
12203
12198
  var statDirective = ({ props }) => {
12204
- const isThemeToken = STAT_THEME_TOKENS.has(props.color || "");
12205
- const colorClass = isThemeToken ? ` nr-stat--${props.color}` : "";
12199
+ const statIsThemeToken = isThemeToken(props.color);
12200
+ const colorClass = statIsThemeToken ? ` nr-stat--${props.color}` : "";
12206
12201
  const stat = document.createElement("div");
12207
12202
  stat.className = `nr-stat${colorClass}`;
12208
- if (props.class) stat.classList.add(...props.class.split(/\s+/).filter(Boolean));
12209
- if (props.style) stat.setAttribute("style", props.style);
12210
- const useInlineColor = props.color && isArbitraryColor2(props.color) && !isThemeToken;
12203
+ applyBaseProps(stat, props);
12204
+ const useInlineColor = props.color && isArbitraryColor(props.color) && !statIsThemeToken;
12211
12205
  if (props.icon) {
12212
12206
  const figure = document.createElement("div");
12213
12207
  figure.className = "nr-stat__figure";
@@ -12300,9 +12294,12 @@ function renderHtmlString(html) {
12300
12294
  processedContent = processedContent.replace(
12301
12295
  /<style(?:\s+[^>]*)?>([\s\S]*?)<\/style>/gi,
12302
12296
  (_match, cssContent) => {
12297
+ const trimmed = cssContent.trim();
12298
+ const existing = document.head.querySelector("style[data-nr-global]");
12299
+ if (existing && existing.textContent === trimmed) return "";
12303
12300
  const styleEl = document.createElement("style");
12304
12301
  styleEl.setAttribute("data-nr-global", "");
12305
- styleEl.textContent = cssContent;
12302
+ styleEl.textContent = trimmed;
12306
12303
  document.head.appendChild(styleEl);
12307
12304
  return "";
12308
12305
  }
@@ -12372,19 +12369,14 @@ function renderElement(element, ctx, allElements) {
12372
12369
  switch (element.type) {
12373
12370
  case "header": {
12374
12371
  const tag = `h${element.level}`;
12375
- let text = element.text;
12376
- const alignCenter = text.match(/^->\s*(.+?)\s*<-\s*$/);
12377
- const alignRight = text.match(/^->\s*(.+?)\s*->\s*$/);
12378
- if (alignCenter) text = alignCenter[1];
12379
- else if (alignRight) text = alignRight[1];
12380
12372
  const h = document.createElement(tag);
12381
12373
  h.id = element.id;
12382
12374
  let cls = `md-h${element.level}`;
12383
- if (alignCenter) cls += " text-center";
12384
- if (alignRight) cls += " text-right";
12375
+ if (element.align === "center") cls += " text-center";
12376
+ else if (element.align === "right") cls += " text-right";
12385
12377
  if (element.classes) cls += ` ${element.classes}`;
12386
12378
  h.className = cls;
12387
- h.appendChild(renderInline(text));
12379
+ h.appendChild(renderInline(element.text));
12388
12380
  return h;
12389
12381
  }
12390
12382
  case "paragraph": {
@@ -12831,7 +12823,7 @@ var guideData = [
12831
12823
  "title": "Card",
12832
12824
  "icon": "dashboard",
12833
12825
  "order": 1,
12834
- "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.'
12826
+ "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.'
12835
12827
  },
12836
12828
  {
12837
12829
  "id": "card-m",
@@ -12839,7 +12831,7 @@ var guideData = [
12839
12831
  "title": "Card Modal",
12840
12832
  "icon": "open_in_new",
12841
12833
  "order": 2,
12842
- "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` |'
12834
+ "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` |'
12843
12835
  },
12844
12836
  {
12845
12837
  "id": "keys",
@@ -12847,7 +12839,7 @@ var guideData = [
12847
12839
  "title": "Keys (teclas)",
12848
12840
  "icon": "keyboard",
12849
12841
  "order": 2,
12850
- "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 `+`.'
12842
+ "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 `+`.'
12851
12843
  },
12852
12844
  {
12853
12845
  "id": "accordion",
@@ -12863,7 +12855,7 @@ var guideData = [
12863
12855
  "title": "Card Link",
12864
12856
  "icon": "link",
12865
12857
  "order": 3,
12866
- "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` |'
12858
+ "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` |'
12867
12859
  },
12868
12860
  {
12869
12861
  "id": "carousel",
@@ -12871,7 +12863,7 @@ var guideData = [
12871
12863
  "title": "Carousel",
12872
12864
  "icon": "view_carousel",
12873
12865
  "order": 4,
12874
- "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.'
12866
+ "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.'
12875
12867
  },
12876
12868
  {
12877
12869
  "id": "countdown",
@@ -12887,7 +12879,7 @@ var guideData = [
12887
12879
  "title": "Diff (comparar im\xE1genes)",
12888
12880
  "icon": "compare",
12889
12881
  "order": 6,
12890
- "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).'
12882
+ "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).'
12891
12883
  },
12892
12884
  {
12893
12885
  "id": "hover-3d",
@@ -12911,7 +12903,7 @@ var guideData = [
12911
12903
  "title": "Chat",
12912
12904
  "icon": "chat_bubble",
12913
12905
  "order": 9,
12914
- "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.'
12906
+ "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.'
12915
12907
  },
12916
12908
  {
12917
12909
  "id": "richlist",
@@ -12927,7 +12919,7 @@ var guideData = [
12927
12919
  "title": "Stat",
12928
12920
  "icon": "insights",
12929
12921
  "order": 11,
12930
- "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.'
12922
+ "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.'
12931
12923
  },
12932
12924
  {
12933
12925
  "id": "details",
@@ -12935,7 +12927,7 @@ var guideData = [
12935
12927
  "title": "Details",
12936
12928
  "icon": "expand_more",
12937
12929
  "order": 1,
12938
- "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.'
12930
+ "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.'
12939
12931
  },
12940
12932
  {
12941
12933
  "id": "modal",
@@ -12943,7 +12935,7 @@ var guideData = [
12943
12935
  "title": "Modal",
12944
12936
  "icon": "open_in_full",
12945
12937
  "order": 2,
12946
- "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.'
12938
+ "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.'
12947
12939
  },
12948
12940
  {
12949
12941
  "id": "button",
@@ -12951,7 +12943,7 @@ var guideData = [
12951
12943
  "title": "Button",
12952
12944
  "icon": "touch_app",
12953
12945
  "order": 3,
12954
- "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 |'
12946
+ "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 |'
12955
12947
  },
12956
12948
  {
12957
12949
  "id": "slide",
@@ -12959,7 +12951,7 @@ var guideData = [
12959
12951
  "title": "Slide",
12960
12952
  "icon": "slideshow",
12961
12953
  "order": 4,
12962
- "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.'
12954
+ "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.'
12963
12955
  },
12964
12956
  {
12965
12957
  "id": "html-blocks",
@@ -12982,6 +12974,7 @@ var Guide = ({
12982
12974
  const [query, setQuery] = (0, import_react4.useState)("");
12983
12975
  const [selectedId, setSelectedId] = (0, import_react4.useState)(null);
12984
12976
  const [collapsed, setCollapsed] = (0, import_react4.useState)({});
12977
+ const [navOpen, setNavOpen] = (0, import_react4.useState)(false);
12985
12978
  const contentRef = (0, import_react4.useRef)(null);
12986
12979
  const groups = (0, import_react4.useMemo)(() => {
12987
12980
  const map = /* @__PURE__ */ new Map();
@@ -13009,6 +13002,7 @@ var Guide = ({
13009
13002
  (0, import_react4.useEffect)(() => {
13010
13003
  if (!open) return;
13011
13004
  setQuery("");
13005
+ setNavOpen(false);
13012
13006
  if (!selectedId) {
13013
13007
  const initial = guideData.find((e) => e.id === initialDirective) ?? guideData.find((e) => e.id === "introduccion") ?? guideData[0];
13014
13008
  setSelectedId(initial?.id ?? null);
@@ -13041,6 +13035,15 @@ var Guide = ({
13041
13035
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "nr-guide__overlay", onClick: onClose }),
13042
13036
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "nr-guide__panel", children: [
13043
13037
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "nr-guide__head", children: [
13038
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
13039
+ "button",
13040
+ {
13041
+ className: "nr-guide__nav-toggle",
13042
+ onClick: () => setNavOpen(true),
13043
+ "aria-label": "Abrir navegaci\xF3n",
13044
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "material-icons-round", children: "menu" })
13045
+ }
13046
+ ),
13044
13047
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "material-icons-round", children: "menu_book" }),
13045
13048
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { children: "Gu\xEDa de sintaxis" }),
13046
13049
  search && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -13064,6 +13067,43 @@ var Guide = ({
13064
13067
  )
13065
13068
  ] }),
13066
13069
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "nr-guide__body", children: [
13070
+ navOpen && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
13071
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "nr-guide__nav-overlay", onClick: () => setNavOpen(false) }),
13072
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "nr-guide__nav-drawer nr-guide__nav-drawer--open", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("nav", { children: [
13073
+ filtered.map(([cat, entries]) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "nr-guide__cat", children: [
13074
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
13075
+ "button",
13076
+ {
13077
+ className: "nr-guide__cat-head",
13078
+ onClick: () => toggleCategory(cat),
13079
+ children: [
13080
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "material-icons-round nr-guide__cat-chevron", children: collapsed[cat] ? "chevron_right" : "expand_more" }),
13081
+ cat
13082
+ ]
13083
+ }
13084
+ ),
13085
+ !collapsed[cat] && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "nr-guide__items", children: entries.map((e) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
13086
+ "button",
13087
+ {
13088
+ className: `nr-guide__item${selectedId === e.id ? " nr-guide__item--active" : ""}`,
13089
+ onClick: () => {
13090
+ setSelectedId(e.id);
13091
+ setNavOpen(false);
13092
+ },
13093
+ children: [
13094
+ e.icon && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "material-icons-round nr-guide__item-icon", children: e.icon }),
13095
+ e.title
13096
+ ]
13097
+ }
13098
+ ) }, e.id)) })
13099
+ ] }, cat)),
13100
+ filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "nr-guide__empty", children: [
13101
+ "Sin resultados para \xAB",
13102
+ query,
13103
+ "\xBB."
13104
+ ] })
13105
+ ] }) })
13106
+ ] }),
13067
13107
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("nav", { className: "nr-guide__nav", children: [
13068
13108
  filtered.map(([cat, entries]) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "nr-guide__cat", children: [
13069
13109
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(