@xscriptor/xcomponents 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CODE_OF_CONDUCT.md +35 -0
  2. package/CONTRIBUTING.md +64 -0
  3. package/LICENSE +21 -0
  4. package/README.md +127 -0
  5. package/SECURITY.md +25 -0
  6. package/dist/chunk-2OAXRRVQ.mjs +150 -0
  7. package/dist/chunk-2OAXRRVQ.mjs.map +1 -0
  8. package/dist/chunk-42XPBYTL.mjs +186 -0
  9. package/dist/chunk-42XPBYTL.mjs.map +1 -0
  10. package/dist/chunk-5G4P2E76.mjs +66 -0
  11. package/dist/chunk-5G4P2E76.mjs.map +1 -0
  12. package/dist/chunk-FZRTAML3.mjs +1 -0
  13. package/dist/chunk-FZRTAML3.mjs.map +1 -0
  14. package/dist/chunk-NY22GB3E.mjs +311 -0
  15. package/dist/chunk-NY22GB3E.mjs.map +1 -0
  16. package/dist/chunk-QCMWPIG7.mjs +320 -0
  17. package/dist/chunk-QCMWPIG7.mjs.map +1 -0
  18. package/dist/chunk-U27ZVCP7.mjs +247 -0
  19. package/dist/chunk-U27ZVCP7.mjs.map +1 -0
  20. package/dist/components/content/index.css +132 -0
  21. package/dist/components/content/index.css.map +1 -0
  22. package/dist/components/content/index.d.mts +17 -0
  23. package/dist/components/content/index.d.ts +17 -0
  24. package/dist/components/content/index.js +102 -0
  25. package/dist/components/content/index.js.map +1 -0
  26. package/dist/components/content/index.mjs +7 -0
  27. package/dist/components/content/index.mjs.map +1 -0
  28. package/dist/components/forms/index.css +307 -0
  29. package/dist/components/forms/index.css.map +1 -0
  30. package/dist/components/forms/index.d.mts +68 -0
  31. package/dist/components/forms/index.d.ts +68 -0
  32. package/dist/components/forms/index.js +357 -0
  33. package/dist/components/forms/index.js.map +1 -0
  34. package/dist/components/forms/index.mjs +9 -0
  35. package/dist/components/forms/index.mjs.map +1 -0
  36. package/dist/components/gallery/index.css +229 -0
  37. package/dist/components/gallery/index.css.map +1 -0
  38. package/dist/components/gallery/index.d.mts +29 -0
  39. package/dist/components/gallery/index.d.ts +29 -0
  40. package/dist/components/gallery/index.js +187 -0
  41. package/dist/components/gallery/index.js.map +1 -0
  42. package/dist/components/gallery/index.mjs +9 -0
  43. package/dist/components/gallery/index.mjs.map +1 -0
  44. package/dist/components/index.css +1181 -0
  45. package/dist/components/index.css.map +1 -0
  46. package/dist/components/index.d.mts +8 -0
  47. package/dist/components/index.d.ts +8 -0
  48. package/dist/components/index.js +1317 -0
  49. package/dist/components/index.js.map +1 -0
  50. package/dist/components/index.mjs +50 -0
  51. package/dist/components/index.mjs.map +1 -0
  52. package/dist/components/layout/index.css +168 -0
  53. package/dist/components/layout/index.css.map +1 -0
  54. package/dist/components/layout/index.d.mts +55 -0
  55. package/dist/components/layout/index.d.ts +55 -0
  56. package/dist/components/layout/index.js +224 -0
  57. package/dist/components/layout/index.js.map +1 -0
  58. package/dist/components/layout/index.mjs +11 -0
  59. package/dist/components/layout/index.mjs.map +1 -0
  60. package/dist/components/navigation/index.css +229 -0
  61. package/dist/components/navigation/index.css.map +1 -0
  62. package/dist/components/navigation/index.d.mts +76 -0
  63. package/dist/components/navigation/index.d.ts +76 -0
  64. package/dist/components/navigation/index.js +347 -0
  65. package/dist/components/navigation/index.js.map +1 -0
  66. package/dist/components/navigation/index.mjs +7 -0
  67. package/dist/components/navigation/index.mjs.map +1 -0
  68. package/dist/components/social/index.css +116 -0
  69. package/dist/components/social/index.css.map +1 -0
  70. package/dist/components/social/index.d.mts +55 -0
  71. package/dist/components/social/index.d.ts +55 -0
  72. package/dist/components/social/index.js +280 -0
  73. package/dist/components/social/index.js.map +1 -0
  74. package/dist/components/social/index.mjs +21 -0
  75. package/dist/components/social/index.mjs.map +1 -0
  76. package/dist/index.css +1181 -0
  77. package/dist/index.css.map +1 -0
  78. package/dist/index.d.mts +8 -0
  79. package/dist/index.d.ts +8 -0
  80. package/dist/index.js +1317 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/index.mjs +50 -0
  83. package/dist/index.mjs.map +1 -0
  84. package/package.json +86 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/layout/xfooter/XFooter.tsx","../src/components/layout/xfooter/XFooter.module.css","../src/components/layout/xseparator/XSeparator.module.css","../src/components/layout/xseparator/XSeparator.tsx","../src/components/layout/xzigzaglayout/XZigZagLayout.tsx","../src/components/layout/xzigzaglayout/XZigZagLayout.module.css"],"sourcesContent":["import Link from \"next/link\";\nimport { CSSProperties } from \"react\";\nimport styles from \"./XFooter.module.css\";\n\nexport type XFooterLink = {\n label: string;\n href: string;\n};\n\nexport type CopyrightConfig = {\n text?: string;\n showYear?: boolean;\n customYear?: number | string;\n yearFirst?: boolean;\n};\n\nexport type XFooterProps = {\n links: XFooterLink[];\n copyright?: CopyrightConfig; \n layout?: \"horizontal\" | \"vertical\";\n columns?: 1 | 2 | 3 | 4;\n colors?: {\n bg?: string;\n text?: string;\n accent?: string;\n border?: string;\n };\n className?: string;\n};\nexport default function XFooter({\n links,\n copyright,\n layout = \"horizontal\",\n columns = 1,\n colors,\n className = \"\",\n}: XFooterProps) {\n \n const customStyles = {\n \"--xf-bg\": colors?.bg,\n \"--xf-text\": colors?.text,\n \"--xf-accent\": colors?.accent,\n \"--xf-border\": colors?.border,\n \"--xf-cols\": layout === \"horizontal\" ? columns : 1,\n } as CSSProperties;\n\n const currentYear = copyright?.customYear || new Date().getFullYear();\n const copyLabel = copyright?.text || \"Xscriptor\";\n\n return (\n <footer className={`${styles.XFooter} ${className}`} style={customStyles}>\n <div className={styles.container}>\n {/* Los links se organizan en el grid definido por --xf-cols */}\n <nav className={layout === \"vertical\" ? styles.navVertical : styles.nav}>\n {links.map((link, idx) => (\n <Link key={idx} href={link.href} className={styles.link}>\n {link.label}\n </Link>\n ))}\n </nav>\n\n {/* El copyright queda fuera del nav, por lo que hereda el centrado del container */}\n <div className={styles.copyright}>\n © {copyright?.yearFirst \n ? `${currentYear} ${copyLabel}` \n : `${copyLabel} ${currentYear}`}\n </div>\n </div>\n </footer>\n );\n}",".XFooter {\n width: 100%;\n background-color: var(--xf-bg, transparent);\n color: var(--xf-text, #333);\n padding: var(--xf-py, 0.9rem) 0.6rem;\n}\n\n.container {\n max-width: 1280px;\n margin: 0 auto;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 0.6rem; /* Espacio entre los links y el copyright */\n}\n\n/* El nav es el que maneja las columnas de los links */\n.nav {\n display: grid;\n gap: 0.5rem 1.5rem; \n grid-template-columns: repeat(var(--xf-cols, 1), minmax(0, auto));\n justify-content: center;\n text-align: center;\n width: 100%;\n}\n\n.navVertical {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 0.75rem;\n}\n\n.link {\n color: var(--xf-accent, inherit);\n text-decoration: none;\n font-size: 0.9rem;\n transition: opacity 0.2s;\n white-space: nowrap; /* Evita que los links se rompan en dos líneas */\n}\n\n.link:hover {\n opacity: 0.7;\n}\n\n/* El copyright siempre irá al final y centrado por el flex del .container */\n.copyright {\n font-size: 0.75rem;\n opacity: 0.6;\n width: 100%;\n text-align: center;\n}",".separatorContainer {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n /* Variables para la X con fallbacks */\n --x-color: var(--separator-color); \n --x-bg: white;\n}\n\n.iconWrapper {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n background-color: var(--x-bg);\n padding: 0 10px;\n \n /* Aplicamos el color dinámico */\n color: var(--x-color);\n \n font-family: system-ui, sans-serif;\n font-weight: bold;\n font-size: 1.2rem;\n line-height: 1;\n user-select: none;\n}\n\n.separator {\n --separator-color: #e2e8f0;\n --separator-thickness: 1px;\n --separator-margin: 1rem;\n \n border: 0;\n background-color: var(--separator-color);\n}\n\n/* Variantes de Estilo */\n.dashed {\n background-color: transparent !important;\n border-bottom: var(--separator-thickness) dashed var(--separator-color);\n}\n\n.dotted {\n background-color: transparent !important;\n border-bottom: var(--separator-thickness) dotted var(--separator-color);\n}\n\n/* Efecto Desvanecido (Fading) */\n/* Usamos mask-image para que funcione con cualquier color de fondo */\n.faded {\n mask-image: linear-gradient(\n to right, \n transparent, \n black 20%, \n black 80%, \n transparent\n );\n}\n\n/* Ajuste para desvanecido vertical */\n.vertical.faded {\n mask-image: linear-gradient(\n to bottom, \n transparent, \n black 20%, \n black 80%, \n transparent\n );\n}\n\n.horizontal {\n width: 100%;\n height: var(--separator-thickness);\n margin: var(--separator-margin) 0;\n}\n\n.vertical {\n width: var(--separator-thickness);\n height: 100%;\n display: inline-block;\n margin: 0 var(--separator-margin);\n vertical-align: middle;\n}","import React from 'react';\nimport styles from './XSeparator.module.css';\n\nexport interface XSeparatorProps {\n orientation?: 'horizontal' | 'vertical';\n variant?: 'solid' | 'dashed' | 'dotted';\n isFaded?: boolean;\n hasX?: boolean;\n xColor?: string; // Nuevo: Color de la X\n xBg?: string; // Nuevo: Fondo detrás de la X (para el recorte)\n thickness?: string;\n color?: string;\n gap?: string;\n className?: string;\n}\n\nexport default function XSeparator({\n orientation = 'horizontal',\n variant = 'solid',\n isFaded = false,\n hasX = false,\n xColor, \n xBg = 'white',\n thickness = '1px',\n color = '#e2e8f0',\n gap = '1rem',\n className = ''\n}: XSeparatorProps) {\n \n const dynamicStyles = {\n '--separator-color': color,\n '--separator-thickness': thickness,\n '--separator-margin': gap,\n '--x-color': xColor || color, // Si no se define xColor, usa el color de la línea\n '--x-bg': xBg,\n } as React.CSSProperties;\n\n const classes = [\n styles.separator,\n orientation === 'vertical' ? styles.vertical : styles.horizontal,\n variant !== 'solid' && styles[variant],\n isFaded && styles.faded,\n ].filter(Boolean).join(' ');\n\n const line = <hr className={classes} style={dynamicStyles} />;\n\n if (!hasX) return line;\n\n return (\n <div className={`${styles.separatorContainer} ${className}`} style={dynamicStyles}>\n {line}\n <div className={styles.iconWrapper}>\n ✕\n </div>\n </div>\n );\n}","import React, { Children, HTMLAttributes, useEffect, useRef, useState, useCallback } from \"react\";\nimport styles from \"./XZigZagLayout.module.css\";\n\nexport type XZigZagLayoutProps = HTMLAttributes<HTMLDivElement> & {\n children: React.ReactNode;\n startSide?: \"left\" | \"right\";\n gap?: number | string;\n offset?: number | string;\n textAlign?: \"inherit\" | \"side\" | \"left\" | \"right\";\n showLine?: boolean;\n lineColor?: string;\n lineThickness?: number | string;\n};\n\nexport default function XZigZagLayout({\n children,\n className,\n style,\n startSide = \"left\",\n gap,\n offset,\n textAlign = \"inherit\",\n showLine = false,\n lineColor = \"#cccccc\",\n lineThickness = 2,\n ...rest\n}: XZigZagLayoutProps) {\n const items = Children.toArray(children).filter(Boolean);\n const containerRef = useRef<HTMLDivElement>(null);\n const itemsRef = useRef<(HTMLDivElement | null)[]>([]);\n const pathRef = useRef<SVGPathElement>(null);\n\n const [points, setPoints] = useState<{ x: number; y: number }[]>([]);\n const [pathLength, setPathLength] = useState(0);\n const [drawProgress, setDrawProgress] = useState(0);\n\n // Calcula el centro de cada elemento para dibujar los puntos\n const calculatePoints = useCallback(() => {\n if (!containerRef.current) return;\n const containerRect = containerRef.current.getBoundingClientRect();\n\n const newPoints = itemsRef.current.filter(Boolean).map((el) => {\n const rect = el!.getBoundingClientRect();\n return {\n x: rect.left + rect.width / 2 - containerRect.left,\n y: rect.top + rect.height / 2 - containerRect.top,\n };\n });\n\n if (newPoints.length > 0) {\n newPoints.unshift({ x: newPoints[0].x, y: 0 }); // Inicia arriba\n newPoints.push({ x: newPoints[newPoints.length - 1].x, y: containerRect.height }); // Termina abajo\n }\n\n setPoints(newPoints);\n }, []);\n\n useEffect(() => {\n if (!showLine || !containerRef.current) return;\n const observer = new ResizeObserver(() => calculatePoints());\n observer.observe(containerRef.current);\n calculatePoints();\n return () => observer.disconnect();\n }, [showLine, calculatePoints]);\n\n useEffect(() => {\n if (pathRef.current) setPathLength(pathRef.current.getTotalLength());\n }, [points]);\n\n useEffect(() => {\n if (!showLine) return;\n const handleScroll = () => {\n if (!containerRef.current) return;\n const { top, height } = containerRef.current.getBoundingClientRect();\n const windowHeight = window.innerHeight;\n const start = windowHeight / 2;\n const progress = (start - top) / height;\n\n setDrawProgress(Math.min(Math.max(progress, 0), 1));\n };\n\n window.addEventListener(\"scroll\", handleScroll);\n handleScroll();\n return () => window.removeEventListener(\"scroll\", handleScroll);\n }, [showLine]);\n\n const cssVars: Record<string, string> = {};\n if (gap !== undefined) cssVars[\"--x-zigzag-gap\"] = typeof gap === \"number\" ? `${gap}px` : gap;\n if (offset !== undefined) cssVars[\"--x-zigzag-offset\"] = typeof offset === \"number\" ? `${offset}px` : offset;\n\n const mergedStyle: React.CSSProperties = { ...style, ...cssVars };\n\n const pathD = points.length > 0\n ? `M ${points[0].x} ${points[0].y} ` + points.slice(1).map((p) => `L ${p.x} ${p.y}`).join(\" \")\n : \"\";\n\n return (\n <div\n ref={containerRef}\n {...rest}\n className={[styles.layout, className].filter(Boolean).join(\" \")}\n style={mergedStyle}\n >\n {showLine && points.length > 0 && (\n <svg className={styles.svgLine} xmlns=\"http://www.w3.org/2000/svg\">\n <path\n ref={pathRef}\n d={pathD}\n fill=\"none\"\n stroke={lineColor}\n strokeWidth={lineThickness}\n strokeDasharray={pathLength}\n strokeDashoffset={pathLength - pathLength * drawProgress}\n style={{ transition: \"stroke-dashoffset 0.1s ease-out\" }}\n />\n </svg>\n )}\n\n {items.map((child, index) => {\n const isStartLeft = startSide === \"left\";\n const alignLeft = isStartLeft ? index % 2 === 0 : index % 2 !== 0;\n const alignmentClass =\n textAlign === \"side\"\n ? alignLeft ? styles.textLeft : styles.textRight\n : textAlign === \"left\" ? styles.textLeft : textAlign === \"right\" ? styles.textRight : \"\";\n\n return (\n <div key={index} className={`${styles.item} ${alignLeft ? styles.left : styles.right} ${alignmentClass}`}>\n {/* El wrapper interno nos permite medir exactamente dónde queda el contenido */}\n <div ref={(el) => { itemsRef.current[index] = el; }} className={styles.contentWrapper}>\n {child}\n </div>\n </div>\n );\n })}\n </div>\n );\n}",".layout {\n width: min(100%, 72rem);\n margin: 0 auto;\n display: flex;\n flex-direction: column;\n gap: var(--x-zigzag-gap, 0.5rem);\n padding-inline: clamp(0.5rem, 2vw, 1rem);\n position: relative; \n}\n\n/* Nuevos estilos para el SVG de la línea */\n.svgLine {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n z-index: 0;\n}\n\n.item {\n width: 100%;\n display: grid;\n grid-template-columns: repeat(12, minmax(0, 1fr));\n}\n\n.item > * {\n width: 100%;\n text-align: inherit;\n}\n\n/* Wrapper transparente que envuelve al child */\n.contentWrapper {\n width: 100%;\n}\n\n.textLeft > * {\n text-align: left;\n}\n\n.textRight > * {\n text-align: right;\n}\n\n.left > * {\n grid-column: 2 / 8;\n}\n\n.right > * {\n grid-column: 6 / 12;\n}\n\n@media (max-width: 768px) {\n /* En lugar de ocupar las 12 columnas, los hacemos un poco más angostos para que sus centros se desfasen */\n .left > * {\n grid-column: 1 / 11;\n }\n\n .right > * {\n grid-column: 3 / 13;\n }\n}"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs"]}
