> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…
feat(web): la franja es la misma de gioser.net y tawasuyu.net, y el enlace compartido trae tarjeta
- franja.js: copia de la de gioser-web (el símbolo que sube, las pestañas que se
vuelven menú, deslizar entre secciones); el index sólo dice qué sección mostrar
- elegir una sección pide /visto?t=<sección>, sin cookies: lo cuenta el registro
propio de Caddy
- og:image 1200x630 y twitter:card grande; la descripción decía 941 recetas y
926 selladas, hoy son 954 y todas selladas
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
diff --git a/web/takana-os.net/franja.js b/web/takana-os.net/franja.js
new file mode 100644
index 00000000..5a64d1d2
--- /dev/null
+++ b/web/takana-os.net/franja.js
@@ -0,0 +1,223 @@
+/* franja.js — la franja de los sitios de gioser: gioser.net, tawasuyu.net y takana-os.net.
+ *
+ * UNA sola fuente. Vive en gioser-web (`franja.js`) y se copia TAL CUAL a tawasuyu
+ * (`web/tawasuyu-web/franja.js`) y a takana (`web/takana-os.net/franja.js`): esos dos sitios no le
+ * piden nada a terceros, así que no la pueden pedir a gioser.net. `scripts/franja-copias.sh`
+ * compara las tres y dice cuál se separó. Antes eran tres copias escritas a mano, y un mismo
+ * defecto —el enlace al inicio que le robaba el clic a la primera pestaña— hubo que arreglarlo
+ * tres veces, una de ellas de otra manera (14-oct).
+ *
+ * Lo que hace, igual en los tres:
+ * · el símbolo sube desde la portada con el primer scroll y se planta en la franja;
+ * · la franja se pinta clara sobre la portada y con el color del texto sobre el cuerpo;
+ * · lo que se pasa por debajo de la franja (la tarjeta de la portada) se apaga antes de tocarla;
+ * · cuando las pestañas llegan arriba se encogen y se acomodan en la franja: el menú son ellas;
+ * · se pasa de una pestaña a otra deslizando, como un PageView de Flutter (el panel sigue al
+ * dedo, entra el vecino desde ese lado, rebota en el borde; lo que se desplaza de costado
+ * manda hasta su borde; el gesto se reclama para que el navegador no lo tome como «volver»);
+ * · elegir una pestaña a mano manda una señal mínima a `/visto?t=<pestaña>` (sin cookies ni
+ * identificadores: el `#` de la dirección nunca llega al servidor, y sin esto no hay forma de
+ * saber qué se lee). Caddy contesta 204 y lo anota en su registro.
+ *
+ * Lo que es de cada sitio entra por las opciones: qué elementos, cómo se elige una pestaña (en
+ * tawasuyu hay que traer el catálogo primero), qué panel está a la vista.
+ */
+(function () {
+ 'use strict';
+
+ var suave = function (t) { return t * t * (3 - 2 * t); };
+ var RAMPA = 90; // px de scroll en que las pestañas se vuelven menú
+
+ function Franja(o) {
+ var quieto = matchMedia('(prefers-reduced-motion: reduce)').matches;
+ var sc = o.scroller || null; // el que se desplaza; null = la ventana
+ var pos = function () { return sc ? sc.scrollTop : scrollY; };
+ var ir = function (top, suavecito) {
+ var op = { top: top, behavior: (suavecito && !quieto) ? 'smooth' : 'auto' };
+ if (sc) sc.scrollTo(op); else scrollTo(op);
+ };
+ var franja = o.franja, marca = o.marca, grande = o.grande, glifos = o.glifos;
+ var barra = o.barra, tabs = o.tabs, apagar = o.apagar || [];
+ var esc = o.escala || [0.55, 0.92], margen = o.margen || 104;
+ var geo = null, menu = null, p = 0;
+ document.documentElement.classList.add('js');
+
+ function medir() {
+ var s = pos(), alto = franja.offsetHeight;
+ if (marca && grande) {
+ marca.style.transform = 'none';
+ var h = grande.getBoundingClientRect(), b = marca.getBoundingClientRect();
+ geo = { hx: h.left, hy: h.top + s, hw: h.width, bx: b.left, by: b.top, bw: b.width };
+ }
+ barra.style.setProperty('--p', 0);
+ tabs.forEach(function (t) { t.style.transform = 'none'; });
+ var top = alto - barra.offsetHeight; // pegada, asoma sólo su franja de abajo
+ barra.style.setProperty(o.varTop, top + 'px');
+ // un sticky reporta su posición PEGADA: el lugar natural de la barra lo dice el sitio
+ var docTop = s + o.inicioBarra();
+ var br = barra.getBoundingClientRect();
+ var rot = tabs.map(function (t) {
+ var l = t.querySelector(o.rotulo).getBoundingClientRect(), b = t.getBoundingClientRect();
+ return { cx: l.left + l.width / 2, w: l.width, cy: l.top + l.height / 2 - br.top,
+ ox: l.left + l.width / 2 - b.left, oy: l.top + l.height / 2 - b.top };
+ });
+ // dónde caben en la franja: entre el símbolo (con su palabra) y los glifos
+ var izq = (marca ? marca.getBoundingClientRect().right : 0) + (innerWidth < 720 ? 14 : margen);
+ var der = (glifos ? glifos.getBoundingClientRect().left : innerWidth) - 14;
+ var gap = innerWidth < 720 ? 12 : 22;
+ var suma = rot.reduce(function (a, l) { return a + l.w; }, 0);
+ var k = Math.max(esc[0], Math.min(esc[1], (der - izq - gap * (rot.length - 1)) / suma));
+ var total = suma * k + gap * (rot.length - 1);
+ var x0 = innerWidth / 2 - total / 2;
+ if (x0 < izq) x0 = izq;
+ if (x0 + total > der) x0 = Math.max(izq, der - total);
+ var x = x0;
+ rot.forEach(function (l) { l.dx = x + l.w * k / 2 - l.cx; x += l.w * k + gap; });
+ menu = { alto: alto, top: top, pegado: docTop - top, rot: rot, k: k };
+ tabs.forEach(function (t, i) { t.style.transformOrigin = rot[i].ox + 'px ' + rot[i].oy + 'px'; });
+ }
+
+ function volar(s) {
+ if (!geo) return;
+ // el símbolo sube con la portada hasta la altura de la franja, y en ese trayecto se
+ // corre a su lugar y se achica
+ var t = Math.min(1, Math.max(0, s / Math.max(1, geo.hy - geo.by))), e = suave(t);
+ var x = geo.hx + (geo.bx - geo.hx) * e, y = Math.max(geo.by, geo.hy - s);
+ var k = geo.hw / geo.bw + (1 - geo.hw / geo.bw) * e;
+ marca.style.transform = 'translate(' + (x - geo.bx).toFixed(1) + 'px, ' + (y - geo.by).toFixed(1) +
+ 'px) scale(' + k.toFixed(3) + ')';
+ franja.classList.toggle('llego', t > 0.98);
+ }
+
+ function encoger(s) {
+ if (!menu) return;
+ var t = Math.min(1, Math.max(0, (s - (menu.pegado - RAMPA)) / RAMPA));
+ p = suave(t);
+ barra.style.setProperty('--p', p.toFixed(3));
+ var falta = Math.max(0, menu.pegado - s);
+ tabs.forEach(function (tab, i) {
+ var l = menu.rot[i], dy = menu.alto / 2 - (menu.top + l.cy + falta), k = 1 - (1 - menu.k) * p;
+ tab.style.transform = p === 0 ? 'none'
+ : 'translate(' + (l.dx * p).toFixed(1) + 'px, ' + (dy * p).toFixed(1) + 'px) scale(' + k.toFixed(3) + ')';
+ });
+ franja.classList.toggle('con-menu', p > 0.5);
+ }
+
+ var tick = false;
+ function pintar() {
+ tick = false;
+ var s = pos(), alto = franja.offsetHeight;
+ volar(s); encoger(s);
+ apagar.forEach(function (el) {
+ var tp = el.getBoundingClientRect().top;
+ el.style.opacity = Math.min(1, Math.max(0, (tp - alto) / (alto * 1.5))).toFixed(3);
+ });
+ if (o.cuerpo) franja.classList.toggle('sobre-portada', o.cuerpo.getBoundingClientRect().top > alto);
+ }
+ (sc || window).addEventListener('scroll', function () {
+ if (!tick) { tick = true; requestAnimationFrame(pintar); }
+ }, { passive: true });
+ addEventListener('resize', function () { medir(); pintar(); }, { passive: true });
+
+ function irALista() { if (menu) ir(Math.round(menu.pegado), true); }
+
+ // ── elegir: lo de cada sitio, más la señal de lectura ──
+ var indice = function () {
+ return tabs.findIndex(function (t) { return t.getAttribute('aria-selected') === 'true'; });
+ };
+ function senal(id) {
+ if (!o.senal || !navigator.sendBeacon) return;
+ try { navigator.sendBeacon('/visto?t=' + encodeURIComponent(id)); } catch (e) { /* sin señal */ }
+ }
+ function elegir(id, como) { // como: {foco, comoMenu, desde: 'der'|'izq'}
+ como = como || {};
+ return Promise.resolve(o.elegir(id)).then(function () {
+ senal(id);
+ if (como.foco) { var t = tabs.find(function (x) { return o.id(x) === id; }); if (t) t.focus(); }
+ if (como.desde) {
+ var nuevo = o.panel();
+ if (nuevo) {
+ nuevo.classList.remove('desde-der', 'desde-izq'); void nuevo.offsetWidth;
+ nuevo.classList.add('desde-' + como.desde);
+ nuevo.addEventListener('animationend', function () { nuevo.classList.remove('desde-der', 'desde-izq'); }, { once: true });
+ }
+ }
+ if (como.comoMenu) irALista();
+ });
+ }
+
+ tabs.forEach(function (t, i) {
+ t.addEventListener('click', function () { elegir(o.id(t), { comoMenu: p > 0.5 }); });
+ t.addEventListener('keydown', function (e) {
+ var n = tabs.length;
+ var j = e.key === 'ArrowRight' ? (i + 1) % n : e.key === 'ArrowLeft' ? (i - 1 + n) % n
+ : e.key === 'Home' ? 0 : e.key === 'End' ? n - 1 : -1;
+ if (j >= 0) { e.preventDefault(); elegir(o.id(tabs[j]), { foco: true }); }
+ });
+ });
+ if (o.inicio) o.inicio.addEventListener('click', function (e) { e.preventDefault(); ir(0, true); });
+
+ // ── deslizar entre pestañas (como un PageView) ──
+ var zona = o.zonaGesto;
+ if (zona) {
+ var vecino = function (dir) { var j = indice() + dir; return (j >= 0 && j < tabs.length) ? o.id(tabs[j]) : null; };
+ var corre = function (el) {
+ for (; el && el !== zona; el = el.parentElement) {
+ var ox = getComputedStyle(el).overflowX;
+ if ((ox === 'auto' || ox === 'scroll') && el.scrollWidth > el.clientWidth + 2) return el;
+ }
+ return null;
+ };
+ var x0 = null, y0 = 0, dx = 0, eje = null, panel = null, rueda = null;
+ zona.addEventListener('touchstart', function (e) {
+ if (e.touches.length !== 1) { x0 = null; return; }
+ rueda = corre(e.target);
+ x0 = e.touches[0].clientX; y0 = e.touches[0].clientY; dx = 0; eje = null; panel = o.panel();
+ }, { passive: true });
+ zona.addEventListener('touchmove', function (e) {
+ if (x0 === null || !panel) return;
+ var mx = e.touches[0].clientX - x0, my = e.touches[0].clientY - y0;
+ if (!eje) {
+ if (Math.abs(mx) < 10 && Math.abs(my) < 10) return;
+ eje = Math.abs(mx) > Math.abs(my) * 1.3 ? 'x' : 'y';
+ // como un PageView anidado: el que se desplaza de costado manda, salvo que esté en su borde
+ if (eje === 'x' && rueda) {
+ var finD = rueda.scrollLeft + rueda.clientWidth >= rueda.scrollWidth - 2, finI = rueda.scrollLeft <= 2;
+ if (!(mx < 0 ? finD : finI)) eje = 'rueda';
+ }
+ }
+ if (eje !== 'x') return;
+ // el gesto horizontal es de la página: sin esto el navegador lo toma como «volver atrás»
+ // (en gioser.net el que se desplaza es un `.layer` fijo, donde overscroll-behavior no alcanza)
+ if (e.cancelable) e.preventDefault();
+ dx = vecino(mx < 0 ? 1 : -1) ? mx : mx * 0.25; // en el borde resiste, como el rebote
+ panel.style.transition = 'none';
+ panel.style.transform = 'translateX(' + (dx * 0.6).toFixed(1) + 'px)';
+ panel.style.opacity = (1 - Math.min(0.45, Math.abs(dx) / innerWidth)).toFixed(3);
+ }, { passive: false });
+ zona.addEventListener('touchend', function () {
+ if (x0 === null || !panel) return;
+ x0 = null;
+ var p0 = panel, d = dx, dir = d < 0 ? 1 : -1, id = vecino(dir);
+ p0.style.transition = 'transform .2s ease, opacity .2s ease';
+ p0.style.transform = ''; p0.style.opacity = '';
+ setTimeout(function () { p0.style.transition = ''; }, 220);
+ if (eje !== 'x' || !id || Math.abs(d) < Math.min(90, innerWidth * 0.22)) return;
+ elegir(id, { desde: dir > 0 ? 'der' : 'izq', comoMenu: p > 0.5 });
+ });
+ }
+
+ medir(); pintar();
+ // las fuentes y la portada llegan tarde y mueven las medidas (la primera vez trastabillaba)
+ if (document.fonts && document.fonts.ready) document.fonts.ready.then(function () { medir(); pintar(); });
+ addEventListener('load', function () { medir(); pintar(); });
+
+ return {
+ medir: medir, pintar: pintar, irALista: irALista,
+ p: function () { return p; },
+ elegir: function (id, como) { return elegir(id, como); }
+ };
+ }
+
+ window.Franja = Franja;
+})();
diff --git a/web/takana-os.net/index.html b/web/takana-os.net/index.html
index cfb0f90e..e7b31eb7 100644
--- a/web/takana-os.net/index.html
+++ b/web/takana-os.net/index.html
@@ -9,9 +9,13 @@
<link rel="icon" href="simbolo.svg" type="image/svg+xml">
<link rel="stylesheet" href="fuentes.css">
<meta property="og:title" content="takana — la forja determinista">
-<meta property="og:description" content="Un laboratorio hermético abajo, un Linux clásico y mutable arriba. 941 recetas, 926 selladas.">
+<meta property="og:description" content="Un laboratorio hermético abajo, un Linux clásico y mutable arriba. 954 recetas, todas selladas.">
<meta property="og:url" content="https://takana-os.net/">
<meta property="og:type" content="website">
+<meta property="og:image" content="https://takana-os.net/tarjeta.jpg">
+<meta property="og:image:width" content="1200">
+<meta property="og:image:height" content="630">
+<meta name="twitter:card" content="summary_large_image">
<style>
/* ── paleta oficial (docs/marca/README.md) ─────────────────────────────────────
Obsidiana #17130E · Piedra #2A241B · Alpaca #F2EBDD
@@ -951,126 +955,50 @@ html.js .t-panel.activo.desde-izq{animation:t-desde-izq .32s cubic-bezier(.2,.8,
</script>
+<script src="franja.js"></script>
<script>
-/* ── la franja y las pestañas (2026-10-13) ──────────────────────────────────────────────────────
- El símbolo sube desde la portada con el primer scroll y se planta en la franja; cuando las
- pestañas de las secciones llegan arriba, se encogen y se acomodan en ella: el menú son ellas.
- Elegir una muestra su sección; sin JS se ven las cuatro, una abajo de otra. */
+/* ── las pestañas de las cuatro secciones ───────────────────────────────────────────────────────
+ La franja —el símbolo que sube desde la portada, las pestañas que se vuelven el menú, el gesto de
+ deslizar entre secciones y la señal de lectura— es `franja.js`, la misma de gioser.net y
+ tawasuyu.net. Es una COPIA de la de gioser-web (allí `scripts/franja-copias.sh` compara las
+ tres): este sitio no le pide nada a terceros. Acá queda lo de esta página: mostrar la sección
+ elegida, el `#` de la dirección y los enlaces internos. Sin JS se ven las cuatro. */
(function(){
- var r=document.documentElement, quieto=matchMedia('(prefers-reduced-motion: reduce)').matches;
- r.classList.add('js');
- var franja=document.getElementById('t-franja'), marca=document.querySelector('#t-marca .simbolo'),
- grande=document.querySelector('.t-tarjeta .titular .simbolo'), glifos=document.getElementById('t-glifos'),
- cuerpo=document.querySelector('.t-cuerpo'), barra=document.getElementById('t-tabs'),
- tabs=[].slice.call(document.querySelectorAll('.t-tab')), tarjeta=document.querySelector('.t-tarjeta');
- if(!franja||!barra||!cuerpo) return;
- var suave=function(t){return t*t*(3-2*t)}, RAMPA=90, geo=null, menu=null, p=0;
-
- function medir(){
- var s=scrollY, alto=franja.offsetHeight;
- if(marca&&grande){ marca.style.transform='none';
- var h=grande.getBoundingClientRect(), b=marca.getBoundingClientRect();
- geo={hx:h.left,hy:h.top+s,hw:h.width,bx:b.left,by:b.top,bw:b.width}; }
- barra.style.setProperty('--p',0); tabs.forEach(function(t){t.style.transform='none'});
- var top=alto-barra.offsetHeight; barra.style.setProperty('--t-tabs-top',top+'px');
- // las pestañas son lo primero del cuerpo (el fondo vivo no ocupa lugar); un sticky reporta su
- // posición pegada, así que se mide desde el cuerpo
- var docTop=s+cuerpo.getBoundingClientRect().top, br=barra.getBoundingClientRect();
- var rot=tabs.map(function(t){ var l=t.querySelector('.rot').getBoundingClientRect(), b=t.getBoundingClientRect();
- return {cx:l.left+l.width/2,w:l.width,cy:l.top+l.height/2-br.top,ox:l.left+l.width/2-b.left,oy:l.top+l.height/2-b.top}; });
- var izq=(marca?marca.getBoundingClientRect().right:0)+(innerWidth<720?14:110),
- der=(glifos?glifos.getBoundingClientRect().left:innerWidth)-14, gap=innerWidth<720?12:22,
- suma=rot.reduce(function(a,l){return a+l.w},0),
- k=Math.max(.55,Math.min(.92,(der-izq-gap*(rot.length-1))/suma)), total=suma*k+gap*(rot.length-1),
- x0=innerWidth/2-total/2;
- if(x0<izq) x0=izq; if(x0+total>der) x0=Math.max(izq,der-total);
- var x=x0; rot.forEach(function(l){ l.dx=x+l.w*k/2-l.cx; x+=l.w*k+gap; });
- menu={alto:alto,top:top,pegado:docTop-top,rot:rot,k:k};
- tabs.forEach(function(t,i){ t.style.transformOrigin=rot[i].ox+'px '+rot[i].oy+'px'; });
- }
- function volar(s){ if(!geo) return;
- var t=Math.min(1,Math.max(0,s/Math.max(1,geo.hy-geo.by))), e=suave(t),
- x=geo.hx+(geo.bx-geo.hx)*e, y=Math.max(geo.by,geo.hy-s), k=geo.hw/geo.bw+(1-geo.hw/geo.bw)*e;
- marca.style.transform='translate('+(x-geo.bx).toFixed(1)+'px,'+(y-geo.by).toFixed(1)+'px) scale('+k.toFixed(3)+')';
- franja.classList.toggle('llego',t>.98); }
- function encoger(s){ if(!menu) return;
- var t=Math.min(1,Math.max(0,(s-(menu.pegado-RAMPA))/RAMPA)); p=suave(t);
- barra.style.setProperty('--p',p.toFixed(3));
- var falta=Math.max(0,menu.pegado-s);
- tabs.forEach(function(tab,i){ var l=menu.rot[i], dy=menu.alto/2-(menu.top+l.cy+falta), k=1-(1-menu.k)*p;
- tab.style.transform = p===0 ? 'none' : 'translate('+(l.dx*p).toFixed(1)+'px,'+(dy*p).toFixed(1)+'px) scale('+k.toFixed(3)+')'; });
- franja.classList.toggle('con-menu',p>.5); }
- var tick=false;
- function pintar(){ tick=false; var s=scrollY, alto=franja.offsetHeight; volar(s); encoger(s);
- if(tarjeta){ var tp=tarjeta.getBoundingClientRect().top; tarjeta.style.opacity=Math.min(1,Math.max(0,(tp-alto)/(alto*1.5))).toFixed(3); }
- franja.classList.toggle('sobre-portada', cuerpo.getBoundingClientRect().top>alto); }
- addEventListener('scroll',function(){ if(!tick){tick=true;requestAnimationFrame(pintar)} },{passive:true});
- addEventListener('resize',function(){medir();pintar()},{passive:true});
- medir(); pintar();
- if(document.fonts&&document.fonts.ready) document.fonts.ready.then(function(){medir();pintar()});
- // y otra vez al terminar de cargar todo: la primera vez trastabillaba porque se medía antes
- addEventListener('load',function(){medir();pintar()});
- document.getElementById('t-marca').addEventListener('click',function(e){ e.preventDefault(); scrollTo({top:0,behavior:quieto?'auto':'smooth'}); });
-
- function elegir(id, foco){
+ var cuerpo=document.querySelector('.t-cuerpo'), barra=document.getElementById('t-tabs');
+ var tabs=[].slice.call(document.querySelectorAll('.t-tab'));
+ if(!cuerpo||!barra||!window.Franja) return;
+ function elegir(id){
// sin un id que sea de una pestaña no se toca nada (al cargar sin ancla, «Descargar» queda)
if(!tabs.some(function(t){return t.dataset.tab===id})) return false;
- var hay=false;
- tabs.forEach(function(t){ var on=t.dataset.tab===id; if(on) hay=true;
+ tabs.forEach(function(t){ var on=t.dataset.tab===id;
t.setAttribute('aria-selected',String(on)); t.tabIndex=on?0:-1;
- var panel=document.getElementById(t.dataset.tab); if(panel) panel.classList.toggle('activo',on);
- if(on&&foco) t.focus(); });
- if(hay) try{ history.replaceState(null,'','#'+id); }catch(e){}
- return hay;
+ var panel=document.getElementById(t.dataset.tab); if(panel) panel.classList.toggle('activo',on); });
+ try{ history.replaceState(null,'','#'+id); }catch(e){}
+ return true;
}
- tabs.forEach(function(t,i){
- t.tabIndex = t.getAttribute('aria-selected')==='true' ? 0 : -1;
- t.addEventListener('click',function(){ var comoMenu=p>.5; elegir(t.dataset.tab);
- if(comoMenu&&menu) scrollTo({top:Math.round(menu.pegado),behavior:quieto?'auto':'smooth'}); });
- t.addEventListener('keydown',function(e){ var n=tabs.length,
- j=e.key==='ArrowRight'?(i+1)%n:e.key==='ArrowLeft'?(i-1+n)%n:e.key==='Home'?0:e.key==='End'?n-1:-1;
- if(j>=0){ e.preventDefault(); elegir(tabs[j].dataset.tab,true); } });
+ tabs.forEach(function(t){ t.tabIndex = t.getAttribute('aria-selected')==='true' ? 0 : -1; });
+ var franja=window.Franja({
+ franja:document.getElementById('t-franja'),
+ marca:document.querySelector('#t-marca .simbolo'),
+ grande:document.querySelector('.t-tarjeta .titular .simbolo'),
+ glifos:document.getElementById('t-glifos'),
+ cuerpo:cuerpo,
+ inicio:document.getElementById('t-marca'),
+ barra:barra, tabs:tabs, rotulo:'.rot', varTop:'--t-tabs-top', margen:110,
+ apagar:[document.querySelector('.t-tarjeta')],
+ inicioBarra:function(){ return cuerpo.getBoundingClientRect().top; },
+ zonaGesto:cuerpo, senal:true,
+ id:function(t){ return t.dataset.tab; },
+ panel:function(){ return document.querySelector('.t-panel.activo'); },
+ elegir:elegir
});
-
- // ── deslizar entre pestañas (como un PageView de Flutter) ──
- function vecino(dir){ var i=tabs.findIndex(function(t){return t.getAttribute('aria-selected')==='true'}), j=i+dir;
- return (j>=0&&j<tabs.length) ? tabs[j].dataset.tab : null; }
- var x0=null, y0=0, dx=0, eje=null, panel=null, rueda=null;
- // lo que bajo el dedo se desplaza de costado (un diagrama, una tabla ancha, un bloque de código)
- function corre(el){ for(;el&&el!==cuerpo;el=el.parentElement){ var o=getComputedStyle(el).overflowX;
- if((o==='auto'||o==='scroll')&&el.scrollWidth>el.clientWidth+2) return el; } return null; }
- cuerpo.addEventListener('touchstart',function(e){ if(e.touches.length!==1){x0=null;return;}
- rueda=corre(e.target);
- x0=e.touches[0].clientX; y0=e.touches[0].clientY; dx=0; eje=null; panel=document.querySelector('.t-panel.activo'); },{passive:true});
- cuerpo.addEventListener('touchmove',function(e){ if(x0===null||!panel) return;
- var mx=e.touches[0].clientX-x0, my=e.touches[0].clientY-y0;
- if(!eje){ if(Math.abs(mx)<10&&Math.abs(my)<10) return; eje=Math.abs(mx)>Math.abs(my)*1.3?'x':'y';
- // como un PageView anidado: el que se desplaza de costado manda, salvo que ya esté en su borde
- if(eje==='x'&&rueda){ var finD=rueda.scrollLeft+rueda.clientWidth>=rueda.scrollWidth-2, finI=rueda.scrollLeft<=2;
- if(!(mx<0?finD:finI)) eje='rueda'; } }
- if(eje!=='x') return;
- if(e.cancelable) e.preventDefault(); // el gesto horizontal es de la página, no «volver atrás»
- dx = vecino(mx<0?1:-1) ? mx : mx*.25; // en el borde resiste, como el rebote
- panel.style.transition='none'; panel.style.transform='translateX('+(dx*.6).toFixed(1)+'px)';
- panel.style.opacity=(1-Math.min(.45,Math.abs(dx)/innerWidth)).toFixed(3); },{passive:false});
- cuerpo.addEventListener('touchend',function(){ if(x0===null||!panel) return; x0=null;
- var p0=panel, d=dx, dir=d<0?1:-1, id=vecino(dir);
- p0.style.transition='transform .2s ease,opacity .2s ease'; p0.style.transform=''; p0.style.opacity='';
- setTimeout(function(){p0.style.transition=''},220);
- if(eje!=='x'||!id||Math.abs(d)<Math.min(90,innerWidth*.22)) return;
- elegir(id);
- var nuevo=document.getElementById(id); nuevo.classList.remove('desde-der','desde-izq'); void nuevo.offsetWidth;
- nuevo.classList.add(dir>0?'desde-der':'desde-izq');
- nuevo.addEventListener('animationend',function(){nuevo.classList.remove('desde-der','desde-izq')},{once:true});
- if(p>.5&&menu) scrollTo({top:Math.round(menu.pegado),behavior:quieto?'auto':'smooth'}); });
// un enlace con ancla a una sección (#codigo, #descargar…) abre esa pestaña
var pedido=decodeURIComponent((location.hash||'').slice(1));
- if(elegir(pedido)&&menu) scrollTo({top:Math.round(menu.pegado)});
- addEventListener('hashchange',function(){ var id=decodeURIComponent((location.hash||'').slice(1));
- if(elegir(id)&&menu) scrollTo({top:Math.round(menu.pegado),behavior:quieto?'auto':'smooth'}); });
+ if(elegir(pedido)) franja.irALista();
+ addEventListener('hashchange',function(){ if(elegir(decodeURIComponent((location.hash||'').slice(1)))) franja.irALista(); });
document.addEventListener('click',function(e){ var a=e.target.closest('a[href^="#"]'); if(!a) return;
var id=a.getAttribute('href').slice(1); if(tabs.some(function(t){return t.dataset.tab===id})){
- e.preventDefault(); elegir(id); if(menu) scrollTo({top:Math.round(menu.pegado),behavior:quieto?'auto':'smooth'}); } });
+ e.preventDefault(); elegir(id); franja.irALista(); } });
})();
</script>
</body>
diff --git a/web/takana-os.net/tarjeta.jpg b/web/takana-os.net/tarjeta.jpg
new file mode 100644
index 00000000..22b7c1ca
Binary files /dev/null and b/web/takana-os.net/tarjeta.jpg differ