@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.js CHANGED
@@ -1653,6 +1653,22 @@ function parseHtmlAttrs(attrsString) {
1653
1653
  }
1654
1654
 
1655
1655
  // core/parser.ts
1656
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
1657
+ "area",
1658
+ "base",
1659
+ "br",
1660
+ "col",
1661
+ "embed",
1662
+ "hr",
1663
+ "img",
1664
+ "input",
1665
+ "link",
1666
+ "meta",
1667
+ "param",
1668
+ "source",
1669
+ "track",
1670
+ "wbr"
1671
+ ]);
1656
1672
  function parseMarkdown(markdown2) {
1657
1673
  if (!markdown2) return [];
1658
1674
  const lines = markdown2.replace(/\r\n/g, "\n").replace(/\r/g, "").split("\n");
@@ -1666,8 +1682,19 @@ function parseMarkdown(markdown2) {
1666
1682
  if (match = trimmed.match(/^(#{1,6})\s+(.+)$/)) {
1667
1683
  const level = match[1].length;
1668
1684
  const rawText = match[2];
1669
- const { text: text2, classes: classes2, id: customId } = extractAttributes(rawText);
1670
- const baseId = customId || generateId(text2.replace(/->|<-/g, ""));
1685
+ const { text: rawParsedText, classes: classes2, id: customId } = extractAttributes(rawText);
1686
+ let text2 = rawParsedText;
1687
+ let align;
1688
+ const alignCenter = text2.match(/^->\s*(.+?)\s*<-\s*$/);
1689
+ const alignRight = text2.match(/^->\s*(.+?)\s*->\s*$/);
1690
+ if (alignCenter) {
1691
+ text2 = alignCenter[1];
1692
+ align = "center";
1693
+ } else if (alignRight) {
1694
+ text2 = alignRight[1];
1695
+ align = "right";
1696
+ }
1697
+ const baseId = customId || generateId(text2);
1671
1698
  let id2 = baseId;
1672
1699
  let n = 1;
1673
1700
  while (usedIds.has(id2)) {
@@ -1675,7 +1702,7 @@ function parseMarkdown(markdown2) {
1675
1702
  id2 = `${baseId}-${n}`;
1676
1703
  }
1677
1704
  usedIds.add(id2);
1678
- result.push({ type: "header", level, text: text2, id: id2, classes: classes2 || void 0 });
1705
+ result.push({ type: "header", level, text: text2, id: id2, classes: classes2 || void 0, align });
1679
1706
  i++;
1680
1707
  continue;
1681
1708
  }
@@ -1851,29 +1878,13 @@ function parseMarkdown(markdown2) {
1851
1878
  let tagStartMatch = trimmed.match(/^<([a-zA-Z][\w-]*)/);
1852
1879
  if (tagStartMatch) {
1853
1880
  const tagName = tagStartMatch[1].toLowerCase();
1854
- const voidElements = /* @__PURE__ */ new Set([
1855
- "area",
1856
- "base",
1857
- "br",
1858
- "col",
1859
- "embed",
1860
- "hr",
1861
- "img",
1862
- "input",
1863
- "link",
1864
- "meta",
1865
- "param",
1866
- "source",
1867
- "track",
1868
- "wbr"
1869
- ]);
1870
1881
  const remainingText = lines.slice(i).join("\n");
1871
1882
  const openTagRegex = new RegExp(`^\\s*<${tagName}\\b([^>]*?)>`, "i");
1872
1883
  const openTagMatch = remainingText.match(openTagRegex);
1873
1884
  if (openTagMatch) {
1874
1885
  const fullOpenTag = openTagMatch[0];
1875
1886
  const attrs = openTagMatch[1].replace(/\s+/g, " ").trim();
1876
- const isSelfClosing = fullOpenTag.endsWith("/>") || voidElements.has(tagName);
1887
+ const isSelfClosing = fullOpenTag.endsWith("/>") || VOID_ELEMENTS.has(tagName);
1877
1888
  if (isSelfClosing) {
1878
1889
  const blockText = remainingText.substring(0, openTagMatch.index + fullOpenTag.length);
1879
1890
  const consumedLines = blockText.split("\n").length;
@@ -11267,11 +11278,77 @@ function parseInlinePart(part) {
11267
11278
  return document.createTextNode(part);
11268
11279
  }
11269
11280
 
11281
+ // vanilla/utils.ts
11282
+ var THEME_TOKENS = /* @__PURE__ */ new Set([
11283
+ "primary",
11284
+ "secondary",
11285
+ "accent",
11286
+ "neutral",
11287
+ "info",
11288
+ "success",
11289
+ "warning",
11290
+ "error"
11291
+ ]);
11292
+ function isThemeToken(color) {
11293
+ return !!color && THEME_TOKENS.has(color);
11294
+ }
11295
+ function isArbitraryColor(value) {
11296
+ if (THEME_TOKENS.has(value)) return false;
11297
+ if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
11298
+ if (/^[a-zA-Z]+$/.test(value)) return true;
11299
+ return false;
11300
+ }
11301
+ function applyBaseProps(el, props) {
11302
+ if (props.class) {
11303
+ el.classList.add(...props.class.split(/\s+/).filter(Boolean));
11304
+ }
11305
+ if (props.style) {
11306
+ const styles = parseCssString(props.style);
11307
+ for (const [key, value] of Object.entries(styles)) {
11308
+ el.style.setProperty(key, String(value));
11309
+ }
11310
+ }
11311
+ }
11312
+ function applyFloatStyle(el, float, width) {
11313
+ if (!float) return;
11314
+ if (float === "left" || float === "right") {
11315
+ el.style.float = float;
11316
+ if (!width) el.style.maxWidth = "50%";
11317
+ el.style.marginInlineStart = float === "right" ? "1rem" : "";
11318
+ el.style.marginInlineEnd = float === "left" ? "1rem" : "";
11319
+ } else if (float === "center") {
11320
+ el.style.marginInline = "auto";
11321
+ }
11322
+ }
11323
+ function applyColor(el, color, classSuffix) {
11324
+ if (!color) return "";
11325
+ if (isThemeToken(color)) {
11326
+ return ` ${classSuffix}--${color}`;
11327
+ }
11328
+ if (isArbitraryColor(color)) {
11329
+ el.style.background = color;
11330
+ el.style.color = "white";
11331
+ }
11332
+ return "";
11333
+ }
11334
+ function openModal(dialog) {
11335
+ if (!dialog.open) {
11336
+ document.body.appendChild(dialog);
11337
+ dialog.showModal();
11338
+ dialog.addEventListener("close", () => dialog.remove(), { once: true });
11339
+ }
11340
+ }
11341
+ var IMG_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
11342
+ function parseIntProp(value, defaultValue) {
11343
+ if (!value) return defaultValue;
11344
+ const n = parseInt(value, 10);
11345
+ return Number.isNaN(n) ? defaultValue : n;
11346
+ }
11347
+
11270
11348
  // vanilla/directives/admonition.ts
11271
11349
  var admonitionDirective = ({ directiveType, props, renderSlot }) => {
11272
11350
  const el = createAdmonition(directiveType, props.title, props.icon);
11273
- if (props.class) el.classList.add(...props.class.split(/\s+/).filter(Boolean));
11274
- if (props.style) el.setAttribute("style", props.style);
11351
+ applyBaseProps(el, props);
11275
11352
  const body = el.querySelector(".nr-admonition__body");
11276
11353
  if (body) {
11277
11354
  body.appendChild(renderSlot("default"));
@@ -11287,8 +11364,7 @@ var detailsDirective = ({ props, renderSlot }) => {
11287
11364
  props.icon,
11288
11365
  props.defaultOpen === "true"
11289
11366
  );
11290
- if (props.class) el.classList.add(...props.class.split(/\s+/).filter(Boolean));
11291
- if (props.style) el.setAttribute("style", props.style);
11367
+ applyBaseProps(el, props);
11292
11368
  const body = el.querySelector(".nr-details__body");
11293
11369
  if (body) {
11294
11370
  body.appendChild(renderSlot("default"));
@@ -11301,12 +11377,12 @@ var details_default = detailsDirective;
11301
11377
  var modalDirective = ({ props, renderSlot }) => {
11302
11378
  const label = props.label || props.title || "Open";
11303
11379
  const modalTitle = props.title || "Modal";
11304
- const customClass = props.class || "";
11380
+ const align = props.align || "left";
11305
11381
  const wrapper = document.createElement("div");
11306
- wrapper.className = "nr-modal-trigger";
11382
+ wrapper.className = `nr-modal-trigger${align === "center" ? " nr-modal-trigger--center" : align === "right" ? " nr-modal-trigger--right" : ""}`;
11307
11383
  const btn = document.createElement("button");
11308
11384
  btn.className = `nr-button nr-button--default`;
11309
- if (customClass) btn.classList.add(...customClass.split(/\s+/).filter(Boolean));
11385
+ applyBaseProps(btn, props);
11310
11386
  const icon = props.icon || "open_in_new";
11311
11387
  if (icon) btn.appendChild(createIcon(icon));
11312
11388
  btn.appendChild(document.createTextNode(label));
@@ -11318,15 +11394,7 @@ var modalDirective = ({ props, renderSlot }) => {
11318
11394
  prose.appendChild(renderSlot("default"));
11319
11395
  body.appendChild(prose);
11320
11396
  }
11321
- btn.addEventListener("click", () => {
11322
- if (!dialog.open) {
11323
- document.body.appendChild(dialog);
11324
- dialog.showModal();
11325
- dialog.addEventListener("close", () => {
11326
- dialog.remove();
11327
- }, { once: true });
11328
- }
11329
- });
11397
+ btn.addEventListener("click", () => openModal(dialog));
11330
11398
  wrapper.appendChild(btn);
11331
11399
  wrapper.appendChild(dialog);
11332
11400
  return wrapper;
@@ -11336,19 +11404,20 @@ var modal_default = modalDirective;
11336
11404
  // vanilla/directives/button.ts
11337
11405
  var buttonDirective = ({ props, renderSlot }) => {
11338
11406
  const url = props.url || props.href || "#";
11339
- const label = props.label;
11407
+ const label = props.label || props.title;
11340
11408
  const icon = props.icon || "near_me";
11341
11409
  const target = props.target || "_blank";
11342
11410
  const customClass = props.class || "";
11411
+ const align = props.align || "left";
11343
11412
  const wrapper = document.createElement("div");
11344
- wrapper.className = "nr-button-wrap";
11413
+ wrapper.className = `nr-button-wrap${align === "center" ? " nr-button-wrap--center" : align === "right" ? " nr-button-wrap--right" : ""}`;
11345
11414
  if (label) {
11346
11415
  const a = document.createElement("a");
11347
11416
  a.href = url;
11348
11417
  a.target = target;
11349
11418
  a.rel = "noopener noreferrer";
11350
11419
  a.className = "nr-button nr-button--default";
11351
- if (customClass) a.classList.add(...customClass.split(/\s+/).filter(Boolean));
11420
+ applyBaseProps(a, props);
11352
11421
  a.appendChild(createIcon(icon));
11353
11422
  a.appendChild(document.createTextNode(label));
11354
11423
  wrapper.appendChild(a);
@@ -11359,7 +11428,7 @@ var buttonDirective = ({ props, renderSlot }) => {
11359
11428
  if (links.length > 0) {
11360
11429
  links.forEach((link) => {
11361
11430
  link.classList.add("nr-button", "nr-button--default");
11362
- if (customClass) link.classList.add(...customClass.split(/\s+/).filter(Boolean));
11431
+ applyBaseProps(link, props);
11363
11432
  });
11364
11433
  wrapper.appendChild(slotContent);
11365
11434
  } else {
@@ -11368,7 +11437,7 @@ var buttonDirective = ({ props, renderSlot }) => {
11368
11437
  a.target = target;
11369
11438
  a.rel = "noopener noreferrer";
11370
11439
  a.className = "nr-button nr-button--default";
11371
- if (customClass) a.classList.add(...customClass.split(/\s+/).filter(Boolean));
11440
+ applyBaseProps(a, props);
11372
11441
  a.appendChild(createIcon(icon));
11373
11442
  a.appendChild(slotContent);
11374
11443
  wrapper.appendChild(a);
@@ -11391,12 +11460,9 @@ var cardDirective = ({
11391
11460
  const { isSingleCard } = options || {};
11392
11461
  const isModal = directiveType === "card-m";
11393
11462
  const isLink = directiveType === "card-b";
11394
- const inlineStyles = props.style ? parseCssString(props.style) : {};
11395
11463
  const card = document.createElement("div");
11396
11464
  card.className = `nr-card${isModal || isLink ? " nr-card--interactive" : ""} ${customClass}`.trim();
11397
- for (const [key, value] of Object.entries(inlineStyles)) {
11398
- card.style.setProperty(key, String(value));
11399
- }
11465
+ applyBaseProps(card, props);
11400
11466
  if (image) {
11401
11467
  const imgWrap = document.createElement("div");
11402
11468
  imgWrap.className = `nr-card__image${isSingleCard ? " nr-card__image--tall" : ""}`;
@@ -11475,13 +11541,7 @@ var cardDirective = ({
11475
11541
  prose.appendChild(renderSlot("content") || renderSlot("default"));
11476
11542
  modalBody.appendChild(prose);
11477
11543
  }
11478
- card.addEventListener("click", () => {
11479
- if (!dialog.open) {
11480
- document.body.appendChild(dialog);
11481
- dialog.showModal();
11482
- dialog.addEventListener("close", () => dialog.remove(), { once: true });
11483
- }
11484
- });
11544
+ card.addEventListener("click", () => openModal(dialog));
11485
11545
  const frag = document.createDocumentFragment();
11486
11546
  frag.appendChild(card);
11487
11547
  frag.appendChild(dialog);
@@ -11529,8 +11589,8 @@ var slideDirective = ({
11529
11589
  if (lines.length === 0) {
11530
11590
  return document.createDocumentFragment();
11531
11591
  }
11532
- const interval = parseInt(props.interval || "3000", 10);
11533
- const speed = parseInt(props.speed || "500", 10);
11592
+ const interval = parseIntProp(props.interval, 3e3);
11593
+ const speed = parseIntProp(props.speed, 500);
11534
11594
  const rawClass = props.class || "";
11535
11595
  const inlineStyle = props.style ? parseCssString(props.style) : {};
11536
11596
  const scopeClass = `sld-${++slideCounter}`;
@@ -11581,10 +11641,11 @@ var slideDirective = ({
11581
11641
  }
11582
11642
  });
11583
11643
  if (lines.length > 1) {
11584
- setInterval(() => {
11644
+ const id = setInterval(() => {
11585
11645
  current = (current + 1) % lines.length;
11586
11646
  track.style.transform = `translateY(${-current * maxH}px)`;
11587
11647
  }, interval);
11648
+ container.dataset.nrIntervalId = String(id);
11588
11649
  }
11589
11650
  return container;
11590
11651
  };
@@ -11594,8 +11655,7 @@ var slide_default = slideDirective;
11594
11655
  var keysDirective = ({ props, slots }) => {
11595
11656
  const wrap = document.createElement("div");
11596
11657
  wrap.className = "nr-keys";
11597
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11598
- if (props.style) wrap.setAttribute("style", props.style);
11658
+ applyBaseProps(wrap, props);
11599
11659
  const sizeClass = props.size ? ` nr-kbd--${props.size}` : "";
11600
11660
  const parts = (slots.default || "").split("+").map((p) => p.trim()).filter(Boolean);
11601
11661
  parts.forEach((part, i) => {
@@ -11619,8 +11679,7 @@ var accordionCounter = 0;
11619
11679
  var accordionItemDirective = ({ props, renderSlot }) => {
11620
11680
  const item = document.createElement("div");
11621
11681
  item.className = "nr-accordion__item";
11622
- if (props.class) item.classList.add(...props.class.split(/\s+/).filter(Boolean));
11623
- if (props.style) item.setAttribute("style", props.style);
11682
+ applyBaseProps(item, props);
11624
11683
  const input = document.createElement("input");
11625
11684
  input.type = "radio";
11626
11685
  input.className = "nr-accordion__input";
@@ -11639,8 +11698,7 @@ var accordionItemDirective = ({ props, renderSlot }) => {
11639
11698
  var accordionDirective = ({ props, renderSlot }) => {
11640
11699
  const wrap = document.createElement("div");
11641
11700
  wrap.className = "nr-accordion";
11642
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11643
- if (props.style) wrap.setAttribute("style", props.style);
11701
+ applyBaseProps(wrap, props);
11644
11702
  wrap.appendChild(renderSlot("default"));
11645
11703
  const mode = props.mode === "checkbox" ? "checkbox" : "radio";
11646
11704
  const group = `nr-acc-${++accordionCounter}`;
@@ -11653,7 +11711,6 @@ var accordionDirective = ({ props, renderSlot }) => {
11653
11711
  var accordion_default = accordionDirective;
11654
11712
 
11655
11713
  // vanilla/directives/carousel.ts
11656
- var IMG_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
11657
11714
  var carouselDirective = ({ props, slots }) => {
11658
11715
  const images = [];
11659
11716
  const raw = slots.default || "";
@@ -11669,19 +11726,9 @@ var carouselDirective = ({ props, slots }) => {
11669
11726
  wrap.className = "nr-carousel";
11670
11727
  wrap.tabIndex = 0;
11671
11728
  wrap.setAttribute("aria-label", "Image carousel");
11672
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11673
- if (props.style) wrap.setAttribute("style", props.style);
11729
+ applyBaseProps(wrap, props);
11674
11730
  if (props.width) wrap.style.width = props.width;
11675
- if (props.float) {
11676
- if (props.float === "left" || props.float === "right") {
11677
- wrap.style.float = props.float;
11678
- if (!props.width) wrap.style.maxWidth = "50%";
11679
- wrap.style.marginInlineStart = props.float === "right" ? "1rem" : "";
11680
- wrap.style.marginInlineEnd = props.float === "left" ? "1rem" : "";
11681
- } else if (props.float === "center") {
11682
- wrap.style.marginInline = "auto";
11683
- }
11684
- }
11731
+ applyFloatStyle(wrap, props.float, props.width);
11685
11732
  const viewport = document.createElement("div");
11686
11733
  viewport.className = "nr-carousel__viewport";
11687
11734
  if (props.height) viewport.style.height = props.height;
@@ -11751,11 +11798,10 @@ var DEFAULT_LABELS = ["days", "hours", "min", "sec"];
11751
11798
  var countdownDirective = ({ props }) => {
11752
11799
  const wrap = document.createElement("div");
11753
11800
  wrap.className = "nr-countdown";
11754
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
11755
- if (props.style) wrap.setAttribute("style", props.style);
11801
+ applyBaseProps(wrap, props);
11756
11802
  const labelParts = (props.labels || "").split("|").map((s) => s.trim());
11757
11803
  const labels = DEFAULT_LABELS.map((label, i) => labelParts[i] || label);
11758
- const digits = parseInt(props.digits || "2", 10);
11804
+ const digits = parseIntProp(props.digits, 2);
11759
11805
  const targetTime = props.target ? new Date(props.target).getTime() : NaN;
11760
11806
  const hasTarget = !Number.isNaN(targetTime);
11761
11807
  const blocks = [];
@@ -11802,13 +11848,15 @@ var countdownDirective = ({ props }) => {
11802
11848
  blocks.push({ value });
11803
11849
  });
11804
11850
  render();
11805
- if (hasTarget) setInterval(render, 1e3);
11851
+ if (hasTarget) {
11852
+ const id = setInterval(render, 1e3);
11853
+ wrap.dataset.nrIntervalId = String(id);
11854
+ }
11806
11855
  return wrap;
11807
11856
  };
11808
11857
  var countdown_default = countdownDirective;
11809
11858
 
11810
11859
  // vanilla/directives/diff.ts
11811
- var IMG_RE2 = /!\[([^\]]*)\]\(([^)]+)\)/g;
11812
11860
  var diffDirective = ({ props, slots }) => {
11813
11861
  let before = (props.before || "").split("#")[0].trim();
11814
11862
  let after = (props.after || "").split("#")[0].trim();
@@ -11816,8 +11864,8 @@ var diffDirective = ({ props, slots }) => {
11816
11864
  const urls = [];
11817
11865
  const raw = slots.default || "";
11818
11866
  let m;
11819
- IMG_RE2.lastIndex = 0;
11820
- while ((m = IMG_RE2.exec(raw)) !== null) {
11867
+ IMG_RE.lastIndex = 0;
11868
+ while ((m = IMG_RE.exec(raw)) !== null) {
11821
11869
  urls.push(m[2].split("#")[0].trim());
11822
11870
  }
11823
11871
  if (!before && urls.length > 0) before = urls[0];
@@ -11830,21 +11878,11 @@ var diffDirective = ({ props, slots }) => {
11830
11878
  figure.className = "nr-diff";
11831
11879
  figure.tabIndex = 0;
11832
11880
  figure.setAttribute("aria-label", "Image comparison slider");
11833
- if (props.class) figure.classList.add(...props.class.split(/\s+/).filter(Boolean));
11834
- if (props.style) figure.setAttribute("style", props.style);
11881
+ applyBaseProps(figure, props);
11835
11882
  if (props.aspect) figure.style.aspectRatio = props.aspect;
11836
11883
  if (props.height) figure.style.height = props.height;
11837
11884
  if (props.width) figure.style.width = props.width;
11838
- if (props.float) {
11839
- if (props.float === "left" || props.float === "right") {
11840
- figure.style.float = props.float;
11841
- if (!props.width) figure.style.maxWidth = "50%";
11842
- figure.style.marginInlineStart = props.float === "right" ? "1rem" : "";
11843
- figure.style.marginInlineEnd = props.float === "left" ? "1rem" : "";
11844
- } else if (props.float === "center") {
11845
- figure.style.marginInline = "auto";
11846
- }
11847
- }
11885
+ applyFloatStyle(figure, props.float, props.width);
11848
11886
  const beforeItem = document.createElement("div");
11849
11887
  beforeItem.className = "nr-diff__item nr-diff__item--before";
11850
11888
  beforeItem.setAttribute("role", "img");
@@ -11907,8 +11945,7 @@ var diff_default = diffDirective;
11907
11945
  var hover3dDirective = ({ props, renderSlot }) => {
11908
11946
  const container = document.createElement("div");
11909
11947
  container.className = "nr-hover-3d";
11910
- if (props.class) container.classList.add(...props.class.split(/\s+/).filter(Boolean));
11911
- if (props.style) container.setAttribute("style", props.style);
11948
+ applyBaseProps(container, props);
11912
11949
  const stage = document.createElement("div");
11913
11950
  stage.className = "nr-hover-3d__stage";
11914
11951
  stage.appendChild(renderSlot("default"));
@@ -11921,14 +11958,13 @@ var hover3dDirective = ({ props, renderSlot }) => {
11921
11958
  var hover3d_default = hover3dDirective;
11922
11959
 
11923
11960
  // vanilla/directives/hovergallery.ts
11924
- var IMG_RE3 = /!\[([^\]]*)\]\(([^)]+)\)/g;
11925
11961
  var MAX_IMAGES = 10;
11926
11962
  var hovergalleryDirective = ({ props, slots }) => {
11927
11963
  const images = [];
11928
11964
  const raw = slots.default || "";
11929
11965
  let m;
11930
- IMG_RE3.lastIndex = 0;
11931
- while ((m = IMG_RE3.exec(raw)) !== null) {
11966
+ IMG_RE.lastIndex = 0;
11967
+ while ((m = IMG_RE.exec(raw)) !== null) {
11932
11968
  images.push({ src: m[2].split("#")[0].trim(), alt: m[1].trim() || "gallery image" });
11933
11969
  }
11934
11970
  if (images.length === 0) {
@@ -11937,9 +11973,8 @@ var hovergalleryDirective = ({ props, slots }) => {
11937
11973
  const count = Math.min(images.length, MAX_IMAGES);
11938
11974
  const figure = document.createElement("figure");
11939
11975
  figure.className = "nr-hover-gallery";
11976
+ applyBaseProps(figure, props);
11940
11977
  if (props.aspect) figure.style.aspectRatio = props.aspect;
11941
- if (props.class) figure.classList.add(...props.class.split(/\s+/).filter(Boolean));
11942
- if (props.style) figure.setAttribute("style", (figure.getAttribute("style") || "") + ";" + props.style);
11943
11978
  const imgEls = [];
11944
11979
  for (let i = 0; i < count; i++) {
11945
11980
  const el = document.createElement("img");
@@ -11989,28 +12024,11 @@ var hovergalleryDirective = ({ props, slots }) => {
11989
12024
  var hovergallery_default = hovergalleryDirective;
11990
12025
 
11991
12026
  // vanilla/directives/chat.ts
11992
- var CHAT_THEME_TOKENS = /* @__PURE__ */ new Set([
11993
- "primary",
11994
- "secondary",
11995
- "accent",
11996
- "neutral",
11997
- "info",
11998
- "success",
11999
- "warning",
12000
- "error"
12001
- ]);
12002
- function isArbitraryColor(value) {
12003
- if (CHAT_THEME_TOKENS.has(value)) return false;
12004
- if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
12005
- if (/^[a-zA-Z]+$/.test(value)) return true;
12006
- return false;
12007
- }
12008
12027
  var chatItemDirective = ({ props, renderSlot }) => {
12009
12028
  const side = props.side === "end" ? "end" : "start";
12010
12029
  const wrap = document.createElement("div");
12011
12030
  wrap.className = `nr-chat nr-chat--${side}`;
12012
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
12013
- if (props.style) wrap.setAttribute("style", props.style);
12031
+ applyBaseProps(wrap, props);
12014
12032
  const header = document.createElement("div");
12015
12033
  header.className = "nr-chat__header";
12016
12034
  if (props.name) {
@@ -12035,14 +12053,8 @@ var chatItemDirective = ({ props, renderSlot }) => {
12035
12053
  avatar.appendChild(img);
12036
12054
  wrap.appendChild(avatar);
12037
12055
  }
12038
- const isThemeToken = CHAT_THEME_TOKENS.has(props.color || "");
12039
- const colorClass = isThemeToken ? ` nr-chat__bubble--${props.color}` : "";
12040
12056
  const bubble = document.createElement("div");
12041
- bubble.className = `nr-chat__bubble${colorClass}`;
12042
- if (props.color && isArbitraryColor(props.color) && !isThemeToken) {
12043
- bubble.style.background = props.color;
12044
- bubble.style.color = "white";
12045
- }
12057
+ bubble.className = `nr-chat__bubble${applyColor(bubble, props.color, "nr-chat__bubble")}`;
12046
12058
  bubble.appendChild(renderSlot("default"));
12047
12059
  wrap.appendChild(bubble);
12048
12060
  if (props.footer) {
@@ -12056,8 +12068,7 @@ var chatItemDirective = ({ props, renderSlot }) => {
12056
12068
  var chatDirective = ({ props, renderSlot }) => {
12057
12069
  const wrap = document.createElement("div");
12058
12070
  wrap.className = "nr-chat";
12059
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
12060
- if (props.style) wrap.setAttribute("style", props.style);
12071
+ applyBaseProps(wrap, props);
12061
12072
  wrap.appendChild(renderSlot("default"));
12062
12073
  return wrap;
12063
12074
  };
@@ -12093,8 +12104,7 @@ function bindEventProp(el, eventProp) {
12093
12104
  var richlistItemDirective = ({ props, renderSlot }) => {
12094
12105
  const li = document.createElement("li");
12095
12106
  li.className = "nr-richlist__item";
12096
- if (props.class) li.classList.add(...props.class.split(/\s+/).filter(Boolean));
12097
- if (props.style) li.setAttribute("style", props.style);
12107
+ applyBaseProps(li, props);
12098
12108
  if (props.image) {
12099
12109
  const thumb = document.createElement("div");
12100
12110
  thumb.className = "nr-richlist__thumb";
@@ -12156,36 +12166,20 @@ var richlistItemDirective = ({ props, renderSlot }) => {
12156
12166
  var richlistDirective = ({ props, renderSlot }) => {
12157
12167
  const ul = document.createElement("ul");
12158
12168
  ul.className = "nr-richlist";
12159
- if (props.class) ul.classList.add(...props.class.split(/\s+/).filter(Boolean));
12160
- if (props.style) ul.setAttribute("style", props.style);
12169
+ applyBaseProps(ul, props);
12161
12170
  ul.appendChild(renderSlot("default"));
12162
12171
  return ul;
12163
12172
  };
12164
12173
  var richlist_default = richlistDirective;
12165
12174
 
12166
12175
  // vanilla/directives/stat.ts
12167
- var STAT_THEME_TOKENS = /* @__PURE__ */ new Set([
12168
- "primary",
12169
- "secondary",
12170
- "info",
12171
- "success",
12172
- "warning",
12173
- "error"
12174
- ]);
12175
- function isArbitraryColor2(value) {
12176
- if (STAT_THEME_TOKENS.has(value)) return false;
12177
- if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
12178
- if (/^[a-zA-Z]+$/.test(value)) return true;
12179
- return false;
12180
- }
12181
12176
  var statDirective = ({ props }) => {
12182
- const isThemeToken = STAT_THEME_TOKENS.has(props.color || "");
12183
- const colorClass = isThemeToken ? ` nr-stat--${props.color}` : "";
12177
+ const statIsThemeToken = isThemeToken(props.color);
12178
+ const colorClass = statIsThemeToken ? ` nr-stat--${props.color}` : "";
12184
12179
  const stat = document.createElement("div");
12185
12180
  stat.className = `nr-stat${colorClass}`;
12186
- if (props.class) stat.classList.add(...props.class.split(/\s+/).filter(Boolean));
12187
- if (props.style) stat.setAttribute("style", props.style);
12188
- const useInlineColor = props.color && isArbitraryColor2(props.color) && !isThemeToken;
12181
+ applyBaseProps(stat, props);
12182
+ const useInlineColor = props.color && isArbitraryColor(props.color) && !statIsThemeToken;
12189
12183
  if (props.icon) {
12190
12184
  const figure = document.createElement("div");
12191
12185
  figure.className = "nr-stat__figure";
@@ -12278,9 +12272,12 @@ function renderHtmlString(html) {
12278
12272
  processedContent = processedContent.replace(
12279
12273
  /<style(?:\s+[^>]*)?>([\s\S]*?)<\/style>/gi,
12280
12274
  (_match, cssContent) => {
12275
+ const trimmed = cssContent.trim();
12276
+ const existing = document.head.querySelector("style[data-nr-global]");
12277
+ if (existing && existing.textContent === trimmed) return "";
12281
12278
  const styleEl = document.createElement("style");
12282
12279
  styleEl.setAttribute("data-nr-global", "");
12283
- styleEl.textContent = cssContent;
12280
+ styleEl.textContent = trimmed;
12284
12281
  document.head.appendChild(styleEl);
12285
12282
  return "";
12286
12283
  }
@@ -12350,19 +12347,14 @@ function renderElement(element, ctx, allElements) {
12350
12347
  switch (element.type) {
12351
12348
  case "header": {
12352
12349
  const tag = `h${element.level}`;
12353
- let text = element.text;
12354
- const alignCenter = text.match(/^->\s*(.+?)\s*<-\s*$/);
12355
- const alignRight = text.match(/^->\s*(.+?)\s*->\s*$/);
12356
- if (alignCenter) text = alignCenter[1];
12357
- else if (alignRight) text = alignRight[1];
12358
12350
  const h = document.createElement(tag);
12359
12351
  h.id = element.id;
12360
12352
  let cls = `md-h${element.level}`;
12361
- if (alignCenter) cls += " text-center";
12362
- if (alignRight) cls += " text-right";
12353
+ if (element.align === "center") cls += " text-center";
12354
+ else if (element.align === "right") cls += " text-right";
12363
12355
  if (element.classes) cls += ` ${element.classes}`;
12364
12356
  h.className = cls;
12365
- h.appendChild(renderInline(text));
12357
+ h.appendChild(renderInline(element.text));
12366
12358
  return h;
12367
12359
  }
12368
12360
  case "paragraph": {
@@ -12809,7 +12801,7 @@ var guideData = [
12809
12801
  "title": "Card",
12810
12802
  "icon": "dashboard",
12811
12803
  "order": 1,
12812
- "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.'
12804
+ "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.'
12813
12805
  },
12814
12806
  {
12815
12807
  "id": "card-m",
@@ -12817,7 +12809,7 @@ var guideData = [
12817
12809
  "title": "Card Modal",
12818
12810
  "icon": "open_in_new",
12819
12811
  "order": 2,
12820
- "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` |'
12812
+ "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` |'
12821
12813
  },
12822
12814
  {
12823
12815
  "id": "keys",
@@ -12825,7 +12817,7 @@ var guideData = [
12825
12817
  "title": "Keys (teclas)",
12826
12818
  "icon": "keyboard",
12827
12819
  "order": 2,
12828
- "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 `+`.'
12820
+ "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 `+`.'
12829
12821
  },
12830
12822
  {
12831
12823
  "id": "accordion",
@@ -12841,7 +12833,7 @@ var guideData = [
12841
12833
  "title": "Card Link",
12842
12834
  "icon": "link",
12843
12835
  "order": 3,
12844
- "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` |'
12836
+ "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` |'
12845
12837
  },
12846
12838
  {
12847
12839
  "id": "carousel",
@@ -12849,7 +12841,7 @@ var guideData = [
12849
12841
  "title": "Carousel",
12850
12842
  "icon": "view_carousel",
12851
12843
  "order": 4,
12852
- "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.'
12844
+ "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.'
12853
12845
  },
12854
12846
  {
12855
12847
  "id": "countdown",
@@ -12865,7 +12857,7 @@ var guideData = [
12865
12857
  "title": "Diff (comparar im\xE1genes)",
12866
12858
  "icon": "compare",
12867
12859
  "order": 6,
12868
- "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).'
12860
+ "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).'
12869
12861
  },
12870
12862
  {
12871
12863
  "id": "hover-3d",
@@ -12889,7 +12881,7 @@ var guideData = [
12889
12881
  "title": "Chat",
12890
12882
  "icon": "chat_bubble",
12891
12883
  "order": 9,
12892
- "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.'
12884
+ "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.'
12893
12885
  },
12894
12886
  {
12895
12887
  "id": "richlist",
@@ -12905,7 +12897,7 @@ var guideData = [
12905
12897
  "title": "Stat",
12906
12898
  "icon": "insights",
12907
12899
  "order": 11,
12908
- "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.'
12900
+ "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.'
12909
12901
  },
12910
12902
  {
12911
12903
  "id": "details",
@@ -12913,7 +12905,7 @@ var guideData = [
12913
12905
  "title": "Details",
12914
12906
  "icon": "expand_more",
12915
12907
  "order": 1,
12916
- "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.'
12908
+ "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.'
12917
12909
  },
12918
12910
  {
12919
12911
  "id": "modal",
@@ -12921,7 +12913,7 @@ var guideData = [
12921
12913
  "title": "Modal",
12922
12914
  "icon": "open_in_full",
12923
12915
  "order": 2,
12924
- "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.'
12916
+ "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.'
12925
12917
  },
12926
12918
  {
12927
12919
  "id": "button",
@@ -12929,7 +12921,7 @@ var guideData = [
12929
12921
  "title": "Button",
12930
12922
  "icon": "touch_app",
12931
12923
  "order": 3,
12932
- "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 |'
12924
+ "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 |'
12933
12925
  },
12934
12926
  {
12935
12927
  "id": "slide",
@@ -12937,7 +12929,7 @@ var guideData = [
12937
12929
  "title": "Slide",
12938
12930
  "icon": "slideshow",
12939
12931
  "order": 4,
12940
- "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.'
12932
+ "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.'
12941
12933
  },
12942
12934
  {
12943
12935
  "id": "html-blocks",
@@ -12950,7 +12942,7 @@ var guideData = [
12950
12942
  ];
12951
12943
 
12952
12944
  // react/Guide.tsx
12953
- import { jsx as jsx4, jsxs } from "react/jsx-runtime";
12945
+ import { Fragment, jsx as jsx4, jsxs } from "react/jsx-runtime";
12954
12946
  var Guide = ({
12955
12947
  open,
12956
12948
  onClose,
@@ -12960,6 +12952,7 @@ var Guide = ({
12960
12952
  const [query, setQuery] = useState("");
12961
12953
  const [selectedId, setSelectedId] = useState(null);
12962
12954
  const [collapsed, setCollapsed] = useState({});
12955
+ const [navOpen, setNavOpen] = useState(false);
12963
12956
  const contentRef = useRef4(null);
12964
12957
  const groups = useMemo(() => {
12965
12958
  const map = /* @__PURE__ */ new Map();
@@ -12987,6 +12980,7 @@ var Guide = ({
12987
12980
  useEffect4(() => {
12988
12981
  if (!open) return;
12989
12982
  setQuery("");
12983
+ setNavOpen(false);
12990
12984
  if (!selectedId) {
12991
12985
  const initial = guideData.find((e) => e.id === initialDirective) ?? guideData.find((e) => e.id === "introduccion") ?? guideData[0];
12992
12986
  setSelectedId(initial?.id ?? null);
@@ -13019,6 +13013,15 @@ var Guide = ({
13019
13013
  /* @__PURE__ */ jsx4("div", { className: "nr-guide__overlay", onClick: onClose }),
13020
13014
  /* @__PURE__ */ jsxs("div", { className: "nr-guide__panel", children: [
13021
13015
  /* @__PURE__ */ jsxs("header", { className: "nr-guide__head", children: [
13016
+ /* @__PURE__ */ jsx4(
13017
+ "button",
13018
+ {
13019
+ className: "nr-guide__nav-toggle",
13020
+ onClick: () => setNavOpen(true),
13021
+ "aria-label": "Abrir navegaci\xF3n",
13022
+ children: /* @__PURE__ */ jsx4("span", { className: "material-icons-round", children: "menu" })
13023
+ }
13024
+ ),
13022
13025
  /* @__PURE__ */ jsx4("span", { className: "material-icons-round", children: "menu_book" }),
13023
13026
  /* @__PURE__ */ jsx4("h2", { children: "Gu\xEDa de sintaxis" }),
13024
13027
  search && /* @__PURE__ */ jsx4(
@@ -13042,6 +13045,43 @@ var Guide = ({
13042
13045
  )
13043
13046
  ] }),
13044
13047
  /* @__PURE__ */ jsxs("div", { className: "nr-guide__body", children: [
13048
+ navOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
13049
+ /* @__PURE__ */ jsx4("div", { className: "nr-guide__nav-overlay", onClick: () => setNavOpen(false) }),
13050
+ /* @__PURE__ */ jsx4("div", { className: "nr-guide__nav-drawer nr-guide__nav-drawer--open", children: /* @__PURE__ */ jsxs("nav", { children: [
13051
+ filtered.map(([cat, entries]) => /* @__PURE__ */ jsxs("div", { className: "nr-guide__cat", children: [
13052
+ /* @__PURE__ */ jsxs(
13053
+ "button",
13054
+ {
13055
+ className: "nr-guide__cat-head",
13056
+ onClick: () => toggleCategory(cat),
13057
+ children: [
13058
+ /* @__PURE__ */ jsx4("span", { className: "material-icons-round nr-guide__cat-chevron", children: collapsed[cat] ? "chevron_right" : "expand_more" }),
13059
+ cat
13060
+ ]
13061
+ }
13062
+ ),
13063
+ !collapsed[cat] && /* @__PURE__ */ jsx4("ul", { className: "nr-guide__items", children: entries.map((e) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsxs(
13064
+ "button",
13065
+ {
13066
+ className: `nr-guide__item${selectedId === e.id ? " nr-guide__item--active" : ""}`,
13067
+ onClick: () => {
13068
+ setSelectedId(e.id);
13069
+ setNavOpen(false);
13070
+ },
13071
+ children: [
13072
+ e.icon && /* @__PURE__ */ jsx4("span", { className: "material-icons-round nr-guide__item-icon", children: e.icon }),
13073
+ e.title
13074
+ ]
13075
+ }
13076
+ ) }, e.id)) })
13077
+ ] }, cat)),
13078
+ filtered.length === 0 && /* @__PURE__ */ jsxs("div", { className: "nr-guide__empty", children: [
13079
+ "Sin resultados para \xAB",
13080
+ query,
13081
+ "\xBB."
13082
+ ] })
13083
+ ] }) })
13084
+ ] }),
13045
13085
  /* @__PURE__ */ jsxs("nav", { className: "nr-guide__nav", children: [
13046
13086
  filtered.map(([cat, entries]) => /* @__PURE__ */ jsxs("div", { className: "nr-guide__cat", children: [
13047
13087
  /* @__PURE__ */ jsxs(