Translated ['', 'src/welcome/hacktricks-values-and-faq.md'] to es

This commit is contained in:
Translator 2025-10-04 09:05:12 +00:00
parent 8c441ed615
commit 0d36f271d0
2 changed files with 238 additions and 201 deletions

View File

@ -7,14 +7,14 @@
> [!TIP]
> Estos son los **valores del Proyecto HackTricks**:
>
> - Dar acceso **GRATIS** a recursos **educativos de hacking** a **TODO** Internet.
> - Hacking trata sobre aprender, y el aprendizaje debería ser lo más libre posible.
> - Dar **FREE** acceso a recursos **EDUCATIONAL hacking** a **TODO** Internet.
> - El hacking trata sobre aprender, y el aprendizaje debería ser lo más gratuito posible.
> - El propósito de este libro es servir como un **recurso educativo** integral.
> - **ALMACENAR** increíbles **hacking** técnicas que la comunidad publica dando a los **AUTORES** **ORIGINALES** todos los **créditos**.
> - **STORE** increíbles técnicas de **hacking** que la comunidad publica dando a los **AUTORES** **ORIGINALES** todos los **créditos**.
> - **No queremos el crédito de otras personas**, solo queremos almacenar trucos geniales para todos.
> - También escribimos **nuestras propias investigaciones** en HackTricks.
> - En varios casos solo escribiremos **en HackTricks un resumen de las partes importantes** de la técnica y **animaremos al lector a visitar el post original** para más detalles.
> - **ORGANIZAR** todas las técnicas de **hacking** en el libro para que sea **MÁS ACCESIBLE**
> - En varios casos solo escribiremos **en HackTricks un resumen de las partes importantes** de la técnica y **animaremos al lector a visitar la publicación original** para más detalles.
> - **ORGANIZE** todas las técnicas de **hacking** en el libro para que sea **MÁS ACCESIBLE**
> - El equipo de HackTricks ha dedicado miles de horas de forma gratuita **solo para organizar el contenido** para que la gente pueda **aprender más rápido**
<figure><img src="../images/hack tricks gif.gif" alt="" width="375"><figcaption></figcaption></figure>
@ -25,32 +25,32 @@
>
> - **Muchas gracias por estos recursos, ¿cómo puedo agradecerles?**
Puedes agradecer públicamente a los equipos de HackTricks por reunir todos estos recursos públicamente en un tweet mencionando [**@hacktricks_live**](https://twitter.com/hacktricks_live).\
If you are specially grateful you can also [**sponsor the project here**](https://github.com/sponsors/carlospolop).\
Y no olvides **dar una estrella en los proyectos de Github!** (Encuentra los enlaces abajo).
Puedes agradecer públicamente al equipo de HackTricks por reunir todos estos recursos públicamente en un tweet mencionando [**@hacktricks_live**](https://twitter.com/hacktricks_live).\
Si estás especialmente agradecido también puedes [**patrocinar el proyecto aquí**](https://github.com/sponsors/carlospolop).\
Y no olvides **dar una estrella a los proyectos de Github!** (¡Encuentra los enlaces abajo!).
> [!TIP]
>
> - **¿Cómo puedo contribuir al proyecto?**
Puedes **compartir nuevos consejos y trucos con la comunidad o corregir bugs** que encuentres en los libros enviando un **Pull Request** a las páginas respectivas de Github:
Puedes **compartir nuevos consejos y trucos con la comunidad o corregir errores** que encuentres en los libros enviando un **Pull Request** a las páginas correspondientes de Github:
- [https://github.com/carlospolop/hacktricks](https://github.com/carlospolop/hacktricks)
- [https://github.com/carlospolop/hacktricks-cloud](https://github.com/carlospolop/hacktricks-cloud)
¡No olvides **dar una estrella en los proyectos de Github!**
No olvides **dar una estrella a los proyectos de Github!**
> [!TIP]
>
> - **¿Puedo copiar contenido de HackTricks y ponerlo en mi blog?**
Sí, puedes, pero **no olvides mencionar el/los enlace(s) específicos** de donde se tomó el contenido.
Sí, puedes, pero **no olvides mencionar los enlaces específicos** de donde se tomó el contenido.
> [!TIP]
>
> - **¿Cómo puedo citar una página de HackTricks?**
> - **¿Cómo puedo referenciar una página de HackTricks?**
Con tal de que aparezca el enlace **de** la(s) página(s) de donde obtuviste la información, es suficiente.\
Mientras aparezca el enlace **de** la(s) página(s) de donde obtuviste la información, es suficiente.\
Si necesitas un bibtex puedes usar algo como:
```latex
@misc{hacktricks-bibtexing,
@ -62,82 +62,82 @@ url = {\url{https://book.hacktricks.wiki/specific-page}},
```
> [!WARNING]
>
> - **Can I copy all HackTricks in my blog?**
> - **¿Puedo copiar todo el contenido de HackTricks en mi blog?**
**Preferiría que no**. Eso **no va a beneficiar a nadie** ya que todo el **contenido ya está disponible públicamente** en los libros oficiales de HackTricks de forma gratuita.
**Preferiría que no**. Eso **no beneficiará a nadie** ya que todo el **contenido ya está disponible públicamente** en los libros oficiales de HackTricks de forma gratuita.
Si temes que vaya a desaparecer, simplemente haz fork en Github o descárgalo; como dije, ya es gratuito.
Si temes que desaparezca, simplemente hazle fork en Github o descárgalo; como dije, ya es gratuito.
> [!WARNING]
>
> - **Why do you have sponsors? Are HackTricks books for commercial purposes?**
> - **¿Por qué tienen sponsors? ¿Los libros de HackTricks son con fines comerciales?**
El primer **valor** de **HackTricks** es ofrecer recursos educativos de hacking **GRATIS** para **TODO** el mundo. El equipo de HackTricks ha **dedicado miles de horas** para ofrecer este contenido, de nuevo, de forma **GRATIS**.
El primer **valor** de **HackTricks** es ofrecer recursos educativos de hacking **GRATIS** para **TODO** el mundo. El equipo de HackTricks ha **dedicado miles de horas** para ofrecer este contenido, de nuevo, **GRATIS**.
Si piensas que los libros de HackTricks están hechos con **fines comerciales** estás **COMPLETAMENTE EQUIVOCADO**.
Tenemos sponsors porque, aunque todo el contenido sea GRATIS, queremos **ofrecer a la comunidad la posibilidad de valorar nuestro trabajo** si lo desean. Por eso ofrecemos a la gente la opción de donar a HackTricks a través de [**Github sponsors**](https://github.com/sponsors/carlospolop), y a **empresas relevantes de ciberseguridad** patrocinar HackTricks y **tener algunos anuncios** en el libro, colocando los **anuncios** siempre en lugares donde sean **visibles** pero **no interfieran en el proceso de aprendizaje** si alguien se concentra en el contenido.
Tenemos patrocinadores porque, aunque todo el contenido sea GRATIS, queremos **ofrecer a la comunidad la posibilidad de valorar nuestro trabajo** si así lo desean. Por ello, ofrecemos a las personas la opción de donar a HackTricks a través de [**Github sponsors**](https://github.com/sponsors/carlospolop), y a **empresas relevantes de ciberseguridad** patrocinar HackTricks y **poner algunos anuncios** en el libro, siendo los **anuncios** siempre colocados en lugares donde sean **visibles** pero **no interfieran en el proceso de aprendizaje** si alguien se concentra en el contenido.
No encontrarás HackTricks lleno de anuncios molestos como otros blogs con mucho menos contenido que HackTricks, porque HackTricks no está hecho con fines comerciales.
No encontrarás a HackTricks lleno de anuncios molestos como otros blogs con mucho menos contenido que HackTricks, porque HackTricks no está hecho con fines comerciales.
> [!CAUTION]
>
> - **What should I do if some HackTricks page is based on my blog post but it isn't referenced?**
> - **¿Qué debo hacer si alguna página de HackTricks está basada en mi entrada de blog pero no está referenciada?**
**Lo sentimos mucho. Esto no debería haber ocurrido**. Por favor, háznoslo saber mediante Github issues, Twitter, Discord... el enlace de la página de HackTricks con el contenido y el enlace de tu blog y **lo revisaremos y lo añadiremos lo antes posible**.
**Lo sentimos mucho. Esto no debería haber ocurrido**. Por favor, háznoslo saber vía Github issues, Twitter, Discord... el enlace de la página de HackTricks con el contenido y el enlace de tu blog y **lo comprobaremos y lo añadiremos lo antes posible**.
> [!CAUTION]
>
> - **What should I do if there is content from my blog in HackTricks and I don't want it there?**
> - **¿Qué debo hacer si hay contenido de mi blog en HackTricks y no quiero que esté ahí?**
Ten en cuenta que tener enlaces a tu página en HackTricks:
- Mejora tu **SEO**
- El contenido se **traduce a más de 15 idiomas**, permitiendo que más personas accedan a este contenido
- **HackTricks anima** a la gente a **visitar tu página** (varias personas nos han mencionado que desde que alguna de sus páginas está en HackTricks reciben más visitas)
- El contenido se **traduce a más de 15 idiomas**, permitiendo que más gente acceda a este contenido
- **HackTricks anima** a la gente a **visitar tu página** (varias personas nos han comentado que desde que alguna de sus páginas está en HackTricks reciben más visitas)
Sin embargo, si aún quieres que el contenido de tu blog sea eliminado de HackTricks, solo háznoslo saber y definitivamente **eliminaremos todo enlace a tu blog**, y cualquier contenido basado en él.
Sin embargo, si aún deseas que el contenido de tu blog sea eliminado de HackTricks, simplemente avísanos y **definitivamente eliminaremos todo enlace a tu blog**, y cualquier contenido basado en él.
> [!CAUTION]
>
> - **What should I do if I find copy-pasted content in HackTricks?**
> - **¿Qué debo hacer si encuentro contenido copiado y pegado en HackTricks?**
Siempre **damos a los autores originales todos los créditos**. Si encuentras una página con contenido copiado y pegado sin la fuente original referenciada, háznoslo saber y nosotros o bien **la eliminaremos**, **añadiremos el enlace antes del texto**, o **la reescribiremos añadiendo el enlace**.
Siempre **damos todo el crédito a los autores originales**. Si encuentras una página con contenido copiado sin referencia a la fuente original, háznoslo saber y nosotros **lo eliminaremos**, **añadiremos el enlace antes del texto**, o **lo reescribiremos añadiendo el enlace**.
## LICENCIA
Copyright © Todos los derechos reservados, salvo que se especifique lo contrario.
Copyright © Todos los derechos reservados salvo que se especifique lo contrario.
#### Resumen de la licencia:
- Atribución: Eres libre de:
- Compartir — copiar y redistribuir el material en cualquier medio o formato.
- Adaptar — remixar, transformar y crear obras derivadas del material.
- Adaptar — remezclar, transformar y elaborar a partir del material.
#### Términos adicionales:
- Contenido de terceros: Algunas partes de este blog/libro pueden incluir contenido de otras fuentes, como extractos de otros blogs o publicaciones. El uso de dicho contenido se realiza bajo los principios de fair use o con permiso explícito de los respectivos titulares de derechos de autor. Por favor, consulta las fuentes originales para información específica sobre licencias respecto al contenido de terceros.
- Autoría: El contenido original creado por HackTricks está sujeto a los términos de esta licencia. Se recomienda atribuir este trabajo al autor cuando lo compartas o adaptes.
- Contenido de terceros: Algunas partes de este blog/libro pueden incluir contenido de otras fuentes, como extractos de otros blogs o publicaciones. El uso de dicho contenido se realiza bajo los principios del uso legítimo (fair use) o con permiso explícito de los titulares de derechos de autor correspondientes. Por favor, consulta las fuentes originales para información específica sobre la licencia del contenido de terceros.
- Autoría: El contenido original creado por HackTricks está sujeto a los términos de esta licencia. Se recomienda atribuir esta obra al autor al compartirla o adaptarla.
#### Exenciones:
- Uso comercial: Para consultas sobre el uso comercial de este contenido, por favor contáctame.
Esta licencia no concede ningún derecho sobre marcas comerciales o branding en relación con el contenido. Todas las marcas comerciales y el branding que aparecen en este blog/libro son propiedad de sus respectivos dueños.
Esta licencia no concede ningún derecho sobre marcas registradas o identidad de marca en relación con el contenido. Todas las marcas y elementos de branding mostrados en este blog/libro son propiedad de sus respectivos titulares.
**Al acceder o usar HackTricks, aceptas cumplir con los términos de esta licencia. Si no estás de acuerdo con estos términos, por favor, no accedas a este sitio web.**
**Al acceder o usar HackTricks, aceptas cumplir los términos de esta licencia. Si no estás de acuerdo con estos términos, por favor, no accedas a este sitio web.**
## **Descargo de responsabilidad**
> [!CAUTION]
> Este libro, 'HackTricks,' está destinado únicamente a fines educativos e informativos. El contenido de este libro se proporciona 'tal cual', y los autores y editores no hacen representaciones ni garantías de ningún tipo, expresas o implícitas, sobre la totalidad, exactitud, fiabilidad, idoneidad o disponibilidad de la información, productos, servicios o gráficos relacionados contenidos en este libro. Cualquier confianza que deposites en dicha información será, por tanto, estrictamente bajo tu propia responsabilidad.
> Este libro, 'HackTricks,' está destinado únicamente a fines educativos e informativos. El contenido de este libro se proporciona 'tal cual', y los autores y editores no hacen declaraciones ni garantías de ningún tipo, expresas o implícitas, sobre la integridad, exactitud, fiabilidad, idoneidad o disponibilidad de la información, productos, servicios o gráficos relacionados contenidos en este libro. Cualquier confianza que deposites en dicha información es, por tanto, estrictamente bajo tu propia responsabilidad.
>
> En ningún caso los autores y editores serán responsables de ninguna pérdida o daño, incluyendo sin limitación, pérdidas o daños indirectos o consecuentes, o cualquier pérdida o daño que surja de la pérdida de datos o beneficios derivados de, o en conexión con, el uso de este libro.
> Los autores y editores no serán en ningún caso responsables por cualquier pérdida o daño, incluyendo, sin limitación, pérdidas o daños indirectos o consecuentes, o cualquier pérdida o daño que surja de la pérdida de datos o beneficios derivados de, o en relación con, el uso de este libro.
>
> Además, las técnicas y consejos descritos en este libro se proporcionan únicamente con fines educativos e informativos, y no deben utilizarse para actividades ilegales o maliciosas. Los autores y editores no aprueban ni apoyan ninguna actividad ilegal o poco ética, y cualquier uso de la información contenida en este libro corre por cuenta y riesgo del usuario.
> Además, las técnicas y consejos descritos en este libro se proporcionan únicamente con fines educativos e informativos, y no deben utilizarse para actividades ilegales o maliciosas. Los autores y editores no avalan ni apoyan actividades ilegales o poco éticas, y cualquier uso de la información contenida en este libro es bajo la propia responsabilidad y criterio del usuario.
>
> El usuario es el único responsable de cualquier acción tomada basándose en la información contenida en este libro, y siempre debe buscar asesoramiento y asistencia profesional al intentar implementar cualquiera de las técnicas o consejos descritos en el mismo.
> El usuario es el único responsable de cualquier acción tomada con base en la información contenida en este libro, y siempre debe buscar asesoramiento y asistencia profesional al intentar implementar cualquiera de las técnicas o consejos aquí descritos.
>
> Al usar este libro, el usuario acepta liberar a los autores y editores de cualquier y toda responsabilidad por daños, pérdidas o perjuicios que puedan resultar del uso de este libro o de cualquiera de las informaciones contenidas en el mismo.
> Al usar este libro, el usuario acepta eximir a los autores y editores de toda responsabilidad por cualquier daño, pérdida o perjuicio que pueda resultar del uso de este libro o de cualquiera de las informaciones contenidas en él.
{{#include ../banners/hacktricks-training.md}}

View File

@ -6,34 +6,63 @@
*/
(() => {
"use strict";
"use strict";
/* ───────────── 0. helpers (main thread) ───────────── */
const clear = el => { while (el.firstChild) el.removeChild(el.firstChild); };
/* ───────────── 1. WebWorker code ─────────────────── */
const workerCode = `
self.window = self;
self.search = self.search || {};
const abs = p => location.origin + p;
/* 1 — elasticlunr */
try { importScripts('https://cdn.jsdelivr.net/npm/elasticlunr@0.9.5/elasticlunr.min.js'); }
catch { importScripts(abs('/elasticlunr.min.js')); }
/* 2 — decompress gzip data */
async function decompressGzip(arrayBuffer){
if(typeof DecompressionStream !== 'undefined'){
/* Modern browsers: use native DecompressionStream */
const stream = new Response(arrayBuffer).body.pipeThrough(new DecompressionStream('gzip'));
const decompressed = await new Response(stream).arrayBuffer();
return new TextDecoder().decode(decompressed);
} else {
/* Fallback: use pako library */
if(typeof pako === 'undefined'){
try { importScripts('https://cdn.jsdelivr.net/npm/pako@2.1.0/dist/pako.min.js'); }
catch(e){ throw new Error('pako library required for decompression: '+e); }
}
const uint8Array = new Uint8Array(arrayBuffer);
const decompressed = pako.ungzip(uint8Array, {to: 'string'});
return decompressed;
}
}
/* ───────────── 0. helpers (main thread) ───────────── */
const clear = el => { while (el.firstChild) el.removeChild(el.firstChild); };
/* ───────────── 1. WebWorker code ─────────────────── */
const workerCode = `
self.window = self;
self.search = self.search || {};
const abs = p => location.origin + p;
/* 1 — elasticlunr */
try { importScripts('https://cdn.jsdelivr.net/npm/elasticlunr@0.9.5/elasticlunr.min.js'); }
catch { importScripts(abs('/elasticlunr.min.js')); }
/* 2 — load a single index (remote → local) */
/* 3 — load a single index (remote → local) */
async function loadIndex(remote, local, isCloud=false){
let rawLoaded = false;
if(remote){
/* Try ONLY compressed version from GitHub (remote already includes .js.gz) */
try {
const r = await fetch(remote,{mode:'cors'});
if (!r.ok) throw new Error('HTTP '+r.status);
importScripts(URL.createObjectURL(new Blob([await r.text()],{type:'application/javascript'})));
rawLoaded = true;
} catch(e){ console.warn('remote',remote,'failed →',e); }
if (r.ok) {
const compressed = await r.arrayBuffer();
const text = await decompressGzip(compressed);
importScripts(URL.createObjectURL(new Blob([text],{type:'application/javascript'})));
rawLoaded = true;
console.log('Loaded compressed from GitHub:',remote);
}
} catch(e){ console.warn('compressed GitHub',remote,'failed →',e); }
}
/* If remote (GitHub) failed, fall back to local uncompressed file */
if(!rawLoaded && local){
try { importScripts(abs(local)); rawLoaded = true; }
try {
importScripts(abs(local));
rawLoaded = true;
console.log('Loaded local fallback:',local);
}
catch(e){ console.error('local',local,'failed →',e); }
}
if(!rawLoaded) return null; /* give up on this index */
@ -61,151 +90,159 @@
return local ? loadIndex(null, local, isCloud) : null;
}
let built = [];
const MAX = 30, opts = {bool:'AND', expand:true};
self.onmessage = async ({data}) => {
if(data.type === 'init'){
const lang = data.lang || 'en';
const searchindexBase = 'https://raw.githubusercontent.com/HackTricks-wiki/hacktricks-searchindex/master';
(async () => {
const htmlLang = (document.documentElement.lang || 'en').toLowerCase();
const lang = htmlLang.split('-')[0];
const mainReleaseBase = 'https://github.com/HackTricks-wiki/hacktricks/releases/download';
const cloudReleaseBase = 'https://github.com/HackTricks-wiki/hacktricks-cloud/releases/download';
/* Remote sources are .js.gz (compressed), local fallback is .js (uncompressed) */
const mainFilenames = Array.from(new Set(['searchindex-' + lang + '.js.gz', 'searchindex-en.js.gz']));
const cloudFilenames = Array.from(new Set(['searchindex-cloud-' + lang + '.js.gz', 'searchindex-cloud-en.js.gz']));
const mainTags = Array.from(new Set([\`searchindex-\${lang}\`, 'searchindex-en', 'searchindex-master']));
const cloudTags = Array.from(new Set([\`searchindex-\${lang}\`, 'searchindex-en', 'searchindex-master']));
const MAIN_REMOTE_SOURCES = mainFilenames.map(function(filename) { return searchindexBase + '/' + filename; });
const CLOUD_REMOTE_SOURCES = cloudFilenames.map(function(filename) { return searchindexBase + '/' + filename; });
const MAIN_REMOTE_SOURCES = mainTags.map(tag => \`\${mainReleaseBase}/\${tag}/searchindex.js\`);
const CLOUD_REMOTE_SOURCES = cloudTags.map(tag => \`\${cloudReleaseBase}/\${tag}/searchindex.js\`);
const indices = [];
const main = await loadWithFallback(MAIN_REMOTE_SOURCES , '/searchindex.js', false); if(main) indices.push(main);
const cloud= await loadWithFallback(CLOUD_REMOTE_SOURCES, '/searchindex-cloud.js', true ); if(cloud) indices.push(cloud);
if(!indices.length){ postMessage({ready:false, error:'no-index'}); return; }
/* build index objects */
const built = indices.map(d => ({
idx : elasticlunr.Index.load(d.json),
urls: d.urls,
cloud: d.cloud,
base: d.cloud ? 'https://cloud.hacktricks.wiki/' : ''
}));
postMessage({ready:true});
const MAX = 30, opts = {bool:'AND', expand:true};
self.onmessage = ({data:q}) => {
if(!q){ postMessage([]); return; }
const all = [];
for(const s of built){
const res = s.idx.search(q,opts);
if(!res.length) continue;
const max = res[0].score || 1;
res.forEach(r => {
const doc = s.idx.documentStore.getDoc(r.ref);
all.push({
norm : r.score / max,
title: doc.title,
body : doc.body,
breadcrumbs: doc.breadcrumbs,
url : s.base + s.urls[r.ref],
cloud: s.cloud
const indices = [];
const main = await loadWithFallback(MAIN_REMOTE_SOURCES , '/searchindex-book.js', false); if(main) indices.push(main);
const cloud= await loadWithFallback(CLOUD_REMOTE_SOURCES, '/searchindex.js', true ); if(cloud) indices.push(cloud);
if(!indices.length){ postMessage({ready:false, error:'no-index'}); return; }
/* build index objects */
built = indices.map(d => ({
idx : elasticlunr.Index.load(d.json),
urls: d.urls,
cloud: d.cloud,
base: d.cloud ? 'https://cloud.hacktricks.wiki/' : ''
}));
postMessage({ready:true});
return;
}
const q = data.query || data;
if(!q){ postMessage([]); return; }
const all = [];
for(const s of built){
const res = s.idx.search(q,opts);
if(!res.length) continue;
const max = res[0].score || 1;
res.forEach(r => {
const doc = s.idx.documentStore.getDoc(r.ref);
all.push({
norm : r.score / max,
title: doc.title,
body : doc.body,
breadcrumbs: doc.breadcrumbs,
url : s.base + s.urls[r.ref],
cloud: s.cloud
});
});
});
}
all.sort((a,b)=>b.norm-a.norm);
postMessage(all.slice(0,MAX));
};
})();
`;
}
all.sort((a,b)=>b.norm-a.norm);
postMessage(all.slice(0,MAX));
};
`;
/* ───────────── 2. spawn worker ───────────── */
const worker = new Worker(URL.createObjectURL(new Blob([workerCode],{type:'application/javascript'})));
/* ───────────── 2.1. initialize worker with language ───────────── */
const htmlLang = (document.documentElement.lang || 'en').toLowerCase();
const lang = htmlLang.split('-')[0];
worker.postMessage({type: 'init', lang: lang});
/* ───────────── 3. DOM refs ─────────────── */
const wrap = document.getElementById('search-wrapper');
const bar = document.getElementById('searchbar');
const list = document.getElementById('searchresults');
const listOut = document.getElementById('searchresults-outer');
const header = document.getElementById('searchresults-header');
const icon = document.getElementById('search-toggle');
const READY_ICON = icon.innerHTML;
icon.textContent = '⏳';
icon.setAttribute('aria-label','Loading search …');
icon.setAttribute('title','Search is loading, please wait...');
/* ───────────── 2. spawn worker ───────────── */
const worker = new Worker(URL.createObjectURL(new Blob([workerCode],{type:'application/javascript'})));
/* ───────────── 3. DOM refs ─────────────── */
const wrap = document.getElementById('search-wrapper');
const bar = document.getElementById('searchbar');
const list = document.getElementById('searchresults');
const listOut = document.getElementById('searchresults-outer');
const header = document.getElementById('searchresults-header');
const icon = document.getElementById('search-toggle');
const READY_ICON = icon.innerHTML;
icon.textContent = '⏳';
icon.setAttribute('aria-label','Loading search …');
icon.setAttribute('title','Search is loading, please wait...');
const HOT=83, ESC=27, DOWN=40, UP=38, ENTER=13;
let debounce, teaserCount=0;
/* ───────────── helpers (teaser, metric) ───────────── */
const escapeHTML = (()=>{const M={'&':'&amp;','<':'&lt;','>':'&gt;','"':'&#34;','\'':'&#39;'};return s=>s.replace(/[&<>'"]/g,c=>M[c]);})();
const URL_MARK='highlight';
function metric(c,t){return c?`${c} search result${c>1?'s':''} for '${t}':`:`No search results for '${t}'.`;}
function makeTeaser(body,terms){
const stem=w=>elasticlunr.stemmer(w.toLowerCase());
const T=terms.map(stem),W_S=40,W_F=8,W_N=2,WIN=30;
const W=[],sents=body.toLowerCase().split('. ');
let i=0,v=W_F,found=false;
sents.forEach(s=>{v=W_F; s.split(' ').forEach(w=>{ if(w){ if(T.some(t=>stem(w).startsWith(t))){v=W_S;found=true;} W.push([w,v,i]); v=W_N;} i+=w.length+1; }); i++;});
if(!W.length) return body;
const win=Math.min(W.length,WIN);
const sums=[W.slice(0,win).reduce((a,[,wt])=>a+wt,0)];
for(let k=1;k<=W.length-win;k++) sums[k]=sums[k-1]-W[k-1][1]+W[k+win-1][1];
const best=found?sums.lastIndexOf(Math.max(...sums)):0;
const out=[]; i=W[best][2];
for(let k=best;k<best+win;k++){const [w,wt,pos]=W[k]; if(i<pos){out.push(body.substring(i,pos)); i=pos;} if(wt===W_S) out.push('<em>'); out.push(body.substr(pos,w.length)); if(wt===W_S) out.push('</em>'); i=pos+w.length;}
return out.join('');
}
function format(d,terms){
const teaser=makeTeaser(escapeHTML(d.body),terms);
teaserCount++;
const enc=encodeURIComponent(terms.join(' ')).replace(/'/g,'%27');
const parts=d.url.split('#'); if(parts.length===1) parts.push('');
const abs=d.url.startsWith('http');
const href=`${abs?'':path_to_root}${parts[0]}?${URL_MARK}=${enc}#${parts[1]}`;
const style=d.cloud?" style=\"color:#1e88e5\"":"";
const isCloud=d.cloud?" [Cloud]":" [Book]";
return `<a href="${href}" aria-details="teaser_${teaserCount}"${style}>`+
`${d.breadcrumbs}${isCloud}<span class="teaser" id="teaser_${teaserCount}" aria-label="Search Result Teaser">${teaser}</span></a>`;
}
/* ───────────── UI control ───────────── */
function showUI(s){wrap.classList.toggle('hidden',!s); icon.setAttribute('aria-expanded',s); if(s){window.scrollTo(0,0); bar.focus(); bar.select();} else {listOut.classList.add('hidden'); [...list.children].forEach(li=>li.classList.remove('focus'));}}
function blur(){const t=document.createElement('input'); t.style.cssText='position:absolute;opacity:0;'; icon.appendChild(t); t.focus(); t.remove();}
icon.addEventListener('click',()=>showUI(wrap.classList.contains('hidden')));
document.addEventListener('keydown',e=>{
if(e.altKey||e.ctrlKey||e.metaKey||e.shiftKey) return;
const f=/^(?:input|select|textarea)$/i.test(e.target.nodeName);
if(e.keyCode===HOT && !f){e.preventDefault(); showUI(true);} else if(e.keyCode===ESC){e.preventDefault(); showUI(false); blur();}
else if(e.keyCode===DOWN && document.activeElement===bar){e.preventDefault(); const first=list.firstElementChild; if(first){blur(); first.classList.add('focus');}}
else if([DOWN,UP,ENTER].includes(e.keyCode) && document.activeElement!==bar){const cur=list.querySelector('li.focus'); if(!cur) return; e.preventDefault(); if(e.keyCode===DOWN){const nxt=cur.nextElementSibling; if(nxt){cur.classList.remove('focus'); nxt.classList.add('focus');}} else if(e.keyCode===UP){const prv=cur.previousElementSibling; cur.classList.remove('focus'); if(prv){prv.classList.add('focus');} else {bar.focus();}} else {const a=cur.querySelector('a'); if(a) window.location.assign(a.href);}}
});
bar.addEventListener('input',e=>{ clearTimeout(debounce); debounce=setTimeout(()=>worker.postMessage(e.target.value.trim()),120); });
/* ───────────── worker messages ───────────── */
worker.onmessage = ({data}) => {
if(data && data.ready!==undefined){
if(data.ready){
icon.innerHTML=READY_ICON;
icon.setAttribute('aria-label','Open search (S)');
icon.removeAttribute('title');
}
else {
icon.textContent='❌';
icon.setAttribute('aria-label','Search unavailable');
icon.setAttribute('title','Search is unavailable');
}
return;
const HOT=83, ESC=27, DOWN=40, UP=38, ENTER=13;
let debounce, teaserCount=0;
/* ───────────── helpers (teaser, metric) ───────────── */
const escapeHTML = (()=>{const M={'&':'&amp;','<':'&lt;','>':'&gt;','"':'&#34;','\'':'&#39;'};return s=>s.replace(/[&<>'"]/g,c=>M[c]);})();
const URL_MARK='highlight';
function metric(c,t){return c?`${c} search result${c>1?'s':''} for '${t}':`:`No search results for '${t}'.`;}
function makeTeaser(body,terms){
const stem=w=>elasticlunr.stemmer(w.toLowerCase());
const T=terms.map(stem),W_S=40,W_F=8,W_N=2,WIN=30;
const W=[],sents=body.toLowerCase().split('. ');
let i=0,v=W_F,found=false;
sents.forEach(s=>{v=W_F; s.split(' ').forEach(w=>{ if(w){ if(T.some(t=>stem(w).startsWith(t))){v=W_S;found=true;} W.push([w,v,i]); v=W_N;} i+=w.length+1; }); i++;});
if(!W.length) return body;
const win=Math.min(W.length,WIN);
const sums=[W.slice(0,win).reduce((a,[,wt])=>a+wt,0)];
for(let k=1;k<=W.length-win;k++) sums[k]=sums[k-1]-W[k-1][1]+W[k+win-1][1];
const best=found?sums.lastIndexOf(Math.max(...sums)):0;
const out=[]; i=W[best][2];
for(let k=best;k<best+win;k++){const [w,wt,pos]=W[k]; if(i<pos){out.push(body.substring(i,pos)); i=pos;} if(wt===W_S) out.push('<em>'); out.push(body.substr(pos,w.length)); if(wt===W_S) out.push('</em>'); i=pos+w.length;}
return out.join('');
}
const docs=data, q=bar.value.trim(), terms=q.split(/\s+/).filter(Boolean);
header.textContent=metric(docs.length,q);
clear(list);
docs.forEach(d=>{const li=document.createElement('li'); li.innerHTML=format(d,terms); list.appendChild(li);});
listOut.classList.toggle('hidden',!docs.length);
};
})();
function format(d,terms){
const teaser=makeTeaser(escapeHTML(d.body),terms);
teaserCount++;
const enc=encodeURIComponent(terms.join(' ')).replace(/'/g,'%27');
const parts=d.url.split('#'); if(parts.length===1) parts.push('');
const abs=d.url.startsWith('http');
const href=`${abs?'':path_to_root}${parts[0]}?${URL_MARK}=${enc}#${parts[1]}`;
const style=d.cloud?" style=\"color:#1e88e5\"":"";
const isCloud=d.cloud?" [Cloud]":" [Book]";
return `<a href="${href}" aria-details="teaser_${teaserCount}"${style}>`+
`${d.breadcrumbs}${isCloud}<span class="teaser" id="teaser_${teaserCount}" aria-label="Search Result Teaser">${teaser}</span></a>`;
}
/* ───────────── UI control ───────────── */
function showUI(s){wrap.classList.toggle('hidden',!s); icon.setAttribute('aria-expanded',s); if(s){window.scrollTo(0,0); bar.focus(); bar.select();} else {listOut.classList.add('hidden'); [...list.children].forEach(li=>li.classList.remove('focus'));}}
function blur(){const t=document.createElement('input'); t.style.cssText='position:absolute;opacity:0;'; icon.appendChild(t); t.focus(); t.remove();}
icon.addEventListener('click',()=>showUI(wrap.classList.contains('hidden')));
document.addEventListener('keydown',e=>{
if(e.altKey||e.ctrlKey||e.metaKey||e.shiftKey) return;
const f=/^(?:input|select|textarea)$/i.test(e.target.nodeName);
if(e.keyCode===HOT && !f){e.preventDefault(); showUI(true);} else if(e.keyCode===ESC){e.preventDefault(); showUI(false); blur();}
else if(e.keyCode===DOWN && document.activeElement===bar){e.preventDefault(); const first=list.firstElementChild; if(first){blur(); first.classList.add('focus');}}
else if([DOWN,UP,ENTER].includes(e.keyCode) && document.activeElement!==bar){const cur=list.querySelector('li.focus'); if(!cur) return; e.preventDefault(); if(e.keyCode===DOWN){const nxt=cur.nextElementSibling; if(nxt){cur.classList.remove('focus'); nxt.classList.add('focus');}} else if(e.keyCode===UP){const prv=cur.previousElementSibling; cur.classList.remove('focus'); if(prv){prv.classList.add('focus');} else {bar.focus();}} else {const a=cur.querySelector('a'); if(a) window.location.assign(a.href);}}
});
bar.addEventListener('input',e=>{ clearTimeout(debounce); debounce=setTimeout(()=>worker.postMessage({query: e.target.value.trim()}),120); });
/* ───────────── worker messages ───────────── */
worker.onmessage = ({data}) => {
if(data && data.ready!==undefined){
if(data.ready){
icon.innerHTML=READY_ICON;
icon.setAttribute('aria-label','Open search (S)');
icon.removeAttribute('title');
}
else {
icon.textContent='❌';
icon.setAttribute('aria-label','Search unavailable');
icon.setAttribute('title','Search is unavailable');
}
return;
}
const docs=data, q=bar.value.trim(), terms=q.split(/\s+/).filter(Boolean);
header.textContent=metric(docs.length,q);
clear(list);
docs.forEach(d=>{const li=document.createElement('li'); li.innerHTML=format(d,terms); list.appendChild(li);});
listOut.classList.toggle('hidden',!docs.length);
};
})();