PageSourceSearch

https://kobdev.pages.dev/script.js?v=20260805a

js kobdev.pages.dev collected 2026-10-03 05:20:34 UTC 59,549 bytes, 1,080 lines download raw bytes

1/* ========================================
2   kob.dev — Consultoría IA — Client-side JS
3   ======================================== */
4
5document.addEventListener('DOMContentLoaded', () => {
6  initI18n();
7  initScrollAnimations();
8  initContactForm();
9  initSmoothScroll();
10  initServicePreselect();
11  initAudioPlayers();
12  initTemaPreselect();
13  initAudiolibroForm();
14  initPrimerAgenteForm();
15});
16
17/* ----------------------------------------
18   Preselección del servicio vía ?s=<valor>
19   (p. ej. la página de Oferta enlaza a /?s=audit#contacto)
20   ---------------------------------------- */
21function initServicePreselect() {
22  const params = new URLSearchParams(window.location.search);
23  const s = params.get('s');
24  if (!s) return;
25  const select = document.getElementById('tipo_servicio');
26  if (select && [...select.options].some((o) => o.value === s)) {
27    select.value = s;
28  }
29}
30
31/* ----------------------------------------
32   Internationalization (ES / EN)
33   ---------------------------------------- */
34const I18N = {
35  es: {
36    'meta.title': 'kob.dev — Consultoría en IA Generativa para Empresas',
37    'meta.desc': 'Consultoría en inteligencia artificial generativa: cómo aplicarla en tu empresa y cómo construir agentes de IA a medida. Diagnóstico y acompañamiento experto.',
38
39    'hero.badge': '🇨🇱 Consultoría en IA generativa para empresas',
40    'hero.title': 'Aprovecha la IA generativa<br><span class="gradient-text">en tu negocio, de verdad.</span>',
41    'hero.subtitle': 'Te ayudamos a entender <strong>cómo aplicar la inteligencia artificial generativa en tu empresa</strong> y a <strong>construir agentes de IA a medida</strong> que resuelven problemas concretos. Sin humo, con foco en resultados.',
42    'hero.cta1': 'Quiero una consultoría',
43    'hero.cta2': 'Ver la oferta',
44    'hero.stat1num': '+20',
45    'hero.stat1label': 'Años en tecnología',
46    'hero.stat3num': 'Agentes',
47    'hero.stat3label': 'de IA a medida',
48
49    'svc.tag': 'Servicios',
50    'svc.h2': 'Tres formas de trabajar juntos',
51    'svc.desc': 'Ya sea que estés empezando a explorar la IA generativa o que quieras construir algo concreto, tenemos una consultoría para ti.',
52    'svc.c1title': 'IA generativa aplicada a tu empresa',
53    'svc.c1p': 'Analizamos tus procesos y detectamos <strong>dónde la IA generativa genera valor real</strong>: qué automatizar, qué herramientas usar, cómo integrarla a tu operación y cómo capacitar a tu equipo. Sales con un plan claro y priorizado.',
54    'svc.c1r': '→ Estrategia de adopción de IA con foco en ROI',
55    'svc.c2title': 'Creación de agentes con IA generativa',
56    'svc.c2p': 'Diseñamos y construimos <strong>agentes de IA a medida</strong> para tu negocio. Tenemos experiencia creando asistentes expertos que resuelven consultas, agentes de pentesting, automatizaciones inteligentes e integraciones con tus sistemas — desde la arquitectura hasta la puesta en marcha.',
57    'svc.c2r': '→ Agentes funcionando, integrados a tus herramientas',
58    'svc.c3badge': 'Para startups',
59    'svc.c3title': 'Soluciones end-to-end para startups',
60    'svc.c3p': 'Si tienes una startup, construimos tu solución con IA <strong>de punta a punta</strong>, en plazos cortos y con un precio acorde a tu etapa. Para validar rápido y avanzar sin comprometer tu runway.',
61    'svc.c3r': '→ De la idea a producto en semanas, a precio de startup',
62    'svc.offercta': '→ Ver la oferta: Automatización con Agentes de IA',
63
64    'ag.tag': 'Nuestros agentes',
65    'ag.h2': 'Agentes de IA, a la medida de tu empresa',
66    'ag.desc': 'No son productos cerrados: cada agente se diseña e implementa a la medida de cada cliente, integrado a tus sistemas. Cotiza el que se ajuste a tu necesidad — o cuéntanos uno nuevo.',
67    'ag.a1title': 'Agente de Ley de Protección de Datos (Chile)',
68    'ag.a1p': 'Se conecta a tus sistemas, audita el cumplimiento de la Ley de Protección de Datos de Chile y genera reportes de auditoría bajo demanda. Ideal para llegar preparado a la entrada en vigencia de la ley.',
69    'ag.a2title': 'Agente Experto',
70    'ag.a2p': 'Consume grandes volúmenes de información sobre un dominio o nicho —interno o externo— y se vuelve experto en él. El objetivo: resolver <strong>más del 90% de las consultas</strong>, liberando el tiempo de tus expertos reales para lo que de verdad lo requiere.',
71    'ag.cta': 'Cotizar',
72    'ag.pymecta': '→ Quiero un agente para mi PYME, desde $49.000 + IVA',
73
74    'how.tag': 'Cómo Funciona',
75    'how.h2': 'De la conversación a resultados concretos',
76    'how.desc': 'Un proceso simple y directo, pensado para que tomes decisiones informadas sobre IA.',
77    'how.c1title': 'Conversación inicial',
78    'how.c1p': 'Nos cuentas tu contexto, tus objetivos y qué te gustaría lograr con IA. <strong>Sin costo y sin compromiso.</strong>',
79    'how.c1r': '→ Vemos si podemos ayudarte',
80    'how.c2title': 'Diagnóstico y propuesta',
81    'how.c2p': 'Analizamos tu caso y te presentamos un <strong>plan concreto</strong>: oportunidades, alcance, tiempos y forma de trabajo.',
82    'how.c2r': '→ Un plan claro y accionable',
83    'how.c3title': 'Consultoría o construcción',
84    'how.c3p': 'Trabajamos juntos: asesoría estratégica, capacitación a tu equipo o <strong>construcción del agente de IA</strong>, según lo que necesites.',
85    'how.c3r': '→ Avanzas con acompañamiento experto',
86    'how.c4title': 'Autonomía',
87    'how.c4p': 'Te dejamos con el conocimiento, la documentación y las herramientas para que <strong>sigas por tu cuenta</strong> si así lo prefieres.',
88    'how.c4r': '→ Tu equipo empoderado con IA',
89
90    'nos.tag': 'Nosotros',
91    'nos.h2': 'Trayectoria real en tecnología',
92    'nos.p': 'Detrás de kob.dev hay personas con <strong>más de 20 años de experiencia en tecnología</strong>, que combinan seguridad, años creando agentes de IA generativa y una trayectoria construyendo áreas de seguridad, DevOps y software desde cero.',
93    'nos.c1t': '+20 años en tecnología',
94    'nos.c1p': 'Dos décadas diseñando, construyendo y operando sistemas en producción.',
95    'nos.c3t': '3 años en IA generativa',
96    'nos.c3p': 'Diseñando y desplegando agentes de IA generativa para casos reales.',
97    'nos.c4t': 'Seguridad desde cero',
98    'nos.c4p': 'Certificación OSCP y experiencia montando y liderando áreas de seguridad completas dentro de empresas.',
99    'nos.c5t': 'DevOps e infraestructura',
100    'nos.c5p': 'Automatización, nube, CI/CD y operación confiable a escala.',
101    'nos.c6t': 'Desarrollo de software',
102    'nos.c6p': 'Ingeniería sólida con enfoque Spec-Driven Development.',
103
104    'pil.tag': 'Nuestros pilares',
105    'pil.h2': 'Calidad, seguridad y responsabilidad',
106    'pil.desc': 'Tres pilares que sostienen cada agente que construimos.',
107    'pil.c1t': 'Calidad',
108    'pil.c1p': 'La calidad de un agente no se declara: se mide. Evaluamos cada agente con <strong>evals</strong> (baterías de pruebas automatizadas) que verifican precisión y consistencia. Iteramos con métricas, no con impresiones.',
109    'pil.c2t': 'Seguridad',
110    'pil.c2p': 'Seguridad en el ADN del equipo, con <strong>certificación OSCP</strong>. Construimos siguiendo el <strong>OWASP Top 10 for LLM</strong>, mitigando riesgos como la inyección de prompts y la fuga de información sensible.',
111    'pil.c3t': 'Responsabilidad',
112    'pil.c3p': 'Construimos tecnología que <strong>amplía las capacidades de las personas</strong>, no que las reemplaza. IA generativa usada de forma responsable, con un impacto positivo para las personas.',
113
114    'why.tag': 'Por qué kob.dev',
115    'why.h2': 'Consultoría práctica, no teoría',
116    'why.p': 'La IA generativa cambia rápido y está llena de ruido. Nuestro trabajo es <strong>traducir todo eso en decisiones y sistemas que funcionan en tu realidad</strong>, con un enfoque directo y sin tecnicismos innecesarios.',
117    'why.f1t': 'Foco en tu caso',
118    'why.f1p': 'Nada de recetas genéricas. Trabajamos sobre tus procesos y tus objetivos reales.',
119    'why.f2t': 'De la estrategia al código',
120    'why.f2p': 'Podemos asesorarte y también construir el agente. No nos quedamos en la teoría.',
121    'why.f3t': 'Seguridad como base',
122    'why.f3p': 'La seguridad de tus datos es una prioridad en cada solución que construimos.',
123    'why.f4t': 'Transferencia de conocimiento',
124    'why.f4p': 'Te dejamos capacitado para que tu equipo pueda seguir avanzando por su cuenta.',
125    'why.s1span': 'Paso 1',
126    'why.s1p': 'Conversación inicial sin costo',
127    'why.s2span': 'Paso 2',
128    'why.s2p': 'Diagnóstico y propuesta concreta',
129    'why.s3span': 'Paso 3',
130    'why.s3p': 'Consultoría o construcción del agente',
131    'why.s4span': 'Paso 4',
132    'why.s4p': 'Tu equipo autónomo con IA',
133
134    'cli.tag': 'Clientes',
135    'cli.h2': 'Empresas que confían en kob.dev',
136    'cli.desc': 'Organizaciones que confían en kob.dev para impulsar su tecnología e IA.',
137
138    // Página Oferta
139    'off.metaTitle': 'Oferta: Automatización con Agentes de IA — kob.dev',
140    'off.metaDesc': 'Detectamos qué de tu operación es automatizable con IA y construimos los agentes que lo hacen: del diagnóstico a agentes funcionando en producción, integrados a tus sistemas.',
141    'off.back': '← Volver al inicio',
142    'off.tag': 'Oferta',
143    'off.h2': 'Automatización con Agentes de IA',
144    'off.lead': 'Detectamos qué parte de tu operación es automatizable con IA y construimos los agentes que lo hacen. Del diagnóstico a agentes funcionando en producción — bien hechos y traspasados.',
145
146    'off.who.t': '¿Para quién es?',
147    'off.who.p': 'Empresas con trabajo manual y repetitivo que sospechan que <strong>parte de su operación se puede automatizar con IA</strong>, pero no saben qué conviene automatizar, con qué prioridad, ni cómo llevarlo a producción de forma confiable.',
148    'off.prob.t': 'El problema que resolvemos',
149    'off.prob.p': 'Hay mucho trabajo automatizable, pero no está claro <strong>qué conviene automatizar ni con qué prioridad</strong>. Comprar herramientas sueltas no resuelve el proceso, y llevar un agente a producción —integrado, confiable y seguro— es otra historia. Falta alguien que haga el diagnóstico y, además, lo construya.',
150    'off.what.t': 'Qué es',
151    'off.what.p': 'Primero un <strong>diagnóstico</strong> que mapea qué de tu operación es automatizable y lo prioriza por impacto. Luego <strong>construimos e implementamos los agentes a medida</strong>, integrados a tus sistemas y funcionando en producción — construidos con seguridad desde el diseño. Del plan a algo que funciona.',
152
153    'off.steps.tag': 'Cómo funciona',
154    'off.steps.h2': 'Dos pasos: primero el diagnóstico, luego los agentes',
155    'off.steps.desc': 'Empiezas por un diagnóstico acotado. Si el plan te hace sentido, construimos e implementamos los agentes.',
156    'off.s1.badge': 'Paso 1',
157    'off.s1.hours': 'Alcance fijo',
158    'off.s1.title': 'Diagnóstico de Automatización',
159    'off.s1.p': 'Revisamos tus procesos y detectamos <strong>qué es automatizable con agentes de IA</strong> y qué te falta. Priorizamos por impacto y esfuerzo, y te entregamos un <strong>plan concreto</strong>: qué automatizar, en qué orden, con qué enfoque y una estimación de los costos de terceros (modelos, infraestructura). Sin obligación de continuar.',
160    'off.s1.r': '→ Un roadmap de automatización claro y priorizado',
161    'off.s2.badge': 'Paso 2',
162    'off.s2.hours': 'Cotizado según proyecto',
163    'off.s2.title': 'Implementación de Agentes',
164    'off.s2.p': 'Diseñamos, construimos e integramos los <strong>agentes a medida</strong> del plan: conectados a tus sistemas, con seguridad desde el diseño y funcionando en producción. Te dejamos la documentación y el traspaso para que tu equipo los opere. El alcance se define en el diagnóstico.',
165    'off.s2.r': '→ Agentes funcionando, integrados a tus herramientas',
166
167    'off.not.t': 'Qué NO incluye (a propósito)',
168    'off.not.1': 'Mantención continua ni soporte permanente (se puede contratar aparte).',
169    'off.not.2': 'Los costos de terceros —APIs de modelos, infraestructura, licencias— van por tu cuenta; los estimamos en el diagnóstico.',
170    'off.not.3': 'Automatizar lo que no conviene: te decimos con honestidad qu
170é proceso no vale la pena llevar a un agente.',
171    'off.add.t': 'Add-ons opcionales (no incluidos)',
172    'off.add.1': 'Mantención y evolución de los agentes (mensual).',
173    'off.add.2': 'Nuevos agentes o automatizaciones adicionales.',
174    'off.add.3': 'Capacitación a tu equipo para operar y sacarle provecho a los agentes.',
175    'off.time': 'Plazos: el <strong>diagnóstico se hace en días</strong>. La implementación depende del alcance que definamos en el diagnóstico — con hitos y tiempos acordados antes de empezar.',
176
177    'off.inv.tag': 'Inversión',
178    'off.inv.h2': 'Diagnóstico a precio fijo; implementación cotizada',
179    'off.inv.p': 'El <strong>diagnóstico</strong> es un trabajo acotado con <strong>precio fijo</strong>. La <strong>implementación</strong> se cotiza según el alcance que definamos juntos — un número acordado, no un medidor corriendo. Con precios preferentes para <strong>startups y fundaciones</strong>. El precio se entrega en la conversación inicial.',
180    'off.inv.cta': 'Solicitar diagnóstico',
181    'off.why.t': 'Por qué kob.dev',
182    'off.why.p': 'Más de 20 años en tecnología y 3 años construyendo agentes de IA en producción, con un background de seguridad (OSCP). No solo te decimos qué automatizar: lo construimos, lo integramos y te decimos la verdad sobre qué conviene y qué no.',
183    'off.inv.note': 'Precio del diagnóstico fijo y acordado antes de empezar. Sin sorpresas.',
184
185    'fund.tag': 'Fundación',
186    'fund.h2': 'Fundación kob.dev',
187    'fund.lead': 'Creemos que la IA generativa debería ampliar las capacidades humanas, no debilitarlas.',
188    'fund.p': 'Investigamos cómo usar la inteligencia artificial generativa para <strong>potenciar el pensamiento, el aprendizaje y el criterio</strong> de las personas, y cómo evitar el deterioro cognitivo asociado a su mal uso. No es una preocupación abstracta: la investigación reciente ya documenta una «deuda cognitiva» por el uso excesivo de IA, con efectos en la memoria y el pensamiento crítico.',
189    'fund.ref': 'Referencia: <a href="https://www.media.mit.edu/projects/your-brain-on-chatgpt/" target="_blank" rel="noopener">«Your Brain on ChatGPT», MIT Media Lab (2025)</a>',
190    'fund.cta': 'Conocer la Fundación',
191
192    'ct.tag': 'Hablemos',
193    'ct.h2': 'Cuéntanos tu proyecto',
194    'ct.p': 'Si te interesa aplicar IA generativa en tu empresa o construir un agente a medida, <strong>escríbenos y te respondemos personalmente</strong>. La conversación inicial es sin costo y sin compromiso.',
195
196    'form.nombre': 'Nombre *',
197    'form.email': 'Correo electrónico *',
198    'form.tipo': '¿Qué tipo de consultoría te interesa? *',
199    'form.razon': 'Razón de contacto *',
200    'form.ph.nombre': 'Ej: María González',
201    'form.ph.email': '[email protected]',
202    'form.ph.razon': 'Cuéntame sobre tu empresa, qué problema quieres resolver y qué te gustaría lograr con IA.',
203    'form.opt0': 'Selecciona una opción',
204    'form.opt1': 'Aplicar IA generativa en mi empresa',
205    'form.opt2': 'Creación de agentes con IA generativa',
206    'form.opt3': 'Solución end-to-end para startups',
207    'form.opt4': 'Fundación kob.dev',
208    'form.opt5': 'Automatización con Agentes de IA',
209    'form.opt6': 'Cotizar Agente Protección de Datos',
210    'form.opt7': 'Cotizar Agente Experto',
211    'form.submit': 'Enviar mensaje',
212    'form.loading': 'Enviando...',
213    'form.msg.success': '¡Mensaje enviado! Te responderemos lo antes posible.',
214    'form.msg.error': 'Hubo un error. Intenta de nuevo o escríbeme directamente a [email protected].',
215
216    'foot.tagline': 'Consultoría en IA generativa.',
217    'foot.l1': 'Servicios',
218    'foot.lagentes': 'Nuestros agentes',
219    'foot.l2': 'Nosotros',
220    'foot.l3': 'Cómo Funciona',
221    'foot.l4': 'Nuestros pilares',
222    'foot.loferta': 'Oferta',
223    'foot.lprimer': 'Tu primer agente',
224    'foot.lmineria': 'Proveedores de la minería',
225    'foot.lquimica': 'Industria química',
226    'foot.ltransporte': 'Transporte y logística',
227    'foot.lconstruccion': 'Construcción industrial y minera',
228    'foot.lclientes': 'Clientes',
229    'foot.lfund': 'Fundación',
230    'foot.l5': 'Contacto',
231    'foot.lteam': 'Acerca de',
232    'foot.bottom': '© 2026 kob.dev — Santiago, Chile 🇨🇱',
233
234    // Página Acerca de
235    'team.metaTitle': 'Acerca de — kob.dev',
236    'team.metaDesc': 'Conoce quién está detrás de kob.dev. Nicolás Mery, fundador: líder tecnológico con más de 20 años en tecnología y más de 8 como CTO y Chief Architect en IA, seguridad y fintech.',
237    'team.back': '← Volver al inicio',
238    'team.tag': 'Acerca de',
239    'team.h2': 'Quién está detrás de kob.dev',
240    'team.desc': 'Trayectoria real construyendo tecnología, seguridad e IA generativa.',
241    'team.name': 'Nicolás Mery',
242    'team.role': 'Fundador',
243    'team.bio': 'Líder tecnológico con <strong>más de 20 años en tecnología y más de 8 como CTO y Chief Architect</strong> en fintech, cripto, pagos, ciberseguridad y SaaS. Hoy enfocado en construir <strong>sistemas de agentes de IA generativa</strong> en producción. Certificado OSCP y alumni del programa CTO del MIT.',
244    'team.chip1': '+8 años como CTO',
245    'team.chip2': 'IA generativa',
246    'team.chip3': 'OSCP',
247    'team.chip4': 'MIT CTO Program',
248    'team.cta1': 'Hablemos',
249    'team.cta2': 'Ver servicios',
250
251    // Audios para saber más (/i-know-kung-fu)
252    'foot.laudios': 'Audios para saber más',
253    'kf.metaTitle': 'Audios para saber más — kob.dev',
254    'kf.metaDesc': 'Investigaciones propias del equipo kob.dev en tres profundidades: un resumen de 3 minutos, una versión de 30 minutos y el audiolibro completo.',
255    'kf.back': '← kob.dev',
256    'kf.tag': 'Investigación',
257    'kf.h1': 'Audios para saber más',
258    'kf.subtitle': 'Investigaciones propias del equipo kob.dev, narradas en audio. Cada tema está en <strong>tres profundidades</strong>: escucha los resúmenes aquí mismo y, si el tema te atrapa, pide el audiolibro completo.',
259    'kf.n1.dur': '3 minutos',
260    'kf.n1.h': '¿Me interesa esto?',
261    'kf.n1.p': 'Lo justo para saber de qué va el tema y decidir si merece tu tiempo.',
262    'kf.n2.dur': '30 minutos',
263    'kf.n2.h': 'Quiero entenderlo',
264    'kf.n2.p': 'La versión que deja con criterio formado: los conceptos, los fallos y las decisiones que importan.',
265    'kf.n3.dur': 'Audiolibro',
266    'kf.n3.h': 'Quiero investigar',
267    'kf.n3.p': 'La investigación completa, de una a tres horas: el estado del arte con sus fuentes y sus zonas grises.',
268    'kf.aviso': '<strong>Los audios están narrados en inglés.</strong> Es una consecuencia del pipeline con el que los generamos, no una decisión editorial. Estamos trabajando en versiones en español y otros idiomas. La investigación escrita está en español.',
269    'kf.t1.h': 'Ingeniería de contexto',
270    'kf.t1.meta': '44 notas · ~32.000 palabras · estado del arte a julio de 2026',
271    'kf.t1.desc': 'Cómo se decide qué entra en la ventana de contexto de un modelo y qué se queda fuera. Cubre los modos de fallo —<em>context rot</em>, <em>lost in the middle</em>—, las cuatro estrategias de escribir, seleccionar, comprimir y aislar, la memoria de agentes, el debate entre RAG y contexto largo, el diseño de herramientas y las arquitecturas multi-agente.',
272    'kf.t2.h': 'Loop engineering',
273    'kf.t2.meta': '26 notas · ~17.500 palabras · estado del arte a julio de 2026',
274    'kf.t2.desc': 'El diseño de bucles agénticos: cuándo dejar que un agente itere solo y, sobre todo, cómo saber que debe detenerse. Cubre los cinco niveles de verificación, las condiciones de parada, las arquitecturas <em>maker-checker</em>, los guardrails y presupuestos, y los riesgos —deuda de comprensión, rendición cognitiva, <em>reward hacking</em>.',
275    'kf.t2.nota': 'La investigación advierte que la base empírica del campo todavía es delgada, y marca nota por nota de dónde viene cada afirmación.',
276    'kf.f.corto': 'Resumen de 3 minutos',
277    'kf.f.medio': 'Versión profunda',
278    'kf.f.libro': 'Audiolibro completo',
279    'kf.f.pronto': 'Aún en preparación',
280    'kf.descargar': 'Descargar',
281    'kf.pedir': 'Pedir el audiolibro',
282    'kf.pronto': 'Próximamente',
283    'kf.mas': 'Seguimos investigando. Iremos publicando aquí los temas nuevos a medida que estén listos.',
284    'kf.a11y.play': 'Reproducir',
285    'kf.a11y.pause': 'Pausar',
286    'kf.p.tag': 'Audiolibro',
287    'kf.p.h2': 'Pide el audiolibro completo',
288    'kf.p.p': 'Los audiolibros son archivos grandes y los enviamos a mano, uno por uno. <strong>Déjanos tus datos y te escribimos con el enlace de descarga.</strong> Incluye el MP3 y el M4B con capítulos navegables.',
289    'kf.p.tema': '¿Qué audiolibro quieres? *',
290    'kf.p.ph.tema': 'Ej: Ingeniería de contexto',
291    'kf.p.hint.tema': 'Si el tema que buscas todavía no está publicado, escríbelo igual: nos sirve para saber qué investigar.',
292    'kf.p.motivo': '¿Para qué lo quieres? (opcional)',
293    'kf.p.ph.motivo': 'Nos ayuda a saber qué investigar después.',
294    'kf.p.submit': 'Pedir el audiolibro',
295    'kf.p.msg.success': '¡Listo! Te escribiremos a tu correo con el enlace de descarga.',
296    'kf.p.msg.error': 'No pudimos registrar tu pedido. Vuelve a intentarlo o escríbenos a [email protected].',
297
298    // Landing "Tu primer agente" (/tu-primer-agente). Va sólo en español —el segmento
299    // es la PYME chilena—, pero necesita estas claves porque applyLang() fija el
300    // <title>
300, la meta description y los mensajes del formulario en cada carga.
301    'pa.metaTitle': 'Tu primer agente, desde $49.000 — kob.dev',
302    'pa.metaDesc': 'Construyo la primera versión de un agente que hace esa tarea repetitiva que le quita horas a tu empresa, con tus datos reales. En una semana, y si no sigues el código es tuyo.',
303    'pa.msg.success': '¡Listo! Recibí tu caso. Te respondo en 48 horas, sea que sirva o no.',
304    'pa.msg.error': 'No pudimos enviar tu caso. Vuelve a intentarlo o escríbeme a [email protected].',
305    'pa.msg.herramientas': 'Marca al menos una herramienta en la pregunta 5.',
306  },
307  en: {
308    'meta.title': 'kob.dev — Generative AI Consulting for Companies',
309    'meta.desc': 'Generative AI consulting: how to apply it in your company and how to build custom AI agents. Expert diagnosis and hands-on guidance.',
310
311    'hero.badge': '🇨🇱 Generative AI consulting for companies',
312    'hero.title': 'Put generative AI to work<br><span class="gradient-text">in your business, for real.</span>',
313    'hero.subtitle': 'We help you understand <strong>how to apply generative AI in your company</strong> and <strong>build custom AI agents</strong> that solve real problems. No hype — just results.',
314    'hero.cta1': 'I want a consultation',
315    'hero.cta2': 'See the offer',
316    'hero.stat1num': '+20',
317    'hero.stat1label': 'Years in technology',
318    'hero.stat3num': 'Agents',
319    'hero.stat3label': 'custom-built AI',
320
321    'svc.tag': 'Services',
322    'svc.h2': 'Three ways to work together',
323    'svc.desc': 'Whether you are just starting to explore generative AI or you want to build something concrete, we have a consulting track for you.',
324    'svc.c1title': 'Generative AI applied to your company',
325    'svc.c1p': 'We analyze your processes and pinpoint <strong>where generative AI creates real value</strong>: what to automate, which tools to use, how to integrate it into your operation and how to train your team. You leave with a clear, prioritized plan.',
326    'svc.c1r': '→ AI adoption strategy focused on ROI',
327    'svc.c2title': 'Building agents with generative AI',
328    'svc.c2p': 'We design and build <strong>custom AI agents</strong> for your business. We have experience creating expert assistants that resolve queries, pentesting agents, intelligent automations and integrations with your systems — from architecture to going live.',
329    'svc.c2r': '→ Agents running, integrated with your tools',
330    'svc.c3badge': 'For startups',
331    'svc.c3title': 'End-to-end solutions for startups',
332    'svc.c3p': 'If you\'re a startup, we build your AI solution <strong>end-to-end</strong>, in short timeframes and at a price that fits your stage. To validate fast and move forward without burning your runway.',
333    'svc.c3r': '→ From idea to product in weeks, at startup pricing',
334    'svc.offercta': '→ See the offer: Automation with AI Agents',
335
336    'ag.tag': 'Our agents',
337    'ag.h2': 'AI agents, tailored to your company',
338    'ag.desc': 'They are not off-the-shelf products: each agent is designed and implemented to fit each client, integrated with your systems. Get a quote for the one that fits your need — or tell us about a new one.',
339    'ag.a1title': 'Data Protection Law Agent (Chile)',
340    'ag.a1p': 'It connects to your systems, audits compliance with Chile\'s Data Protection Law and generates audit reports on demand. Ideal to arrive prepared for the law coming into force.',
341    'ag.a2title': 'Expert Agent',
342    'ag.a2p': 'It ingests large volumes of information about a domain or niche —internal or external— and becomes an expert in it. The goal: resolve <strong>more than 90% of queries</strong>, freeing up your real experts\' time for what truly needs it.',
343    'ag.cta': 'Get a quote',
344    'ag.pymecta': '→ I want an agent for my small business, from CLP $49,000 + VAT',
345
346    'how.tag': 'How It Works',
347    'how.h2': 'From conversation to concrete results',
348    'how.desc': 'A simple, direct process designed to help you make informed decisions about AI.',
349    'how.c1title': 'Initial conversation',
350    'how.c1p': 'You tell us your context, your goals and what you would like to achieve with AI. <strong>Free and with no commitment.</strong>',
351    'how.c1r': '→ We find out if we can help you',
352    'how.c2title': 'Diagnosis and proposal',
353    'how.c2p': 'We analyze your case and present a <strong>concrete plan</strong>: opportunities, scope, timelines and how we would work.',
354    'how.c2r': '→ A clear, actionable plan',
355    'how.c3title': 'Consulting or building',
356    'how.c3p': 'We work together: strategic advisory, training for your team or <strong>building the AI agent</strong>, depending on what you need.',
357    'how.c3r': '→ You move forward with expert support',
358    'how.c4title': 'Autonomy',
359    'how.c4p': 'We leave you with the knowledge, documentation and tools so your team can <strong>carry on independently</strong> if you prefer.',
360    'how.c4r': '→ Your team empowered with AI',
361
362    'nos.tag': 'About us',
363    'nos.h2': 'A real track record in technology',
364    'nos.p': 'Behind kob.dev are people with <strong>more than 20 years of experience in technology</strong>, combining security, years building generative AI agents and a track record of building security functions, DevOps and software from scratch.',
365    'nos.c1t': '20+ years in technology',
366    'nos.c1p': 'Two decades designing, building and operating systems in production.',
367    'nos.c3t': '3 years in generative AI',
368    'nos.c3p': 'Designing and deploying generative AI agents for real use cases.',
369    'nos.c4t': 'Security from scratch',
370    'nos.c4p': 'OSCP certification and experience building and leading complete security functions inside companies.',
371    'nos.c5t': 'DevOps & infrastructure',
372    'nos.c5p': 'Automation, cloud, CI/CD and reliable operation at scale.',
373    'nos.c6t': 'Software development',
374    'nos.c6p': 'Solid engineering with a Spec-Driven Development approach.',
375
376    'pil.tag': 'Our pillars',
377    'pil.h2': 'Quality, security and responsibility',
378    'pil.desc': 'Three pillars behind every agent we build.',
379    'pil.c1t': 'Quality',
380    'pil.c1p': 'An agent\'s quality isn\'t claimed: it\'s measured. We evaluate every agent with <strong>evals</strong> (automated test suites) that check accuracy and consistency. We iterate with metrics, not impressions.',
381    'pil.c2t': 'Security',
382    'pil.c2p': 'Security is in the team\'s DNA, with <strong>OSCP certification</strong>. We build following the <strong>OWASP Top 10 for LLM</strong>, mitigating risks like prompt injection and sensitive data leakage.',
383    'pil.c3t': 'Responsibility',
384    'pil.c3p': 'We build technology that <strong>expands people\'s capabilities</strong>, not replaces them. Generative AI used responsibly, with a positive impact on people.',
385
386    'why.tag': 'Why kob.dev',
387    'why.h2': 'Practical consulting, not theory',
388    'why.p': 'Generative AI moves fast and is full of noise. Our job is to <strong>turn all of that into decisions and systems that work in your reality</strong>, with a direct approach and no unnecessary jargon.',
389    'why.f1t': 'Focused on your case',
390    'why.f1p': 'No generic recipes. We work on your processes and your real goals.',
391    'why.f2t': 'From strategy to code',
392    'why.f2p': 'We can advise you and also build the agent. We don\'t stop at theory.',
393    'why.f3t': 'Security by default',
394    'why.f3p': 'The security of your data is a priority in every solution we build.',
395    'why.f4t': 'Knowledge transfer',
396    'why.f4p': 'We leave you trained so your team can keep moving forward on its own.',
397    'why.s1span': 'Step 1',
398    'why.s1p': 'Free initial conversation',
399    'why.s2span': 'Step 2',
400    'why.s2p': 'Diagnosis and concrete proposal',
401    'why.s3span': 'Step 3',
402    'why.s3p': 'Consulting or building the agent',
403    'why.s4span': 'Step 4',
404    'why.s4p': 'Your team autonomous with AI',
405
406    'cli.tag': 'Clients',
407    'cli.h2': 'Companies that trust kob.dev',
408    'cli.desc': 'Organizations that trust kob.dev to drive their technology and AI.',
409
410    // Offer page
411    'off.metaTitle': 'Offer: Automation with AI Agents — kob.dev',
412    'off.metaDesc': 'We find what in your operation can be automated with AI and build the agents that do it: from diagnosis to agents running in production, integrated with your systems.',
413    'off.back': '← Back to home',
414    'off.tag': 'Offer',
415    'off.h2': 'Automation with AI Agents',
416    'off.lead': 'We find what part of your operation can be automated with AI and build the agents that do it. From diagnosis to agents running in production — done well and handed over.',
417
418    'off.who.t': 'Who it\'s for',
419    'off.who.p': 'Companies with manual, repetitive work who suspect <strong>part of their operation can be automated with AI</strong>, but don\'t know what to automate, in what priority, or how to take it to production reliably.',
420    'off.prob.t': 'The problem we solve',
421    'off.prob.p': 'There\'s a lot of automatable work, but it\'s not clear <strong>what\'s worth automating or in what order</strong>. Buying scattered tools doesn\'t fix the process, and getting an agent into production —integrated, reliable and secure— is another story. What\'s missing is someone who does the diagnosis and also builds it.',
422    'off.what.t': 'What it is',
423    'off.what.p': 'First a <strong>diagnosis</strong> that maps what in your operation can be automated and prioritizes it by impact. Then we <strong>build and deploy the custom agents</strong>, integrated with your systems and running in production — built secure by design. From the plan to something that works.',
424
425    'off.steps.tag': 'How it works',
426    'off.steps.h2': 'Two steps: first the diagnosis, then the agents',
427    'off.steps.desc': 'You start with a scoped diagnosis. If the plan makes sense, we build and deploy the agents.',
428    'off.s1.badge': 'Step 1',
429    'off.s1.hours': 'Fixed scope',
430    'off.s1.title': 'Automation Diagnosis',
431    'off.s1.p': 'We review your processes and find <strong>what can be automated with AI agents</strong> and what you\'re missing. We prioritize by impact and effort and hand you a <strong>concrete plan</strong>: what to automate, in what order, with what approach, and an estimate of third-party costs (models, infrastru
431cture). No obligation to continue.',
432    'off.s1.r': '→ A clear, prioritized automation roadmap',
433    'off.s2.badge': 'Step 2',
434    'off.s2.hours': 'Scoped per project',
435    'off.s2.title': 'Agent Implementation',
436    'off.s2.p': 'We design, build and integrate the <strong>custom agents</strong> from the plan: connected to your systems, secure by design and running in production. We leave you documentation and a handover so your team can operate them. The scope is defined in the diagnosis.',
437    'off.s2.r': '→ Agents running, integrated with your tools',
438
439    'off.not.t': 'What it does NOT include (on purpose)',
440    'off.not.1': 'Ongoing maintenance or permanent support (available separately).',
441    'off.not.2': 'Third-party costs —model APIs, infrastructure, licenses— are on you; we estimate them in the diagnosis.',
442    'off.not.3': 'Automating what isn\'t worth it: we honestly tell you which process shouldn\'t become an agent.',
443    'off.add.t': 'Optional add-ons (not bundled)',
444    'off.add.1': 'Maintenance and evolution of the agents (monthly).',
445    'off.add.2': 'New agents or additional automations.',
446    'off.add.3': 'Training for your team to operate and get the most out of the agents.',
447    'off.time': 'Timeline: the <strong>diagnosis takes days</strong>. Implementation depends on the scope we define in the diagnosis — with milestones and timings agreed before we start.',
448
449    'off.inv.tag': 'Investment',
450    'off.inv.h2': 'Fixed-price diagnosis; scoped implementation',
451    'off.inv.p': 'The <strong>diagnosis</strong> is a scoped engagement at a <strong>fixed price</strong>. <strong>Implementation</strong> is quoted based on the scope we define together — one agreed number, not a running meter. With preferential pricing for <strong>startups and foundations</strong>. The price is shared in the initial conversation.',
452    'off.inv.cta': 'Request a diagnosis',
453    'off.why.t': 'Why kob.dev',
454    'off.why.p': '20+ years in tech and 3 years building AI agents in production, with a security background (OSCP). We don\'t just tell you what to automate: we build it, integrate it and tell you the truth about what\'s worth it and what isn\'t.',
455    'off.inv.note': 'Diagnosis price fixed and agreed before we start. No surprises.',
456
457    'fund.tag': 'Foundation',
458    'fund.h2': 'kob.dev Foundation',
459    'fund.lead': 'We believe generative AI should expand human capabilities, not weaken them.',
460    'fund.p': 'We research how to use generative AI to <strong>strengthen people\'s thinking, learning and judgment</strong>, and how to avoid the cognitive decline linked to its misuse. This is not an abstract concern: recent research already documents a “cognitive debt” from overusing AI, with effects on memory and critical thinking.',
461    'fund.ref': 'Reference: <a href="https://www.media.mit.edu/projects/your-brain-on-chatgpt/" target="_blank" rel="noopener">“Your Brain on ChatGPT”, MIT Media Lab (2025)</a>',
462    'fund.cta': 'About the Foundation',
463
464    'ct.tag': 'Let\'s talk',
465    'ct.h2': 'Tell us about your project',
466    'ct.p': 'If you are interested in applying generative AI in your company or building a custom agent, <strong>write to us and we\'ll reply personally</strong>. The initial conversation is free and with no commitment.',
467
468    'form.nombre': 'Name *',
469    'form.email': 'Email *',
470    'form.tipo': 'What kind of consulting are you interested in? *',
471    'form.razon': 'Reason for contact *',
472    'form.ph.nombre': 'E.g. Maria Gonzalez',
473    'form.ph.email': '[email protected]',
474    'form.ph.razon': 'Tell me about your company, what problem you want to solve and what you would like to achieve with AI.',
475    'form.opt0': 'Select an option',
476    'form.opt1': 'Apply generative AI in my company',
477    'form.opt2': 'Build agents with generative AI',
478    'form.opt3': 'End-to-end solution for startups',
479    'form.opt4': 'kob.dev Foundation',
480    'form.opt5': 'Automation with AI Agents',
481    'form.opt6': 'Quote: Data Protection Agent',
482    'form.opt7': 'Quote: Expert Agent',
483    'form.submit': 'Send message',
484    'form.loading': 'Sending...',
485    'form.msg.success': 'Message sent! We\'ll get back to you as soon as possible.',
486    'form.msg.error': 'Something went wrong. Please try again or email me directly at [email protected].',
487
488    'foot.tagline': 'Generative AI consulting.',
489    'foot.l1': 'Services',
490    'foot.lagentes': 'Our agents',
491    'foot.l2': 'About us',
492    'foot.l3': 'How It Works',
493    'foot.l4': 'Our pillars',
494    'foot.loferta': 'Offer',
495    'foot.lprimer': 'Your first agent',
496    'foot.lmineria': 'Mining suppliers',
497    'foot.lquimica': 'Chemical industry',
498    'foot.ltransporte': 'Transport and logistics',
499    'foot.lconstruccion': 'Industrial and mining construction',
500    'foot.lclientes': 'Clients',
501    'foot.lfund': 'Foundation',
502    'foot.l5': 'Contact',
503    'foot.lteam': 'About us',
504    'foot.bottom': '© 2026 kob.dev — Santiago, Chile 🇨🇱',
505
506    // About us page
507    'team.metaTitle': 'About us — kob.dev',
508    'team.metaDesc': 'Meet who is behind kob.dev. Nicolás Mery, founder: technology leader with 20+ years in tech and 8+ as CTO and Chief Architect in AI, security and fintech.',
509    'team.back': '← Back to home',
510    'team.tag': 'About us',
511    'team.h2': 'Who is behind kob.dev',
512    'team.desc': 'A real track record building technology, security and generative AI.',
513    'team.name': 'Nicolás Mery',
514    'team.role': 'Founder',
515    'team.bio': 'Technology leader with <strong>20+ years in tech and 8+ as CTO and Chief Architect</strong> across fintech, crypto, payments, cybersecurity and SaaS. Now focused on building production <strong>generative AI agent systems</strong>. OSCP-certified and MIT CTO Program alumnus.',
516    'team.chip1': '8+ years as CTO',
517    'team.chip2': 'Generative AI',
518    'team.chip3': 'OSCP',
519    'team.chip4': 'MIT CTO Program',
520    'team.cta1': "Let's talk",
521    'team.cta2': 'See services',
522
523    // Audios para saber más (/i-know-kung-fu)
524    'foot.laudios': 'Audio to go deeper',
525    'kf.metaTitle': 'Audio to go deeper — kob.dev',
526    'kf.metaDesc': "Our own research at three depths: a 3-minute summary, a 30-minute version and the full audiobook.",
527    'kf.back': '← kob.dev',
528    'kf.tag': 'Research',
529    'kf.h1': 'Audio to go deeper',
530    'kf.subtitle': "The kob.dev team's own research, narrated. Every topic comes at <strong>three depths</strong>: listen to the summaries right here and, if the topic grabs you, request the full audiobook.",
531    'kf.n1.dur': '3 minutes',
532    'kf.n1.h': 'Is this for me?',
533    'kf.n1.p': "Just enough to know what the topic is about and decide whether it's worth your time.",
534    'kf.n2.dur': '30 minutes',
535    'kf.n2.h': 'I want to understand it',
536    'kf.n2.p': 'The version that leaves you with real judgement: the concepts, the failure modes and the decisions that matter.',
537    'kf.n3.dur': 'Audiobook',
538    'kf.n3.h': 'I want to dig in',
539    'kf.n3.p': 'The full research, one to three hours: the state of the art with its sources and its grey areas.',
540    'kf.aviso': '<strong>The audio is narrated in English.</strong> That comes from the pipeline we generate it with, not from an editorial choice. We are working on Spanish and other languages. The written research is in Spanish.',
541    'kf.t1.h': 'Context engineering',
542    'kf.t1.meta': '44 notes · ~32,000 words · state of the art as of July 2026',
543    'kf.t1.desc': 'How you decide what goes into a model\'s context window and what stays out. Covers the failure modes —<em>context rot</em>, <em>lost in the middle</em>—, the four strategies of write, select, compress and isolate, agent memory, the RAG versus long-context debate, tool design and multi-agent architectures.',
544    'kf.t2.h': 'Loop engineering',
545    'kf.t2.meta': '26 notes · ~17,500 words · state of the art as of July 2026',
546    'kf.t2.desc': 'The design of agentic loops: when to let an agent iterate on its own and, above all, how to know it should stop. Covers the five levels of verification, stopping conditions, <em>maker-checker</em> architectures, guardrails and budgets, and the risks —comprehension debt, 
546cognitive surrender, <em>reward hacking</em>.',
547    'kf.t2.nota': 'The research warns that the empirical base of this field is still thin, and flags note by note where each claim comes from.',
548    'kf.f.corto': '3-minute summary',
549    'kf.f.medio': 'In-depth version',
550    'kf.f.libro': 'Full audiobook',
551    'kf.f.pronto': 'Still in production',
552    'kf.descargar': 'Download',
553    'kf.pedir': 'Request the audiobook',
554    'kf.pronto': 'Coming soon',
555    'kf.mas': "We keep researching. New topics will show up here as they're ready.",
556    'kf.a11y.play': 'Play',
557    'kf.a11y.pause': 'Pause',
558    'kf.p.tag': 'Audiobook',
559    'kf.p.h2': 'Request the full audiobook',
560    'kf.p.p': 'Audiobooks are large files and we send them by hand, one by one. <strong>Leave us your details and we will email you the download link.</strong> Includes the MP3 and the M4B with navigable chapters.',
561    'kf.p.tema': 'Which audiobook do you want? *',
562    'kf.p.ph.tema': 'e.g. Context engineering',
563    'kf.p.hint.tema': "If the topic you're after isn't published yet, write it anyway — it tells us what to research next.",
564    'kf.p.motivo': 'What do you want it for? (optional)',
565    'kf.p.ph.motivo': 'It helps us know what to research next.',
566    'kf.p.submit': 'Request the audiobook',
567    'kf.p.msg.success': "Done! We'll email you the download link.",
568    'kf.p.msg.error': 'We could not register your request. Please try again or write to [email protected].',
569
570    // La landing "Tu primer agente" no se traduce: si el visitante tiene el sitio
571    // en inglés y llega igual, ve la página en español, que es como está escrita.
572    'pa.metaTitle': 'Tu primer agente, desde $49.000 — kob.dev',
573    'pa.metaDesc': 'Construyo la primera versión de un agente que hace esa tarea repetitiva que le quita horas a tu empresa, con tus datos reales. En una semana, y si no sigues el código es tuyo.',
574    'pa.msg.success': '¡Listo! Recibí tu caso. Te respondo en 48 horas, sea que sirva o no.',
575    'pa.msg.error': 'No pudimos enviar tu caso. Vuelve a intentarlo o escríbeme a [email protected].',
576    'pa.msg.herramientas': 'Marca al menos una herramienta en la pregunta 5.',
577  },
578};
579
580// Current active language (used by the contact form for its messages).
581let currentLang = 'es';
582
583function t(key) {
584  const dict = I18N[currentLang] || I18N.es;
585  return dict[key] != null ? dict[key] : (I18N.es[key] != null ? I18N.es[key] : key);
586}
587
588// El sitio parte siempre en español (mercado chileno y versión que indexan los
589// buscadores). El inglés se muestra solo si la persona lo eligió con el botón.
590function detectLang() {
591  const saved = localStorage.getItem('lang');
592  return saved === 'en' ? 'en' : 'es';
593}
594
595function applyLang(lang, persist = false) {
596  currentLang = I18N[lang] ? lang : 'es';
597
598  // Text / inline-HTML content
599  document.querySelectorAll('[data-i18n]').forEach((el) => {
600    const key = el.getAttribute('data-i18n');
601    const val = t(key);
602    if (val != null) el.innerHTML = val;
603  });
604
605  // Placeholders
606  document.querySelectorAll('[data-i18n-ph]').forEach((el) => {
607    const key = el.getAttribute('data-i18n-ph');
608    const val = t(key);
609    if (val != null) el.setAttribute('placeholder', val);
610  });
611
612  // aria-labels (p. ej. el botón de reproducir del reproductor de audio)
613  document.querySelectorAll('[data-i18n-aria]').forEach((el) => {
614    const key = el.getAttribute('data-i18n-aria');
615    const val = t(key);
616    if (val != null) el.setAttribute('aria-label', val);
617  });
618
619  // Document metadata (each page may override which keys to use via data attributes on <body>)
620  document.documentElement.setAttribute('lang', currentLang === 'en' ? 'en' : 'es-CL');
621  const titleKey = document.body.dataset.titleKey || 'meta.title';
622  const descKey = document.body.dataset.descKey || 'meta.desc';
623  document.title = t(titleKey);
624  const metaDesc = document.getElementById('meta-desc');
625  if (metaDesc) metaDesc.setAttribute('content', t(descKey));
626
627  // Switcher active state
628  document.querySelectorAll('#lang-switch button').forEach((btn) => {
629    btn.classList.toggle('active', btn.getAttribute('data-lang') === currentLang);
630    btn.setAttribute('aria-pressed', String(btn.getAttribute('data-lang') === currentLang));
631  });
632
633  if (persist) localStorage.setItem('lang', currentLang);
634}
635
636function initI18n() {
637  // Páginas de un solo idioma (p. ej. las landings por industria) lo declaran con
638  // <body data-single-lang="es">: conservan su <title>, su descripción y su lang
639  // del HTML, sin depender del idioma del navegador de quien las visita.
640  const singleLang = document.body.dataset.singleLang;
641  if (singleLang) {
642    currentLang = I18N[singleLang] ? singleLang : 'es';
643    return;
644  }
645
646  applyLang(detectLang());
647
648  const sw = document.getElementById('lang-switch');
649  if (sw) {
650    sw.addEventListener('click', (e) => {
651      const btn = e.target.closest('button[data-lang]');
652      if (!btn) return;
653      applyLang(btn.getAttribute('data-lang'), true);
654    });
655  }
656}
657
658/* ----------------------------------------
659   Scroll Animations (IntersectionObserver)
660   ---------------------------------------- */
661function initScrollAnimations() {
662  const elements = document.querySelectorAll('.animate-on-scroll');
663  if (!elements.length) return;
664
665  const observer = new IntersectionObserver((entries) => {
666    entries.forEach((entry, index) => {
667      if (entry.isIntersecting) {
668        // Stagger the animation slightly for each card
669        setTimeout(() => {
670          entry.target.classList.add('visible');
671        }, index * 80);
672        observer.unobserve(entry.target);
673      }
674    });
675  }, {
676    threshold: 0.15,
677    rootMargin: '0px 0px -40px 0px'
678  });
679
680  elements.forEach(el => observer.observe(el));
681}
682
683/* ----------------------------------------
684   Smooth Scroll for anchor links
685   ---------------------------------------- */
686function initSmoothScroll() {
687  document.querySelectorAll('a[href^="#"]').forEach(anchor => {
688    anchor.addEventListener('click', (e) => {
689      e.preventDefault();
690      const target = document.querySelector(anchor.getAttribute('href'));
691      if (target) {
692        target.scrollIntoView({ behavior: 'smooth', block: 'start' });
693      }
694    });
695  });
696}
697
698/* ----------------------------------------
699   Contact Form Submission
700   ---------------------------------------- */
701function initContactForm() {
702  const form = document.getElementById('contact-form');
703  if (!form) return;
704
705  form.addEventListener('submit', async (e) => {
706    e.preventDefault();
707
708    const submitBtn = document.getElementById('submit-btn');
709    const btnText = submitBtn.querySelector('.btn-text');
710    const btnLoading = submitBtn.querySelector('.btn-loading');
711    const formMessage = document.getElementById('form-message');
712
713    // Disable button and show loading
714    submitBtn.disabled = true;
715    btnText.style.display = 'none';
716    btnLoading.style.display = 'inline';
717    formMessage.style.display = 'none';
718
719    const formData = new FormData(form);
720    const data = Object.fromEntries(formData.entries());
721    // Cloudflare Turnstile injects a hidden input named "cf-turnstile-response".
722    // Send it under a clearer key for the server.
723    data.turnstileToken = data['cf-turnstile-response'] || '';
724    // Tell the server which language the visitor is using (optional context).
725    data.lang = currentLang;
726
727    try {
728      const response = await fetch('/api/contact', {
729        method: 'POST',
730        headers: { 'Content-Type': 'application/json' },
731        body: JSON.stringify(data)
732      });
733
734      if (!response.ok) {
735        const err = await response.json().catch(() => ({}));
736        throw new Error(err.error || t('form.msg.error'));
737      }
738
739      // Success
740      formMessage.textContent = t('form.msg.success');
741      formMessage.className = 'form-message success';
742      formMessage.style.display = 'block';
743      form.reset();
744
745    } catch (error) {
746      formMessage.textContent = error.message || t('form.msg.error');
747      formMessage.className = 'form-message error';
748      formMessage.style.display = 'block';
749    } finally {
750      submitBtn.disabled = false;
751      btnText.style.display = 'inline';
752      btnLoading.style.display = 'none';
753      // Reset the Turnstile widget so a new token is issued for the next submit.
754      if (window.turnstile) {
755        try { window.turnstile.reset(); } catch (e) { /* noop */ }
756      }
757    }
758  });
759}
760
761/* ----------------------------------------
762   Reproductores de audio (/i-know-kung-fu)
763
764   El <audio> se crea recién al primer play: así abrir la página no
765   descarga ningún MP3. Sólo puede sonar uno a la vez.
766   ---------------------------------------- */
767function initAudioPlayers() {
768  const players = document.querySelectorAll('.kf-player');
769  if (!players.length) return;
770
771  players.forEach((player) => setupAudioPlayer(player, players));
772}
773
774function setupAudioPlayer(player, allPlayers) {
775  const btn = player.querySelector('.kf-play');
776  const progress = player.querySelector('.kf-progress');
777  const fill = player.querySelector('.kf-progress-fill');
778  const timeEl = player.querySelector('.kf-time');
779  const src = player.getAttribute('data-src');
780  // Duración conocida de antemano: permite mostrar el tiempo y aceptar
781  // saltos en la barra antes de que el archivo se haya cargado.
782  const knownDuration = parseFloat(player.getAttribute('data-dur')) || 0;
783
784  if (!btn || !src) return;
785
786  let audio = null;
787  let pendingSeek = null;
788
789  function duration() {
790    return audio && isFinite(audio.duration) && audio.duration > 0 ? audio.duration : knownDuration;
791  }
792
793  function render() {
794    const total = duration();
795    const current = audio ? audio.currentTime : 0;
796    const pct = total > 0 ? Math.min(100, (current / total) * 100) : 0;
797    if (fill) fill.style.width = pct + '%';
798    if (timeEl) timeEl.textContent = `${formatTime(current)} / ${formatTime(total)}`;
799    if (progress) {
800      progress.setAttribute('aria-valuenow', String(Math.round(pct)));
801      progress.setAttribute('aria-valuemin', '0');
802      progress.setAttribute('aria-valuemax', '100');
803    }
804  }
805
806  function ensureAudio() {
807    if (audio) return audio;
808
809    audio = new Audio();
810    audio.preload = 'metadata';
811    audio.src = src;
812
813    audio.addEventListener('timeupdate', render);
814    audio.addEventListener('loadedmetadata', () => {
815      if (pendingSeek != null) {
816        audio.currentTime = pendingSeek * duration();
817        pendingSeek = null;
818      }
819      render();
820    });
821    audio.addEventListener('playing', () => {
822      btn.classList.remove('is-loading');
823      player.classList.add('is-playing');
824      btn.setAttribute('aria-label', t('kf.a11y.pause'));
825    });
826    audio.addEventListener('pause', () => {
827      player.classList.remove('is-playing');
828      btn.setAttribute('aria-label', t('kf.a11y.play'));
829    });
830    audio.addEventListener('ended', () => {
831      player.classList.remove('is-playing');
832      btn.setAttribute('aria-label', t('kf.a11y.play'));
833      audio.currentTime = 0;
834      render();
835    });
836    audio.addEventListener('error', () => {
837      btn.classList.remove('is-loading');
838      player.classList.remove('is-playing');
839    });
840
841    return audio;
842  }
843
844  btn.addEventListener('click', () => {
845    const a = ensureAudio();
846
847    if (!a.paused) {
848      a.pause();
849      return;
850    }
851
852    // Un solo audio sonando a la vez.
853    allPlayers.forEach((other) => {
854      if (other !== player) other.dispatchEvent(new CustomEvent('kf:pause'));
855    });
856
857    btn.classList.add('is-loading');
858    const played = a.play();
859    if (played && typeof played.catch === 'function') {
860      played.catch(() => btn.classList.remove('is-loading'));
861    }
862  });
863
864  // Otro reproductor pide que este se detenga.
865  player.addEventListener('kf:pause', () => {
866    if (audio && !audio.paused) audio.pause();
867  });
868
869  // Saltar a un punto pinchando la barra de progreso.
870  if (progress) {
871    progress.addEventListener('click', (e) => {
872      const rect = progress.getBoundingClientRect();
873      if (!rect.width) return;
874      const ratio = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width));
875      const a = ensureAudio();
876
877      if (isFinite(a.duration) && a.duration > 0) {
878        a.currentTime = ratio * a.duration;
879        render();
880      } else {
881        // Todavía no hay metadatos: se aplica en cuanto lleguen.
882        pendingSeek = ratio;
883        if (fill) fill.style.width = ratio * 100 + '%';
884      }
885    });
886
887    // Flechas izquierda/derecha para moverse 10 segundos.
888    progress.addEventListener('keydown', (e) => {
889      if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
890      e.preventDefault();
891      const a = ensureAudio();
892      const total = duration();
893      if (!total) return;
894      const delta = e.key === 'ArrowRight' ? 10 : -10;
895      a.currentTime = Math.min(total, Math.max(0, a.currentTime + delta));
896      render();
897    });
898  }
899
900  render();
901}
902
903function formatTime(seconds) {
904  if (!isFinite(seconds) || seconds < 0) seconds = 0;
905  const total = Math.floor(seconds);
906  const h = Math.floor(total / 3600);
907  const m = Math.floor((total % 3600) / 60);
908  const s = total % 60;
909  const pad = (n) => String(n).padStart(2, '0');
910  return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
911}
912
913/* ----------------------------------------
914   Al pulsar «Pedir el audiolibro», rellena el campo del formulario
915   con el nombre del tema. El campo es libre: se puede editar o pedir
916   un tema que todavía no publicamos.
917   ---------------------------------------- */
918function initTemaPreselect() {
919  const botones = document.querySelectorAll('a[data-tema-key]');
920  if (!botones.length) return;
921
922  botones.forEach((boton) => {
923    boton.addEventListener('click', () => {
924      const campo = document.getElementById('ab-tema');
925      if (campo) campo.value = t(boton.getAttribute('data-tema-key'));
926    });
927  });
928}
929
930/* ----------------------------------------
931   Formulario de pedido del audiolibro
932   ---------------------------------------- */
933function initAudiolibroForm() {
934  const form = document.getElementById('audiolibro-form');
935  if (!form) return;
936
937  form.addEventListener('submit', async (e) => {
938    e.preventDefault();
939
940    const submitBtn = document.getElementById('ab-submit-btn');
941    const btnText = submitBtn.querySelector('.btn-text');
942    const btnLoading = submitBtn.querySelector('.btn-loading');
943    const formMessage = document.getElementById('ab-form-message');
944
945    submitBtn.disabled = true;
946    btnText.style.display = 'none';
947    btnLoading.style.display = 'inline';
948    formMessage.style.display = 'none';
949
950    const data = Object.fromEntries(new FormData(form).entries());
951    data.turnstileToken = data['cf-turnstile-response'] || '';
952    data.lang = currentLang;
953
954    try {
955      const response = await fetch('/api/audiolibro', {
956        method: 'POST',
957        headers: { 'Content-Type': 'application/json' },
958        body: JSON.stringify(data),
959      });
960
961      if (!response.ok) {
962        const err = await response.json().catch(() => ({}));
963        throw new Error(err.error || t('kf.p.msg.error'));
964      }
965
966      formMessage.textContent = t('kf.p.msg.success');
967      formMessage.className = 'form-message success';
968      formMessage.style.display = 'block';
969      form.reset();
970
971    } catch (error) {
972      formMessage.textContent = error.message || t('kf.p.msg.error');
973      formMessage.className = 'form-message error';
974      formMessage.style.display = 'block';
975    } finally {
976      submitBtn.disabled = false;
977      btnText.style.display = 'inline';
978      btnLoading.style.display = 'none';
979      if (window.turnstile) {
980        try { window.turnstile.reset(); } catch (e) { /* noop */ }
981      }
982    }
983  });
984}
985
986/* ----------------------------------------
987   Formulario de "Tu primer agente" (/tu-primer-agente)
988
989   La pregunta 5 admite varias respuestas, así que el payload no se puede armar
990   con Object.fromEntries: hay que sacar las marcas con getAll().
991   ---------------------------------------- */
992function initPrimerAgenteForm() {
993  const form = document.getElementById('primer-agente-form');
994  if (!form) return;
995
996  // "Otra" abre un campo de texto obligatorio: sin saber cuál es la herramienta,
997  // la respuesta no sirve para evaluar el caso.
998  const checkOtra = document.getElementById('pa-check-otra');
999  const wrapOtra = document.getElementById('pa-otra-wrap');
1000  const inputOtra = document.getElementById('pa-otra');
1001
1002  if (checkOtra && wrapOtra && inputOtra) {
1003    const sincronizarOtra = () => {
1004      wrapOtra.style.display = checkOtra.checked ? 'block' : 'none';
1005      if (checkOtra.checked) {
1006        inputOtra.setAttribute('required', 'required');
1007        inputOtra.focus();
1008      } else {
1009        inputOtra.removeAttribute('required');
1010        inputOtra.value = '';
1011      }
1012    };
1013    checkOtra.addEventListener('change', sincronizarOtra);
1014  }
1015
1016  form.addEventListener('submit', async (e) => {
1017    e.preventDefault();
1018
1019    const submitBtn = document.getElementById('pa-submit-btn');
1020    const btnText = submitBtn.querySelector('.btn-text');
1021    const btnLoading = submitBtn.querySelector('.btn-loading');
1022    const formMessage = document.getElementById('pa-form-message');
1023
1024    const formData = new FormData(form);
1025    const herramientas = formData.getAll('herramientas');
1026
1027    // Los checkboxes no tienen validación nativa de "al menos uno".
1028    if (!herramientas.length) {
1029      formMessage.textContent = t('pa.msg.herramientas');
1030      formMessage.className = 'form-message error';
1031      formMessage.style.display = 'block';
1032      document.getElementById('pa-herramientas').scrollIntoView({ behavior: 'smooth', block: 'center' });
1033      return;
1034    }
1035
1036    submitBtn.disabled = true;
1037    btnText.style.display = 'none';
1038    btnLoading.style.display = 'inline';
1039    formMessage.style.display = 'none';
1040
1041    const data = Object.fromEntries(formData.entries());
1042    data.herramientas = herramientas;
1043    data.turnstileToken = data['cf-turnstile-response'] || '';
1044
1045    try {
1046      const response = await fetch('/api/primer-agente', {
1047        method: 'POST',
1048        headers: { 'Content-Type': 'application/json' },
1049        body: JSON.stringify(data),
1050      });
1051
1052      if (!response.ok) {
1053        const err = await response.json().catch(() => ({}));
1054        throw new Error(err.error || t('pa.msg.error'));
1055      }
1056
1057      formMessage.textContent = t('pa.msg.success');
1058      formMessage.className = 'form-message success';
1059      formMessage.style.display = 'block';
1060      form.reset();
1061      // form.reset() desmarca el checkbox pero no vuelve a ocultar su campo.
1062      if (wrapOtra && inputOtra) {
1063        wrapOtra.style.display = 'none';
1064        inputOtra.removeAttribute('required');
1065      }
1066
1067    } catch (error) {
1068      formMessage.textContent = error.message || t('pa.msg.error');
1069      formMessage.className = 'form-message error';
1070      formMessage.style.display = 'block';
1071    } finally {
1072      submitBtn.disabled = false;
1073      btnText.style.display = 'inline';
1074      btnLoading.style.display = 'none';
1075      if (window.turnstile) {
1076        try { window.turnstile.reset(); } catch (e) { /* noop */ }
1077      }
1078    }
1079  });
1080}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.