@@ -0,0 +1,66 @@
1
+ // src/components/content/xinteractivephrase/XInteractivePhrase.tsx
2
+ import React, { useState } from "react";
3
+
4
+ // src/components/content/xinteractivephrase/XInteractivePhrase.module.css
5
+ var XInteractivePhrase_default = {};
6
+
7
+ // src/components/content/xinteractivephrase/XInteractivePhrase.tsx
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ function XInteractivePhrase({
10
+ words,
11
+ as: Tag = "p",
12
+ className = ""
13
+ }) {
14
+ const [active1, setActive1] = useState(false);
15
+ const [active2, setActive2] = useState(false);
16
+ const handleAction = (type) => {
17
+ if (type === "underline") setActive1(!active1);
18
+ if (type === "button") setActive2(!active2);
19
+ };
20
+ const onKeyDown = (e, type) => {
21
+ if (e.key === "Enter" || e.key === " ") {
22
+ e.preventDefault();
23
+ handleAction(type);
24
+ }
25
+ };
26
+ return /* @__PURE__ */ jsx("div", { className: `${XInteractivePhrase_default.container} ${className}`, children: /* @__PURE__ */ jsx(Tag, { className: XInteractivePhrase_default.title, children: words.map((word, index) => {
27
+ let dynamicClass = "";
28
+ let clickHandler = void 0;
29
+ let keyHandler = void 0;
30
+ if (word.type === "underline") {
31
+ dynamicClass = XInteractivePhrase_default.underlineEffect;
32
+ clickHandler = () => handleAction("underline");
33
+ keyHandler = (e) => onKeyDown(e, "underline");
34
+ } else if (word.type === "button") {
35
+ dynamicClass = XInteractivePhrase_default.buttonEffect;
36
+ clickHandler = () => handleAction("button");
37
+ keyHandler = (e) => onKeyDown(e, "button");
38
+ } else if (word.type === "blur1") {
39
+ dynamicClass = `${XInteractivePhrase_default.blurEffect} ${active1 ? XInteractivePhrase_default.isVisible : XInteractivePhrase_default.isHidden}`;
40
+ } else if (word.type === "blur2") {
41
+ dynamicClass = `${XInteractivePhrase_default.blurEffect} ${active2 ? XInteractivePhrase_default.isVisible : XInteractivePhrase_default.isHidden}`;
42
+ }
43
+ let content = word.text;
44
+ if (word.italic) content = /* @__PURE__ */ jsx("em", { children: content });
45
+ if (word.bold) content = /* @__PURE__ */ jsx("strong", { children: content });
46
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
47
+ /* @__PURE__ */ jsx(
48
+ "span",
49
+ {
50
+ className: dynamicClass,
51
+ onClick: clickHandler,
52
+ onKeyDown: keyHandler,
53
+ role: clickHandler ? "button" : void 0,
54
+ tabIndex: clickHandler ? 0 : void 0,
55
+ children: content
56
+ }
57
+ ),
58
+ word.breakAfter ? /* @__PURE__ */ jsx("span", { className: XInteractivePhrase_default.lineBreak }) : " "
59
+ ] }, index);
60
+ }) }) });
61
+ }
62
+
63
+ export {
64
+ XInteractivePhrase
65
+ };
66
+ //# sourceMappingURL=chunk-5G4P2E76.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/content/xinteractivephrase/XInteractivePhrase.tsx","../src/components/content/xinteractivephrase/XInteractivePhrase.module.css"],"sourcesContent":["\"use client\";\n\nimport React, { useState, KeyboardEvent } from \"react\";\nimport styles from \"./XInteractivePhrase.module.css\";\n\nexport interface WordConfig {\n text: string;\n // Solo comportamientos de interacción\n type: \"normal\" | \"underline\" | \"button\" | \"blur1\" | \"blur2\"; \n breakAfter?: boolean;\n italic?: boolean; // Esto controla el <em>\n bold?: boolean; // Esto controla el <strong>\n}\n\nexport interface XInteractivePhraseProps {\n words: WordConfig[];\n as?: \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"span\";\n className?: string;\n}\n\nexport default function XInteractivePhrase({ \n words, \n as: Tag = \"p\", \n className = \"\" \n}: XInteractivePhraseProps) {\n const [active1, setActive1] = useState<boolean>(false);\n const [active2, setActive2] = useState<boolean>(false);\n\n const handleAction = (type: \"underline\" | \"button\") => {\n if (type === \"underline\") setActive1(!active1);\n if (type === \"button\") setActive2(!active2);\n };\n\n const onKeyDown = (e: KeyboardEvent<HTMLSpanElement>, type: \"underline\" | \"button\") => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n handleAction(type);\n }\n };\n\n return (\n <div className={`${styles.container} ${className}`}>\n <Tag className={styles.title}>\n {words.map((word, index) => {\n let dynamicClass = \"\";\n let clickHandler: (() => void) | undefined = undefined;\n let keyHandler: ((e: KeyboardEvent<HTMLSpanElement>) => void) | undefined = undefined;\n\n // Gestión de lógica por tipo\n if (word.type === \"underline\") {\n dynamicClass = styles.underlineEffect;\n clickHandler = () => handleAction(\"underline\");\n keyHandler = (e) => onKeyDown(e, \"underline\");\n } \n else if (word.type === \"button\") {\n dynamicClass = styles.buttonEffect;\n clickHandler = () => handleAction(\"button\");\n keyHandler = (e) => onKeyDown(e, \"button\");\n } \n else if (word.type === \"blur1\") {\n dynamicClass = `${styles.blurEffect} ${active1 ? styles.isVisible : styles.isHidden}`;\n } \n else if (word.type === \"blur2\") {\n dynamicClass = `${styles.blurEffect} ${active2 ? styles.isVisible : styles.isHidden}`;\n }\n\n // Renderizado del contenido con estilos combinables\n let content: React.ReactNode = word.text;\n if (word.italic) content = <em>{content}</em>;\n if (word.bold) content = <strong>{content}</strong>;\n\n return (\n <React.Fragment key={index}>\n <span\n className={dynamicClass}\n onClick={clickHandler}\n onKeyDown={keyHandler}\n role={clickHandler ? \"button\" : undefined}\n tabIndex={clickHandler ? 0 : undefined}\n >\n {content}\n </span>\n {word.breakAfter ? <span className={styles.lineBreak} /> : \" \"}\n </React.Fragment>\n );\n })}\n </Tag>\n </div>\n );\n}",".container {\n padding: 1rem 0;\n color: var(--text, #000);\n width: 100%;\n}\n\n.title {\n margin: 0;\n padding: 0;\n line-height: 1.3;\n font-weight: inherit;\n text-align: inherit;\n}\n\n/* Escala tipográfica fluida */\nh1.title { font-size: clamp(2rem, 6vw, 2.2rem); font-weight: 800; margin: 0; padding: 0; text-align: inherit; }\nh2.title { font-size: clamp(1.675rem, 4vw, 1.8rem); font-weight: 700; margin: 0; padding: 0; text-align: inherit; }\nh3.title { font-size: clamp(1.4rem, 3vw, 1.95rem); font-weight: 700; margin: 0; padding: 0; text-align: inherit; }\nh4.title { font-size: clamp(1.25rem, 3vw, 2rem); font-weight: 600; margin: 0; padding: 0; text-align: inherit; }\nh5.title { font-size: clamp(1.125rem, 3vw, 1.5rem); font-weight: 600; margin: 0; padding: 0; text-align: inherit; }\nh6.title { font-size: clamp(1rem, 2.5vw, 1.25rem); font-weight: 600; margin: 0; padding: 0; text-align: inherit; }\np.title { font-size: 1rem; font-weight: 400; margin: 0; padding: 0; text-align: inherit; }\n\n/* Estilos para etiquetas de énfasis */\n.title strong {\n font-weight: 900;\n color: var(--accent, #0070f3);\n}\n\n.title em {\n font-style: italic;\n font-family: serif; /* Opcional: da un toque elegante a las itálicas */\n}\n\n/* Efectos */\n.underlineEffect {\n position: relative;\n cursor: pointer;\n color: var(--accent, #0070f3);\n display: inline-block;\n transition: color 0.3s ease;\n}\n\n.underlineEffect::after {\n content: \"\";\n position: absolute;\n bottom: -2px;\n left: 0;\n height: 0.15em;\n width: 100%;\n background: repeating-linear-gradient(\n 45deg,\n transparent,\n transparent 2px,\n currentColor 2px,\n currentColor 4px\n );\n transition: opacity 0.3s ease;\n}\n\n.underlineEffect:hover::after {\n opacity: 0;\n}\n\n.buttonEffect {\n display: inline-block;\n padding: 0.1em 0.6em;\n border: 2px dashed currentColor;\n border-radius: 9999px;\n cursor: pointer;\n color: var(--accent, #0070f3);\n transition: all 0.3s ease;\n}\n\n.buttonEffect:hover {\n background: currentColor;\n color: var(--bg, #fff);\n}\n\n.blurEffect {\n display: inline-block;\n transition: filter 0.7s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.7s ease;\n}\n\n.isVisible {\n filter: blur(0);\n opacity: 1;\n}\n\n.isHidden {\n filter: blur(10px);\n opacity: 0.3;\n user-select: none;\n}\n\n.lineBreak {\n display: block;\n height: 0.8rem;\n}\n\n.underlineEffect:focus-visible,\n.buttonEffect:focus-visible {\n outline: 2px solid var(--accent, #0070f3);\n outline-offset: 4px;\n}"],"mappings":";AAEA,OAAO,SAAS,gBAA+B;;;ACF/C;;;ADoEqC,cAIzB,YAJyB;AAhDtB,SAAR,mBAAoC;AAAA,EACzC;AAAA,EACA,IAAI,MAAM;AAAA,EACV,YAAY;AACd,GAA4B;AAC1B,QAAM,CAAC,SAAS,UAAU,IAAI,SAAkB,KAAK;AACrD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAkB,KAAK;AAErD,QAAM,eAAe,CAAC,SAAiC;AACrD,QAAI,SAAS,YAAa,YAAW,CAAC,OAAO;AAC7C,QAAI,SAAS,SAAU,YAAW,CAAC,OAAO;AAAA,EAC5C;AAEA,QAAM,YAAY,CAAC,GAAmC,SAAiC;AACrF,QAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,QAAE,eAAe;AACjB,mBAAa,IAAI;AAAA,IACnB;AAAA,EACF;AAEA,SACE,oBAAC,SAAI,WAAW,GAAG,2BAAO,SAAS,IAAI,SAAS,IAC9C,8BAAC,OAAI,WAAW,2BAAO,OACpB,gBAAM,IAAI,CAAC,MAAM,UAAU;AAC1B,QAAI,eAAe;AACnB,QAAI,eAAyC;AAC7C,QAAI,aAAwE;AAG5E,QAAI,KAAK,SAAS,aAAa;AAC7B,qBAAe,2BAAO;AACtB,qBAAe,MAAM,aAAa,WAAW;AAC7C,mBAAa,CAAC,MAAM,UAAU,GAAG,WAAW;AAAA,IAC9C,WACS,KAAK,SAAS,UAAU;AAC/B,qBAAe,2BAAO;AACtB,qBAAe,MAAM,aAAa,QAAQ;AAC1C,mBAAa,CAAC,MAAM,UAAU,GAAG,QAAQ;AAAA,IAC3C,WACS,KAAK,SAAS,SAAS;AAC9B,qBAAe,GAAG,2BAAO,UAAU,IAAI,UAAU,2BAAO,YAAY,2BAAO,QAAQ;AAAA,IACrF,WACS,KAAK,SAAS,SAAS;AAC9B,qBAAe,GAAG,2BAAO,UAAU,IAAI,UAAU,2BAAO,YAAY,2BAAO,QAAQ;AAAA,IACrF;AAGA,QAAI,UAA2B,KAAK;AACpC,QAAI,KAAK,OAAQ,WAAU,oBAAC,QAAI,mBAAQ;AACxC,QAAI,KAAK,KAAM,WAAU,oBAAC,YAAQ,mBAAQ;AAE1C,WACE,qBAAC,MAAM,UAAN,EACC;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,UACX,SAAS;AAAA,UACT,WAAW;AAAA,UACX,MAAM,eAAe,WAAW;AAAA,UAChC,UAAU,eAAe,IAAI;AAAA,UAE5B;AAAA;AAAA,MACH;AAAA,MACC,KAAK,aAAa,oBAAC,UAAK,WAAW,2BAAO,WAAW,IAAK;AAAA,SAVxC,KAWrB;AAAA,EAEJ,CAAC,GACH,GACF;AAEJ;","names":[]}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chunk-FZRTAML3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,311 @@
1
+ // src/components/navigation/xnavbar/XNavbar.tsx
2
+ import { useEffect, useState } from "react";
3
+ import { motion } from "framer-motion";
4
+ import Link from "next/link";
5
+ import { usePathname } from "next/navigation";
6
+
7
+ // src/components/navigation/xnavbar/XNavbar.module.css
8
+ var XNavbar_default = {};
9
+
10
+ // src/components/navigation/xnavbar/XNavbar.tsx
11
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
+ var iconTrayRightVariants = {
13
+ hidden: { opacity: 0, x: -6, pointerEvents: "none" },
14
+ visible: { opacity: 1, x: 0, pointerEvents: "none", transition: { duration: 0.18 } }
15
+ };
16
+ var iconTrayLeftVariants = {
17
+ hidden: { opacity: 0, x: 6, pointerEvents: "none" },
18
+ visible: { opacity: 1, x: 0, pointerEvents: "none", transition: { duration: 0.18 } }
19
+ };
20
+ var topVariants = { closed: { rotate: 0 }, opened: { rotate: 45 } };
21
+ var centerVariants = { closed: { opacity: 1 }, opened: { opacity: 0 } };
22
+ var bottomVariants = { closed: { rotate: 0 }, opened: { rotate: -45 } };
23
+ var listVariants = {
24
+ closed: { x: "100vw" },
25
+ opened: { x: 0, transition: { when: "beforeChildren", staggerChildren: 0.02 } }
26
+ };
27
+ var itemVariants = {
28
+ closed: { x: -10, opacity: 0 },
29
+ opened: { x: 0, opacity: 1 }
30
+ };
31
+ function renderIcon(icon, size, color) {
32
+ if (typeof icon === "function") return icon(size, color);
33
+ return icon;
34
+ }
35
+ function XNavLink({ link, onClick, mobile }) {
36
+ const pathname = usePathname();
37
+ const isActive = link.url === "/" ? pathname === "/" : pathname?.startsWith(link.url);
38
+ const baseClass = mobile ? XNavbar_default.mobileNavLink : XNavbar_default.navLink;
39
+ const activeClass = mobile ? XNavbar_default.mobileNavLinkActive : XNavbar_default.navLinkActive;
40
+ const externalClass = link.external ? XNavbar_default.navLinkExternal : "";
41
+ return /* @__PURE__ */ jsx(
42
+ Link,
43
+ {
44
+ href: link.url,
45
+ onClick,
46
+ target: link.external ? "_blank" : void 0,
47
+ rel: link.external ? "noopener noreferrer" : void 0,
48
+ "aria-label": `Ir a ${link.title}`,
49
+ "aria-current": isActive ? "page" : void 0,
50
+ className: [baseClass, isActive ? activeClass : "", externalClass].filter(Boolean).join(" "),
51
+ children: link.title
52
+ }
53
+ );
54
+ }
55
+ function XNavbar({
56
+ linksLeft = [],
57
+ linksRight = [],
58
+ logo = "X",
59
+ logoAsThemeToggle = true,
60
+ onLogoClick,
61
+ themeIcons,
62
+ defaultTheme = "light",
63
+ storageKey = "theme",
64
+ linkColor,
65
+ linkHoverColor,
66
+ linkActiveColor,
67
+ iconColor,
68
+ iconHoverColor,
69
+ iconSize = 22,
70
+ hamburgerColor,
71
+ hamburgerBarWidth,
72
+ hamburgerBarThickness,
73
+ cssVars,
74
+ labelOpen = "Abrir men\xFA",
75
+ labelClose = "Cerrar men\xFA",
76
+ labelDark = "Oscuro",
77
+ labelLight = "Claro",
78
+ className
79
+ }) {
80
+ const [open, setOpen] = useState(false);
81
+ const [hoverX, setHoverX] = useState(false);
82
+ const [theme, setTheme] = useState(defaultTheme);
83
+ useEffect(() => {
84
+ const saved = typeof window !== "undefined" && localStorage.getItem(storageKey);
85
+ applyTheme(saved ?? defaultTheme);
86
+ }, []);
87
+ function applyTheme(t) {
88
+ setTheme(t);
89
+ const root = document.documentElement;
90
+ if (t === "dark") root.setAttribute("data-theme", "dark");
91
+ else root.removeAttribute("data-theme");
92
+ localStorage.setItem(storageKey, t);
93
+ }
94
+ function toggleTheme() {
95
+ applyTheme(theme === "dark" ? "light" : "dark");
96
+ }
97
+ useEffect(() => {
98
+ document.documentElement.style.overflow = open ? "hidden" : "";
99
+ return () => {
100
+ document.documentElement.style.overflow = "";
101
+ };
102
+ }, [open]);
103
+ useEffect(() => {
104
+ if (!open) return;
105
+ const onKey = (e) => {
106
+ if (e.key === "Escape") setOpen(false);
107
+ };
108
+ window.addEventListener("keydown", onKey);
109
+ return () => window.removeEventListener("keydown", onKey);
110
+ }, [open]);
111
+ const allLinks = [...linksLeft, ...linksRight];
112
+ const headerStyle = {
113
+ ...linkColor && { "--xnav-link-color": linkColor },
114
+ ...linkHoverColor && { "--xnav-link-hover": linkHoverColor },
115
+ ...linkActiveColor && { "--xnav-link-active": linkActiveColor },
116
+ ...iconColor && { "--xnav-icon-color": iconColor },
117
+ ...iconHoverColor && { "--xnav-icon-hover": iconHoverColor },
118
+ ...hamburgerColor && { "--xnav-bar-color": hamburgerColor },
119
+ ...hamburgerBarWidth && { "--xnav-bar-w": hamburgerBarWidth },
120
+ ...hamburgerBarThickness && { "--xnav-bar-h": hamburgerBarThickness },
121
+ ...cssVars
122
+ };
123
+ const handleLogoClick = () => {
124
+ if (logoAsThemeToggle) {
125
+ toggleTheme();
126
+ } else {
127
+ onLogoClick?.();
128
+ }
129
+ };
130
+ const logoAriaLabel = logoAsThemeToggle ? `Cambiar a tema ${theme === "dark" ? "claro" : "oscuro"}` : void 0;
131
+ const logoTitle = logoAsThemeToggle ? theme === "dark" ? "Cambiar a tema claro" : "Cambiar a tema oscuro" : void 0;
132
+ return /* @__PURE__ */ jsxs(
133
+ "header",
134
+ {
135
+ className: [XNavbar_default.header, className].filter(Boolean).join(" "),
136
+ style: headerStyle,
137
+ role: "banner",
138
+ children: [
139
+ /* @__PURE__ */ jsxs("nav", { className: XNavbar_default.desktopNav, "aria-label": "Navegaci\xF3n principal", children: [
140
+ /* @__PURE__ */ jsx("div", { className: XNavbar_default.desktopLinksLeft, children: linksLeft.map((link) => /* @__PURE__ */ jsx(XNavLink, { link }, link.url + link.title)) }),
141
+ /* @__PURE__ */ jsx("div", { className: XNavbar_default.logoSlot, children: /* @__PURE__ */ jsxs(
142
+ "div",
143
+ {
144
+ className: XNavbar_default.logoWrapper,
145
+ onMouseEnter: () => setHoverX(true),
146
+ onMouseLeave: () => setHoverX(false),
147
+ children: [
148
+ /* @__PURE__ */ jsx(
149
+ "button",
150
+ {
151
+ type: "button",
152
+ onClick: handleLogoClick,
153
+ "aria-label": logoAriaLabel,
154
+ title: logoTitle,
155
+ className: XNavbar_default.logoBtn,
156
+ children: logo
157
+ }
158
+ ),
159
+ logoAsThemeToggle && themeIcons && /* @__PURE__ */ jsx(Fragment, { children: theme === "light" ? /* @__PURE__ */ jsx(
160
+ motion.div,
161
+ {
162
+ initial: false,
163
+ animate: hoverX ? "visible" : "hidden",
164
+ variants: iconTrayRightVariants,
165
+ className: `${XNavbar_default.iconTray} ${XNavbar_default.iconTrayRight}`,
166
+ style: {
167
+ color: (hoverX ? iconHoverColor ?? iconColor : iconColor) || void 0
168
+ },
169
+ "aria-hidden": true,
170
+ children: renderIcon(themeIcons.toDark, iconSize, iconColor)
171
+ }
172
+ ) : /* @__PURE__ */ jsx(
173
+ motion.div,
174
+ {
175
+ initial: false,
176
+ animate: hoverX ? "visible" : "hidden",
177
+ variants: iconTrayLeftVariants,
178
+ className: `${XNavbar_default.iconTray} ${XNavbar_default.iconTrayLeft}`,
179
+ style: {
180
+ color: (hoverX ? iconHoverColor ?? iconColor : iconColor) || void 0
181
+ },
182
+ "aria-hidden": true,
183
+ children: renderIcon(themeIcons.toLight, iconSize, iconColor)
184
+ }
185
+ ) })
186
+ ]
187
+ }
188
+ ) }),
189
+ /* @__PURE__ */ jsx("div", { className: XNavbar_default.desktopLinksRight, children: linksRight.map((link) => /* @__PURE__ */ jsx(XNavLink, { link }, link.url + link.title)) })
190
+ ] }),
191
+ /* @__PURE__ */ jsx("div", { className: XNavbar_default.mobileToggle, children: /* @__PURE__ */ jsxs(
192
+ "button",
193
+ {
194
+ "aria-label": open ? labelClose : labelOpen,
195
+ "aria-expanded": open,
196
+ "aria-controls": "xnavbar-mobile-menu",
197
+ onClick: () => setOpen((p) => !p),
198
+ className: XNavbar_default.hamburgerBtn,
199
+ title: open ? labelClose : labelOpen,
200
+ children: [
201
+ /* @__PURE__ */ jsx(
202
+ motion.div,
203
+ {
204
+ variants: topVariants,
205
+ initial: "closed",
206
+ animate: open ? "opened" : "closed",
207
+ className: XNavbar_default.bar,
208
+ style: { originX: "left" },
209
+ "aria-hidden": true
210
+ }
211
+ ),
212
+ /* @__PURE__ */ jsx(
213
+ motion.div,
214
+ {
215
+ variants: centerVariants,
216
+ initial: "closed",
217
+ animate: open ? "opened" : "closed",
218
+ className: XNavbar_default.bar,
219
+ "aria-hidden": true
220
+ }
221
+ ),
222
+ /* @__PURE__ */ jsx(
223
+ motion.div,
224
+ {
225
+ variants: bottomVariants,
226
+ initial: "closed",
227
+ animate: open ? "opened" : "closed",
228
+ className: XNavbar_default.bar,
229
+ style: { originX: "left" },
230
+ "aria-hidden": true
231
+ }
232
+ )
233
+ ]
234
+ }
235
+ ) }),
236
+ open && /* @__PURE__ */ jsxs(
237
+ motion.div,
238
+ {
239
+ id: "xnavbar-mobile-menu",
240
+ variants: listVariants,
241
+ initial: "closed",
242
+ animate: "opened",
243
+ className: XNavbar_default.mobileOverlay,
244
+ role: "dialog",
245
+ "aria-modal": "true",
246
+ "aria-label": "Men\xFA de navegaci\xF3n",
247
+ children: [
248
+ /* @__PURE__ */ jsxs(
249
+ "button",
250
+ {
251
+ "aria-label": labelClose,
252
+ onClick: () => setOpen(false),
253
+ className: XNavbar_default.mobileCloseBtn,
254
+ style: { WebkitTapHighlightColor: "transparent" },
255
+ children: [
256
+ /* @__PURE__ */ jsx(
257
+ "span",
258
+ {
259
+ "aria-hidden": true,
260
+ className: XNavbar_default.closeBar,
261
+ style: { transform: "rotate(45deg)" }
262
+ }
263
+ ),
264
+ /* @__PURE__ */ jsx(
265
+ "span",
266
+ {
267
+ "aria-hidden": true,
268
+ className: XNavbar_default.closeBar,
269
+ style: { transform: "rotate(-45deg)" }
270
+ }
271
+ ),
272
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: labelClose })
273
+ ]
274
+ }
275
+ ),
276
+ /* @__PURE__ */ jsx(motion.div, { variants: itemVariants, className: XNavbar_default.mobileLogo, children: logo }),
277
+ allLinks.map((link) => /* @__PURE__ */ jsx(motion.div, { variants: itemVariants, children: /* @__PURE__ */ jsx(
278
+ XNavLink,
279
+ {
280
+ link,
281
+ onClick: () => setOpen(false),
282
+ mobile: true
283
+ }
284
+ ) }, link.url + link.title)),
285
+ /* @__PURE__ */ jsx(
286
+ motion.button,
287
+ {
288
+ variants: itemVariants,
289
+ onClick: toggleTheme,
290
+ className: XNavbar_default.themeToggleMobile,
291
+ children: themeIcons ? theme === "light" ? /* @__PURE__ */ jsxs(Fragment, { children: [
292
+ renderIcon(themeIcons.toDark, iconSize, iconColor),
293
+ labelDark
294
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
295
+ renderIcon(themeIcons.toLight, iconSize, iconColor),
296
+ labelLight
297
+ ] }) : theme === "light" ? labelDark : labelLight
298
+ }
299
+ )
300
+ ]
301
+ }
302
+ )
303
+ ]
304
+ }
305
+ );
306
+ }
307
+
308
+ export {
309
+ XNavbar
310
+ };
311
+ //# sourceMappingURL=chunk-NY22GB3E.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/navigation/xnavbar/XNavbar.tsx","../src/components/navigation/xnavbar/XNavbar.module.css"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState, ReactNode, CSSProperties } from \"react\";\nimport { motion, Variants } from \"framer-motion\";\nimport Link from \"next/link\";\nimport { usePathname } from \"next/navigation\";\nimport styles from \"./XNavbar.module.css\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Tipos públicos — exportados para que el consumidor pueda tipear sus datos\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type NavLinkItem = {\n /** Ruta destino */\n url: string;\n /** Texto visible */\n title: string;\n /** Si es true abre en pestaña nueva y muestra el indicador ↗ */\n external?: boolean;\n};\n\n/**\n * ReactNode estático O función de render (size, color?) => ReactNode.\n * La función recibe el tamaño y el color vigente para que el ícono\n * pueda adaptarse dinámicamente a los props del navbar.\n */\nexport type IconRenderer = ReactNode | ((size: number, color?: string) => ReactNode);\n\nexport type ThemeToggleIcons = {\n /** Ícono/renderer hacia tema oscuro (ReactNode o función) */\n toDark: IconRenderer;\n /** Ícono/renderer hacia tema claro (ReactNode o función) */\n toLight: IconRenderer;\n};\n\nexport type XNavbarProps = {\n // ── Navegación ──────────────────────────────────────────────────────────\n /** Links que aparecen a la izquierda del logo en desktop */\n linksLeft?: NavLinkItem[];\n /** Links que aparecen a la derecha del logo en desktop */\n linksRight?: NavLinkItem[];\n\n // ── Logo central ────────────────────────────────────────────────────────\n /** Contenido del botón central (texto o JSX). Por defecto: \"X\" */\n logo?: ReactNode;\n /** Si true el logo actúa como toggle de tema (comportamiento original). Default: true */\n logoAsThemeToggle?: boolean;\n /** Callback custom si logoAsThemeToggle es false */\n onLogoClick?: () => void;\n\n // ── Toggle de tema ──────────────────────────────────────────────────────\n /** Íconos para el toggle de tema. Si no se pasa, no se mostrará hint de ícono */\n themeIcons?: ThemeToggleIcons;\n /** Tema inicial. Default: \"light\" */\n defaultTheme?: \"light\" | \"dark\";\n /** Key de localStorage para persistencia del tema. Default: \"theme\" */\n storageKey?: string;\n\n // ── Color de los enlaces de navegación ──────────────────────────────────────\n /** Color base de los enlaces (Inicio, Contacto, etc.). Default: var(--text) */\n linkColor?: string;\n /** Color al hacer hover sobre los enlaces. Default: opacidad reducida del linkColor */\n linkHoverColor?: string;\n /** Color del borde inferior del enlace activo. Default: linkColor */\n linkActiveColor?: string;\n\n // ── Color y tamaño de íconos theme-toggle ────────────────────────────────\n /** Color base de los íconos. Acepta cualquier valor CSS: hex, hsl, \"var(--accent)\", etc.\n * Si no se pasa, los íconos heredan el color del texto (currentColor). */\n iconColor?: string;\n /** Color que toman los íconos al hacer hover sobre el logo. Default: iconColor */\n iconHoverColor?: string;\n /** Tamaño en px que se pasa a IconRenderer cuando es función. Default: 22 */\n iconSize?: number;\n\n // ── Hamburguesa ──────────────────────────────────────────────────────────\n /** Color de las 3 barras (y la X de cierre móvil). Acepta cualquier valor CSS. Default: var(--text) */\n hamburgerColor?: string;\n /** Ancho de las barras. Cualquier unidad CSS. Default: \"2rem\" */\n hamburgerBarWidth?: string;\n /** Grosor (altura) de las barras. Cualquier unidad CSS. Default: \"3px\" */\n hamburgerBarThickness?: string;\n\n // ── Variables CSS personalizadas ─────────────────────────────────────────\n /** Inyecta variables CSS extra directamente en el style del <header>.\n * Útil para pasar tokens del tema: { '--xnav-icon-color': 'var(--accent)' } */\n cssVars?: Record<string, string>;\n\n // ── Labels de accesibilidad ─────────────────────────────────────────────\n /** aria-label del botón hamburguesa cuando está cerrado. Default: \"Abrir menú\" */\n labelOpen?: string;\n /** aria-label del botón hamburguesa cuando está abierto. Default: \"Cerrar menú\" */\n labelClose?: string;\n /** Texto del botón de tema oscuro en menú móvil. Default: \"Oscuro\" */\n labelDark?: string;\n /** Texto del botón de tema claro en menú móvil. Default: \"Claro\" */\n labelLight?: string;\n\n // ── Estilos adicionales ─────────────────────────────────────────────────\n /** className extra que se añade al <header> */\n className?: string;\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Variantes de animación (idénticas al navbar original)\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst iconTrayRightVariants: Variants = {\n hidden: { opacity: 0, x: -6, pointerEvents: \"none\" },\n visible: { opacity: 1, x: 0, pointerEvents: \"none\", transition: { duration: 0.18 } },\n};\nconst iconTrayLeftVariants: Variants = {\n hidden: { opacity: 0, x: 6, pointerEvents: \"none\" },\n visible: { opacity: 1, x: 0, pointerEvents: \"none\", transition: { duration: 0.18 } },\n};\n\nconst topVariants: Variants = { closed: { rotate: 0 }, opened: { rotate: 45 } };\nconst centerVariants: Variants = { closed: { opacity: 1 }, opened: { opacity: 0 } };\nconst bottomVariants: Variants = { closed: { rotate: 0 }, opened: { rotate: -45 } };\n\nconst listVariants: Variants = {\n closed: { x: \"100vw\" },\n opened: { x: 0, transition: { when: \"beforeChildren\", staggerChildren: 0.02 } },\n};\nconst itemVariants: Variants = {\n closed: { x: -10, opacity: 0 },\n opened: { x: 0, opacity: 1 },\n};\n\n/** Renderiza un IconRenderer: si es función la llama con (size, color?), si es ReactNode lo devuelve tal cual. */\nfunction renderIcon(\n icon: IconRenderer,\n size: number,\n color?: string\n): ReactNode {\n if (typeof icon === \"function\") return icon(size, color);\n return icon;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Sub-componente NavLink (interno)\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype NavLinkProps = {\n link: NavLinkItem;\n onClick?: () => void;\n mobile?: boolean;\n};\n\nfunction XNavLink({ link, onClick, mobile }: NavLinkProps) {\n const pathname = usePathname();\n const isActive =\n link.url === \"/\" ? pathname === \"/\" : pathname?.startsWith(link.url);\n\n const baseClass = mobile ? styles.mobileNavLink : styles.navLink;\n const activeClass = mobile ? styles.mobileNavLinkActive : styles.navLinkActive;\n const externalClass = link.external ? styles.navLinkExternal : \"\";\n\n return (\n <Link\n href={link.url}\n onClick={onClick}\n target={link.external ? \"_blank\" : undefined}\n rel={link.external ? \"noopener noreferrer\" : undefined}\n aria-label={`Ir a ${link.title}`}\n aria-current={isActive ? \"page\" : undefined}\n className={[baseClass, isActive ? activeClass : \"\", externalClass]\n .filter(Boolean)\n .join(\" \")}\n >\n {link.title}\n </Link>\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Componente principal XNavbar\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport default function XNavbar({\n linksLeft = [],\n linksRight = [],\n logo = \"X\",\n logoAsThemeToggle = true,\n onLogoClick,\n themeIcons,\n defaultTheme = \"light\",\n storageKey = \"theme\",\n linkColor,\n linkHoverColor,\n linkActiveColor,\n iconColor,\n iconHoverColor,\n iconSize = 22,\n hamburgerColor,\n hamburgerBarWidth,\n hamburgerBarThickness,\n cssVars,\n labelOpen = \"Abrir menú\",\n labelClose = \"Cerrar menú\",\n labelDark = \"Oscuro\",\n labelLight = \"Claro\",\n className,\n}: XNavbarProps) {\n const [open, setOpen] = useState(false);\n const [hoverX, setHoverX] = useState(false);\n const [theme, setTheme] = useState<\"light\" | \"dark\">(defaultTheme);\n\n // Persistir tema\n useEffect(() => {\n const saved = (typeof window !== \"undefined\" &&\n localStorage.getItem(storageKey)) as \"light\" | \"dark\" | null;\n applyTheme(saved ?? defaultTheme);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n function applyTheme(t: \"light\" | \"dark\") {\n setTheme(t);\n const root = document.documentElement;\n if (t === \"dark\") root.setAttribute(\"data-theme\", \"dark\");\n else root.removeAttribute(\"data-theme\");\n localStorage.setItem(storageKey, t);\n }\n\n function toggleTheme() {\n applyTheme(theme === \"dark\" ? \"light\" : \"dark\");\n }\n\n // Bloquear scroll — usamos documentElement para no tocar document.body\n useEffect(() => {\n document.documentElement.style.overflow = open ? \"hidden\" : \"\";\n return () => { document.documentElement.style.overflow = \"\"; };\n }, [open]);\n\n // Cerrar con ESC\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [open]);\n\n const allLinks = [...linksLeft, ...linksRight];\n\n // ── CSS vars inyectadas en el <header> via inline style ─────────────────\n // Usando \"--xnav-*\" como namespace para no colisionar con vars globales.\n const headerStyle = {\n ...(linkColor && { \"--xnav-link-color\" : linkColor }),\n ...(linkHoverColor && { \"--xnav-link-hover\" : linkHoverColor }),\n ...(linkActiveColor && { \"--xnav-link-active\" : linkActiveColor }),\n ...(iconColor && { \"--xnav-icon-color\" : iconColor }),\n ...(iconHoverColor && { \"--xnav-icon-hover\" : iconHoverColor }),\n ...(hamburgerColor && { \"--xnav-bar-color\" : hamburgerColor }),\n ...(hamburgerBarWidth && { \"--xnav-bar-w\" : hamburgerBarWidth }),\n ...(hamburgerBarThickness && { \"--xnav-bar-h\" : hamburgerBarThickness }),\n ...cssVars,\n } as CSSProperties;\n\n // Acción del logo central\n const handleLogoClick = () => {\n if (logoAsThemeToggle) {\n toggleTheme();\n } else {\n onLogoClick?.();\n }\n };\n\n const logoAriaLabel = logoAsThemeToggle\n ? `Cambiar a tema ${theme === \"dark\" ? \"claro\" : \"oscuro\"}`\n : undefined;\n\n const logoTitle = logoAsThemeToggle\n ? theme === \"dark\"\n ? \"Cambiar a tema claro\"\n : \"Cambiar a tema oscuro\"\n : undefined;\n\n return (\n <header\n className={[styles.header, className].filter(Boolean).join(\" \")}\n style={headerStyle}\n role=\"banner\"\n >\n {/* ── Desktop ── */}\n <nav className={styles.desktopNav} aria-label=\"Navegación principal\">\n <div className={styles.desktopLinksLeft}>\n {linksLeft.map((link) => (\n <XNavLink key={link.url + link.title} link={link} />\n ))}\n </div>\n\n {/* Botón logo central */}\n <div className={styles.logoSlot}>\n <div\n className={styles.logoWrapper}\n onMouseEnter={() => setHoverX(true)}\n onMouseLeave={() => setHoverX(false)}\n >\n <button\n type=\"button\"\n onClick={handleLogoClick}\n aria-label={logoAriaLabel}\n title={logoTitle}\n className={styles.logoBtn}\n >\n {logo}\n </button>\n\n {/* Ícono hint al hover (solo si se pasan themeIcons y el logo es toggle de tema) */}\n {logoAsThemeToggle && themeIcons && (\n <>\n {theme === \"light\" ? (\n <motion.div\n initial={false}\n animate={hoverX ? \"visible\" : \"hidden\"}\n variants={iconTrayRightVariants}\n className={`${styles.iconTray} ${styles.iconTrayRight}`}\n style={{\n color:\n (hoverX ? iconHoverColor ?? iconColor : iconColor) ||\n undefined,\n }}\n aria-hidden\n >\n {renderIcon(themeIcons.toDark, iconSize, iconColor)}\n </motion.div>\n ) : (\n <motion.div\n initial={false}\n animate={hoverX ? \"visible\" : \"hidden\"}\n variants={iconTrayLeftVariants}\n className={`${styles.iconTray} ${styles.iconTrayLeft}`}\n style={{\n color:\n (hoverX ? iconHoverColor ?? iconColor : iconColor) ||\n undefined,\n }}\n aria-hidden\n >\n {renderIcon(themeIcons.toLight, iconSize, iconColor)}\n </motion.div>\n )}\n </>\n )}\n </div>\n </div>\n\n <div className={styles.desktopLinksRight}>\n {linksRight.map((link) => (\n <XNavLink key={link.url + link.title} link={link} />\n ))}\n </div>\n </nav>\n\n {/* ── Mobile: botón hamburguesa ── */}\n <div className={styles.mobileToggle}>\n <button\n aria-label={open ? labelClose : labelOpen}\n aria-expanded={open}\n aria-controls=\"xnavbar-mobile-menu\"\n onClick={() => setOpen((p) => !p)}\n className={styles.hamburgerBtn}\n title={open ? labelClose : labelOpen}\n >\n <motion.div\n variants={topVariants}\n initial=\"closed\"\n animate={open ? \"opened\" : \"closed\"}\n className={styles.bar}\n style={{ originX: \"left\" }}\n aria-hidden\n />\n <motion.div\n variants={centerVariants}\n initial=\"closed\"\n animate={open ? \"opened\" : \"closed\"}\n className={styles.bar}\n aria-hidden\n />\n <motion.div\n variants={bottomVariants}\n initial=\"closed\"\n animate={open ? \"opened\" : \"closed\"}\n className={styles.bar}\n style={{ originX: \"left\" }}\n aria-hidden\n />\n </button>\n </div>\n\n {/* ── Mobile overlay — position:fixed, sin portal, sin document.body ── */}\n {open && (\n <motion.div\n id=\"xnavbar-mobile-menu\"\n variants={listVariants}\n initial=\"closed\"\n animate=\"opened\"\n className={styles.mobileOverlay}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Menú de navegación\"\n >\n {/* Botón cerrar */}\n <button\n aria-label={labelClose}\n onClick={() => setOpen(false)}\n className={styles.mobileCloseBtn}\n style={{ WebkitTapHighlightColor: \"transparent\" }}\n >\n <span\n aria-hidden\n className={styles.closeBar}\n style={{ transform: \"rotate(45deg)\" }}\n />\n <span\n aria-hidden\n className={styles.closeBar}\n style={{ transform: \"rotate(-45deg)\" }}\n />\n <span className=\"sr-only\">{labelClose}</span>\n </button>\n\n {/* Logo decorativo */}\n <motion.div variants={itemVariants} className={styles.mobileLogo}>\n {logo}\n </motion.div>\n\n {/* Links */}\n {allLinks.map((link) => (\n <motion.div key={link.url + link.title} variants={itemVariants}>\n <XNavLink\n link={link}\n onClick={() => setOpen(false)}\n mobile\n />\n </motion.div>\n ))}\n\n {/* Toggle de tema */}\n <motion.button\n variants={itemVariants}\n onClick={toggleTheme}\n className={styles.themeToggleMobile}\n >\n {themeIcons ? (\n theme === \"light\" ? (\n <>\n {renderIcon(themeIcons.toDark, iconSize, iconColor)}\n {labelDark}\n </>\n ) : (\n <>\n {renderIcon(themeIcons.toLight, iconSize, iconColor)}\n {labelLight}\n </>\n )\n ) : theme === \"light\" ? (\n labelDark\n ) : (\n labelLight\n )}\n </motion.button>\n </motion.div>\n )}\n </header>\n );\n}\n","/* ────────────────────────────────────────────\n XNavbar.module.css\n Navbar completamente libre de Tailwind.\n Usa variables CSS del tema global (--bg, --text, --border, --accent).\n ──────────────────────────────────────────── */\n\n/* ── Header / contenedor raíz ── */\n.header {\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 2rem 3rem;\n background: var(--bg);\n color: var(--text);\n font-size: 1.25rem;\n z-index: 60;\n}\n\n@media (min-width: 640px) { .header { padding-inline: 2rem; } }\n@media (min-width: 768px) { .header { padding-inline: 3rem; } }\n@media (min-width: 1024px) { .header { padding-inline: 5rem; } }\n@media (min-width: 1280px) { .header { padding-inline: 12rem; } }\n\n/* ── Desktop nav ── */\n.desktopNav {\n display: none;\n align-items: center;\n width: 100%;\n grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);\n padding-bottom: 0.5rem;\n}\n\n@media (min-width: 768px) {\n .desktopNav {\n display: grid;\n }\n}\n\n.desktopLinksLeft,\n.desktopLinksRight {\n display: flex;\n align-items: center;\n gap: 1.5rem;\n min-width: 0;\n}\n\n.desktopLinksLeft {\n justify-content: flex-end;\n}\n\n.desktopLinksRight {\n justify-content: flex-start;\n}\n\n.logoSlot {\n display: flex;\n justify-content: center;\n align-items: center;\n}\n\n/* ── Logo / botón central (desktop) ── */\n.logoWrapper {\n position: relative;\n margin-inline: 1rem;\n display: flex;\n align-items: center;\n}\n\n.logoBtn {\n font-weight: 700;\n font-size: 1.5rem;\n line-height: 1;\n user-select: none;\n background: none;\n border: none;\n cursor: pointer;\n color: var(--text);\n padding: 0;\n}\n\n/* ── Tray de íconos hover (sun/moon) ── */\n.iconTray {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n pointer-events: none;\n}\n\n.iconTrayRight {\n left: 100%;\n padding-left: 0.75rem;\n}\n\n.iconTrayLeft {\n right: 100%;\n padding-right: 0.75rem;\n}\n\n/* ── NavLink (desktop) ── */\n.navLink {\n padding: 0.25rem;\n border-bottom: 2px solid transparent;\n transition: border-color 0.2s, opacity 0.2s, color 0.2s;\n color: var(--xnav-link-color, var(--text));\n text-decoration: none;\n white-space: nowrap;\n}\n\n.navLink:hover {\n color: var(--xnav-link-hover, var(--xnav-link-color, var(--text)));\n opacity: var(--xnav-link-hover-opacity, 0.7);\n}\n\n.navLinkActive {\n border-bottom-color: var(--xnav-link-active, var(--xnav-link-color, var(--text)));\n font-weight: 600;\n}\n\n/* External link indicator */\n.navLinkExternal::after {\n content: \" ↗\";\n font-size: 0.7em;\n opacity: 0.6;\n}\n\n/* ── Mobile hamburger ── */\n.mobileToggle {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n@media (min-width: 768px) {\n .mobileToggle {\n display: none;\n }\n}\n\n.hamburgerBtn {\n width: 2.5rem;\n height: 2rem;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: space-between;\n background: none;\n border: none;\n cursor: pointer;\n position: relative;\n z-index: 80;\n padding: 0;\n}\n\n.bar {\n width: var(--xnav-bar-w, 2rem);\n height: var(--xnav-bar-h, 3px);\n background: var(--xnav-bar-color, var(--text));\n border-radius: 2px;\n}\n\n/* ── Mobile overlay (portal) ── */\n.mobileOverlay {\n position: fixed;\n inset: 0;\n width: 100vw;\n height: 100svh;\n background: var(--bg);\n color: var(--text);\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 2rem;\n font-size: 2.5rem;\n z-index: 9999;\n}\n\n.mobileCloseBtn {\n position: fixed;\n top: 2rem;\n left: 1rem;\n width: 2.5rem;\n height: 2rem;\n display: flex;\n align-items: center;\n justify-content: center;\n background: none;\n border: none;\n cursor: pointer;\n /* hereda el mismo color que las barras del hamburguesa */\n color: var(--xnav-bar-color, var(--text));\n z-index: 10000;\n -webkit-tap-highlight-color: transparent;\n}\n\n.closeBar {\n display: block;\n position: absolute;\n width: 2rem;\n height: 3px;\n border-radius: 2px;\n background: currentColor;\n}\n\n.mobileLogo {\n font-weight: 700;\n font-size: 3rem;\n user-select: none;\n pointer-events: none;\n}\n\n.mobileNavLink {\n font-size: 2.5rem;\n color: var(--xnav-link-color, var(--text));\n text-decoration: none;\n border-bottom: 2px solid transparent;\n transition: border-color 0.2s, color 0.2s, opacity 0.2s;\n padding: 0.25rem;\n}\n\n.mobileNavLink:hover {\n color: var(--xnav-link-hover, var(--xnav-link-color, var(--text)));\n opacity: var(--xnav-link-hover-opacity, 0.7);\n}\n\n.mobileNavLinkActive {\n font-weight: 600;\n border-bottom-color: var(--xnav-link-active, var(--xnav-link-color, var(--text)));\n}\n\n/* ── Botón tema (móvil) ── */\n.themeToggleMobile {\n margin-top: 1rem;\n font-size: 1rem;\n padding: 0.5rem 1rem;\n border: 1px solid var(--border);\n border-radius: 0.375rem;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n background: none;\n color: var(--text);\n cursor: pointer;\n transition: opacity 0.2s;\n}\n\n.themeToggleMobile:hover {\n opacity: 0.7;\n}\n"],"mappings":"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","names":[]}