/* ════════════════════════════════════════════════════
   main.css — las 5 "ventanas" de caleidociclero.
   Sobre los tokens de base.css (look de gestualfabeto).

   ESCRITORIO: todas visibles a la vez, en DOS FILAS. Ninguna ventana
   se desplaza: el contenido se dimensiona para caber.
                 50vw                    50vw
     ┌───────────────────────┬───────────────────────────┐
     │ 2 fuente              │ 3 desarrollo              │  38vh
     ├─────────────┬─────────────────────┬───────────────┤
     │ 1 info      │ 4 visor 3D          │ 5 export      │  62vh
     └─────────────┴─────────────────────┴───────────────┘
         30vw               50vw                  20vw
   Las divisiones VERTICALES no son rectas: son picos ">" que además
   de separar dicen hacia dónde va el gesto (lo de la izquierda se
   arrastra a la derecha). Ver .cc-win--fuente.

   Los tamaños de arranque son los pedidos, pero viven en variables
   CSS que los tiradores (.cc-grip) reescriben al arrastrar — el
   contenido se reajusta en vivo porque los canvas escuchan un
   ResizeObserver (ver app.js).

   TABLET (≤1100px) y TELÉFONO (≤700px): ver el bloque de consultas
   de medios al final. El corte no es "escritorio o móvil": lo que
   manda es que el desarrollo necesita ANCHO.
════════════════════════════════════════════════════ */

:root {
  /* En unidades ABSOLUTAS (vw/vh), no en %: los tiradores las usan dentro de
     calc() posicionados contra el viewport, y un % se resolvería contra un
     contenedor distinto. */
  --alto-fila1: 38vh;   /* alto de la fila de arriba */
  --col-sup-a:  50vw;   /* fila de arriba: ancho de la ventana 2 (fuente) */
  --col-inf-a:  30vw;   /* fila de abajo: ancho de la ventana 1 (info) */
  --col-inf-b:  50vw;   /* fila de abajo: ancho de la ventana 4 (visor 3D) */
                        /* lo que queda, 20vw, es la ventana 5 (exportar) */
  --grip: 6px;
  /* Cuánto se adentra el pico ">" de cada división. Es su ANCHO TOTAL: la
     línea va de (X − mitad) arriba, a (X + mitad) a media altura, y vuelve.
     Se calcula como una fracción del ALTO de la fila, no como un valor fijo,
     para que las dos filas —de alturas muy distintas— muestren el pico con
     el MISMO ángulo. Con un valor fijo, el de la fila alta se vería casi
     recto. El clamp evita que en pantallas muy altas el pico se coma
     demasiado ancho de las columnas. */
  --chevron-sup: clamp(20px, calc(var(--alto-fila1) * 0.11), 60px);
  --chevron-inf: clamp(20px, calc((100vh - var(--alto-fila1)) * 0.11), 60px);
}

html, body { height: 100%; }
body { overflow: hidden; }

/* Sin JavaScript no hay nada que mostrar: la página entera se calcula en el
   navegador. Al menos que se explique, en vez de dejar un vacío. */
.cc-sinjs {
  max-width: 34rem; margin: 12vh auto; padding: 0 var(--gap-m);
  color: var(--texto-tenue); font-size: .86rem; line-height: 1.6;
}
.cc-sinjs h1 { color: var(--acento); font-size: 1.1rem; letter-spacing: .08em; text-transform: uppercase; }
.cc-sinjs strong { color: var(--texto); }
/* El preload taparía el mensaje: sin JS nadie lo va a quitar. */
.cc-sinjs ~ .cc-carga, body:has(.cc-sinjs) .cc-carga { display: none; }

/* ── Pantalla de carga ────────────────────────────────────────────────
   El hexágono del caleidociclo visto por su eje, partido en seis
   triángulos —uno por tetraedro— que se llenan en secuencia mientras el
   conjunto gira. Sirve igual en las tres anchuras: el tamaño va en vmin,
   así que se adapta al lado corto de la pantalla sea cual sea.
   Todo en CSS: no depende de que ningún módulo haya cargado, que es
   justamente lo que está esperando. */
.cc-carga {
  position: fixed; inset: 0; z-index: 200;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 20px;
  background: var(--bg);
  transition: opacity .45s ease;
}
.cc-carga svg { width: clamp(96px, 26vmin, 190px); height: auto; display: block; }
.cc-carga__txt {
  margin: 0;
  font-size: .62rem; letter-spacing: .22em; text-transform: uppercase;
  color: var(--texto-tenue);
}
/* El punto de giro es el centro del hexágono en coordenadas del viewBox. */
.cc-carga__gira {
  transform-origin: 60px 60px;
  animation: cc-carga-girar 7s linear infinite;
}
.cc-carga__linea {
  fill: none; stroke: var(--acento); stroke-width: 1.6;
  stroke-linejoin: round; stroke-linecap: round;
  /* el trazo no engorda al escalar el SVG en pantallas grandes */
  vector-effect: non-scaling-stroke;
  opacity: .75;
}
/* --i lo pone el HTML por triángulo: es su turno en la secuencia. */
.cc-carga__cara {
  fill: var(--acento);
  fill-opacity: 0;
  animation: cc-carga-llenar 2.4s ease-in-out infinite;
  animation-delay: calc(var(--i) * .17s);
}
@keyframes cc-carga-girar { to { transform: rotate(360deg); } }
@keyframes cc-carga-llenar {
  0%, 100% { fill-opacity: 0; }
  40%, 62% { fill-opacity: .5; }
}
/* Se va con una transición y deja de estorbar; el nodo se retira después. */
.cc-carga--fuera { opacity: 0; pointer-events: none; }

/* El giro es lo que puede molestar a quien pide menos movimiento; el
   relleno por turnos es suave y se conserva para que siga leyéndose que
   algo está pasando. */
@media (prefers-reduced-motion: reduce) {
  .cc-carga__gira { animation: none; }
  .cc-carga__cara { animation-duration: 3.4s; }
}

/* Flex anidado (no grid): las dos filas tienen distinta cantidad de columnas
   y con grid habría que declarar tracks que no se corresponden entre filas.
   Con flex, cada tirador sólo reescribe una variable de tamaño. */
.cc-app {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100svh;
  position: relative;
}
.cc-fila { display: flex; min-width: 0; min-height: 0; }
.cc-fila--sup { height: var(--alto-fila1); flex-shrink: 0; }
.cc-fila--inf { flex: 1; }

.cc-win {
  position: relative;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  border: 1px solid var(--borde);
  margin: -0.5px;   /* los bordes vecinos se solapan: una sola línea entre ventanas */
  display: flex;
  flex-direction: column;
}
.cc-win__title {
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--texto-tenue);
  margin: 0 0 2px;
}

/* ── 1 · info (fila de abajo, primera columna) ────── */
/* Lleva la explicación completa que antes estaba escondida en un modal.
   Sin desplazamiento: los textos van a su tamaño y el diagrama absorbe lo
   que sobre (o se achica si falta). */
.cc-win--info {
  width: calc(var(--col-inf-a) + var(--chevron-inf) / 2);
  flex-shrink: 0;
  overflow: hidden;
  clip-path: polygon(
    0 0,
    calc(100% - var(--chevron-inf)) 0,
    100% 50%,
    calc(100% - var(--chevron-inf)) 100%,
    0 100%);
}
/* En escritorio, invisible para el layout: sus hijos quedan como si
   estuvieran sueltos dentro de .cc-win--info (que es quien pone flex).
   En móvil/tablet se convierte en la tarjeta del modal — ver esa regla en
   el media query de abajo. */
.cc-panel__tarjeta { display: contents; }
.cc-info__cuerpo {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: var(--gap-s);
  padding: var(--gap-m);
  padding-right: calc(var(--gap-m) + var(--chevron-inf));
  overflow: hidden;
}
/* Sin scroll: el texto va a su tamaño natural y el diagrama se queda con lo
   que sobre. Sin el flex-shrink:0, el reparto lo decidiría flex encogiendo
   los textos. */
.cc-info__cuerpo > :not(.cc-info__diagrama) { flex-shrink: 0; }
/* Manda el ANCHO —90% de la columna—, pero con un techo de alto para que no
   se coma el sitio del diagrama, que es lo que absorbe lo que sobre.
   El techo se aplica sobre el ancho y no como max-height: el logo es
   578,2 × 201,33 (2,872:1), así que "tanto alto como máximo" se traduce a
   "tanto ancho como máximo" multiplicando por esa proporción. Con max-height
   la caja seguiría midiendo el 90% y object-fit dejaría aire a los lados;
   así, en cambio, la imagen ocupa todo lo que ocupa su caja. */
.cc-logo {
  --logo-prop: 2.872;
  --logo-alto-max: clamp(44px, 15vh, 150px);
  width: min(90%, calc(var(--logo-alto-max) * var(--logo-prop)));
  height: auto;
  object-fit: contain; display: block;
  padding-bottom: var(--gap-s);
}
/* Envuelve al logo para que abra "Acerca de" (mismo destino que "+info",
   ver el listener extra en app.js): reseteado a plano, sin chrome de botón,
   así el logo adentro sigue midiendo exactamente lo mismo que medía suelto
   (el 90% de .cc-logo es relativo a ESTA caja, que por eso tiene que seguir
   siendo block/100% de ancho, igual que el img lo era antes). */
.cc-logo-boton {
  display: block;
  width: 100%;
  background: none; border: none; padding: 0; margin: 0;
  cursor: pointer;
  text-align: left;
}
.cc-logo-boton .cc-logo { transition: opacity .15s; }
.cc-logo-boton:hover .cc-logo,
.cc-logo-boton:focus-visible .cc-logo { opacity: .82; }
.cc-logo-boton:focus-visible { outline: 2px solid var(--acento); outline-offset: 3px; }
.cc-info__cuerpo p { margin: 0; font-size: .78rem; line-height: 1.5; color: var(--texto-tenue); }
.cc-info__cuerpo strong { color: var(--texto); font-weight: 600; }
/* margen 0: el espaciado ya lo pone el gap de la columna */
/* Se queda con el alto que sobre y se contiene dentro: es lo único elástico
   del panel, así que es lo que absorbe las diferencias de tamaño de ventana
   sin obligar a desplazar. */
.cc-info__diagrama {
  width: 100%; min-height: 0; display: block;
  object-fit: contain; object-position: left top;
  border-radius: var(--radio-sm);
  margin: 0;
}
.cc-info__pasos {
  margin: 0; padding-left: 1.1rem;
  font-size: .74rem; line-height: 1.5; color: var(--texto-tenue);
}
.cc-info__pasos li { margin-bottom: 3px; }

/* ── 2 · fuente (fila de arriba, primera columna) ─── */
/* ── El pico ">" entre esta ventana y el desarrollo ─────────────────
   No es una raya decorada: las dos ventanas se recortan con formas
   COMPLEMENTARIAS que encajan exacto, así que el pico divide de verdad.
   Dice, además, hacia dónde va el gesto: lo de la izquierda (las imágenes)
   se arrastra hacia la derecha (el desarrollo).

   Llamando X a --col-sup-a y c a --chevron, la frontera va de X−c/2 arriba,
   a X+c/2 a media altura, y vuelve a X−c/2 abajo. Las tres piezas —recorte
   izquierdo, recorte derecho y trazo del tirador— siguen esa misma curva. */
.cc-win--fuente {
  /* se estira media anchura de pico más allá de X, que es hasta donde llega
     la punta a media altura */
  width: calc(var(--col-sup-a) + var(--chevron-sup) / 2);
  flex-shrink: 0;
  padding: 0;
  overflow: hidden;
  clip-path: polygon(
    0 0,
    calc(100% - var(--chevron-sup)) 0,
    100% 50%,
    calc(100% - var(--chevron-sup)) 100%,
    0 100%);
}
/* El margen derecho deja el contenido del lado de acá de la frontera, para
   que el recorte nunca lo toque. Sin scroll: el mosaico de abajo se reparte
   el alto que quede. */
.cc-fuente__cuerpo {
  flex: 1;
  min-height: 0;
  margin-right: var(--chevron-sup);
  padding: var(--gap-m);
  display: flex;
  flex-direction: column;
  gap: var(--gap-s);
  overflow: hidden;
}
/* En escritorio el rótulo sobra: la ventana es lo único que hay ahí y su
   barra de ayudas ya dice qué hacer. Se conserva en el HTML porque en
   pantallas chicas esta misma ventana se abre como panel a pantalla
   completa, y ahí sí hace falta decir qué se abrió (ver la consulta de
   medios al final, que lo vuelve a mostrar). El alto que libera se lo
   queda el mosaico solo, porque ajustarMosaico() lo mide en vivo. */
.cc-fuente__cuerpo .cc-win__title { display: none; }

/* Las muestras se arrastran desde la galería hasta una fila del
   desarrollo (ventana 3); ya no hay ranuras intermedias. */
.cc-tile { -webkit-user-drag: element; }
.cc-tile[draggable="true"] { cursor: grab; }
.cc-tile.cc-tile--arrastrando { opacity: .45; }

/* El mosaico son dos piezas al lado: la zona de carga (2:3, del alto
   completo) y la rejilla de muestras.
   La zona de carga NO es una celda de la rejilla aunque lo parezca. Siéndolo,
   para tener una proporción fija tendría que sacar su ancho de su alto — y en
   una rejilla los anchos de columna se resuelven ANTES que los altos de fila,
   así que ese alto todavía no existe y aspect-ratio no tendría de dónde
   agarrarse. Como hermana en una fila flex, su alto sí es conocido y la
   proporción sale exacta. */
.cc-fuente__mosaico {
  flex: 1;
  min-height: 0;
  display: flex;
  /* center y no flex-start: el alto de la tarjeta lo decide el ANCHO —el
     mayor con el que las muestras aún caben en tres filas—, así que el bloque
     casi nunca llena el alto disponible y sobran ~110px. Alineado arriba, ese
     hueco se acumulaba entero al pie y el mosaico se veía colgando; centrado,
     queda repartido y el bloque se lee apoyado en la ventana.
     Los hijos siguen midiendo lo suyo: center alinea, no estira. */
  align-items: center;
  gap: var(--mos-gap);
}

/* Alto de UNA tarjeta. Lo calcula app.js (ajustarMosaico) resolviendo a la
   vez las dos restricciones —que las tres filas quepan de alto y que todas
   las columnas quepan de ancho— y de ahí salen los altos de todo el mosaico.
   El valor de acá es sólo el de partida, hasta que se mide. */
.cc-fuente__mosaico { --tarjeta-h: 56px; --mos-gap: 8px; }
/* Tres filas más sus dos separaciones: es el alto del bloque de muestras, y
   por lo tanto también el de la zona de carga — que era justo lo pedido. */
.cc-fuente__mosaico > * { height: calc(var(--tarjeta-h) * 3 + var(--mos-gap) * 2); }

.cc-drop {
  position: relative;
  aspect-ratio: 2 / 3;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--gap-s);
  background: var(--bg2);
  border: 1px dashed var(--borde);
  border-radius: var(--radio);
  cursor: pointer;
  transition: background .15s, border-color .15s;
}
.cc-drop:hover, .cc-drop:focus-visible { border-color: var(--acento); outline: none; }
.cc-drop--sobre-arrastre { background: var(--acento-tenue); border-color: var(--acento); }
.cc-drop__msg {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  color: var(--texto-tenue); font-size: .72rem; line-height: 1.4;
}
.cc-drop__msg svg { color: var(--acento); flex-shrink: 0; }

.cc-gallery {
  /* Flujo libre en TRES filas: las tarjetas se van pegando a la izquierda y
     saltan de renglón cuando no caben, como palabras en un párrafo. No hay
     columnas — una miniplaneta angosta no deja hueco a su derecha esperando
     a la de abajo.
     El contenedor mide exactamente tres filas; que quepan justo en esas tres
     lo resuelve ajustarMosaico() en app.js, eligiendo el alto de tarjeta. */
  display: flex;
  flex-flow: row wrap;
  justify-content: flex-start;
  align-items: flex-start;
  align-content: flex-start;
  gap: var(--mos-gap);
  flex: 1;
  min-width: 0;
  overflow: hidden;
}
/* La tarjeta ES la imagen: su proporción es la de la muestra, exacta —
   cuadrada para una miniplaneta 1:1, el doble de ancha que alta para una
   equirectangular 2:1. Por eso el rótulo va ENCIMA y no debajo: debajo,
   sumaría su alto y la tarjeta dejaría de tener la proporción de la imagen.
   Al ir posicionado, tampoco cuenta para el ancho — antes un nombre largo
   ensanchaba la tarjeta muy por encima de su miniatura. */
.cc-tile {
  position: relative;
  font-family: inherit;
  background: var(--bg2);
  border: 1px solid var(--borde);
  border-radius: var(--radio-sm);
  color: var(--texto-tenue);
  padding: 0;
  overflow: hidden;
  display: block;
  cursor: pointer;
  transition: border-color .15s, color .15s;
}
.cc-tile:hover, .cc-tile:focus-visible { border-color: var(--acento); outline: none; }
.cc-tile--usada { border-color: var(--acento); }
/* El alto lo fija el mosaico y el ancho sale de la proporción real de la
   imagen, así que la tarjeta mide exactamente lo que la imagen: cuadrada
   para una miniplaneta, el doble de ancha que alta para una 2:1. */
.cc-tile { height: var(--tarjeta-h); flex: 0 0 auto; width: auto; }
.cc-tile__img { height: 100%; width: auto; display: block; }
/* Alto fijo para TODAS las miniaturas (igual sea la muestra 2:1 o 1:1) — el
   ancho lo da la proporción real de cada imagen, no al revés. Por eso el
   contenedor es inline-flex (se ajusta al ancho de su contenido) en vez de
   ocupar toda la columna: una miniplaneta 1:1 queda angosta y de la misma
   altura que una equirectangular 2:1, no aplastada a su forma.
   No hace falta conocer el tamaño de antemano para evitar saltos: el alto
   nunca cambia, así que no hay salto vertical al cargar cada imagen; el
   pequeño ajuste de ancho al llegar la miniatura real es casi imperceptible. */
/* Franja de rótulo sobre el borde inferior de la imagen.
   El degradado va de transparente arriba al tono más oscuro de la paleta
   abajo: sin él, un nombre claro sobre una foto clara —cielo, nieve, arena—
   se vuelve ilegible justo en la parte de abajo, que es donde cae. Se usa
   var(--bg) y no un color escrito a mano para que siga la paleta si cambia.
   Los navegadores interpolan el degradado con alfa premultiplicado, así que
   "transparent → var(--bg)" no pasa por un gris intermedio. */
.cc-tile__name {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 14px 3px 3px;
  background: linear-gradient(to bottom, transparent, var(--bg) 78%);
  color: var(--texto);
  font-size: .52rem; letter-spacing: .02em; text-transform: uppercase;
  text-align: center; line-height: 1.15;
  overflow: hidden;
  /* con anywhere el mínimo del texto es UN carácter, así que ni siquiera
     posicionado podría ensanchar nada */
  overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}

/* ── 3 · desarrollo ───────────────────────────────── */
/* Segunda columna de la fila de arriba: se lleva lo que sobre.
   El margen negativo la trae hasta el arranque del pico y el recorte le abre
   la muesca que encaja con la ventana de al lado; el relleno izquierdo deja
   el lienzo del otro lado de la frontera, para que el recorte no le coma una
   esquina del desarrollo. */
.cc-win--red {
  flex: 1; min-width: 0; background: var(--bg2);
  display: flex; flex-direction: column;
  margin-left: calc(-1 * var(--chevron-sup) - 0.5px);
  padding-left: var(--chevron-sup);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, var(--chevron-sup) 50%);
}
/* El lienzo va dentro de su propio contenedor y NO directamente en la
   ventana: la barra de ayudas de abajo también ocupa alto, y app.js mide
   justo este contenedor (canvas.parentElement) para saber a qué tamaño
   dibujar. min-height:0 para que flex lo pueda achicar de verdad. */
.cc-red-lienzo { flex: 1 1 auto; min-height: 0; position: relative; }
/* cursor:move — el desarrollo se dibuja en un canvas, así que no tiene ningún
   indicio visual de que se pueda tocar. El cursor de mover anuncia el gesto
   que importa, que ya no es sólo horizontal: arrastrar una fila la reencuadra
   en los DOS ejes (a los lados gira, arriba y abajo acerca) y llevarla sobre
   otra fila las intercambia.
   touch-action:none — por lo mismo, el navegador no debe quedarse con ninguna
   de las dos direcciones. La página no se desplaza igual (body va con
   overflow:hidden), así que no se pierde nada. */
.cc-red-lienzo canvas {
  width: 100%; height: 100%; display: block;
  cursor: move; touch-action: none;
}

/* Barra de ayudas, compartida por la ventana de imágenes y la del
   desarrollo: los gestos principales (arrastrar una muestra a una fila,
   elegir fila) no tienen affordance visible, porque el desarrollo se dibuja
   en un canvas y el arrastre no deja rastro hasta que empieza. */
.cc-ayuda {
  flex-shrink: 0;
  margin: 0;
  padding: 6px var(--gap-s);
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: center;
  gap: 4px 14px;
  border-top: 1px solid var(--borde);
  font-size: .62rem; letter-spacing: .04em; text-transform: uppercase;
  color: var(--texto-tenue);
  text-align: center;
  /* align-self:center hace dos cosas de una: la barra se encoge a su
     contenido (así la línea de arriba mide lo que miden las ayudas y no
     todo el ancho de la ventana) y queda centrada. Si el contenido no cabe,
     flex-wrap la deja crecer hasta el ancho disponible, como antes. */
  align-self: center;
  max-width: 100%;
  /* Todas las barras del mismo alto, lleven texto o botones: sin esto, las
     que llevan botones quedan ~10 px más altas que las de sólo texto y las
     ventanas vecinas no alinean por abajo. */
  min-height: 34px;
}
/* Los separadores se dibujan con ::before para que no aparezcan sueltos si
   la barra envuelve a dos líneas. Sólo entre HIJOS DIRECTOS: adentro de cada
   ayuda hay variantes anidadas según el puntero, y sin el `>` una variante
   oculta le pondría un separador suelto a la que sí se ve. */
.cc-ayuda > span + span::before { content: '·'; margin-right: 14px; opacity: .5; }

/* Variantes de texto según con qué se apunte. En táctil no existe el
   arrastre —HTML5 drag-and-drop no lo dispara el dedo—, así que las ayudas
   tienen que nombrar el gesto que de verdad funciona ahí. */
.cc-si-tacto { display: none; }
@media (pointer: coarse) {
  .cc-si-raton { display: none; }
  .cc-si-tacto { display: inline; }
}
.cc-ayuda__dato { color: var(--texto); }
.cc-ayuda__dato b { color: var(--acento); }

/* ── Barras de ayudas que en vez de texto llevan controles ────────────
   Las de la fila de abajo no explican: hacen. Los botones son chicos y
   planos a propósito, para que la barra siga leyéndose como un pie de
   ventana y no compita con las acciones grandes de exportar. */
/* Con LAS DOS clases (.cc-ayuda.cc-ayuda--botones) y no sólo con la
   modificadora: el bloque de teléfono, al final del archivo, vuelve a fijar
   el gap de .cc-ayuda para apretar las barras de texto. Con una clase cada
   una, empatan en especificidad y gana la que va después — o sea la del
   teléfono, que dejaba estos botones a 10px justo donde más se notaba. Con
   dos clases, esta manda siempre y no depende del orden del archivo. */
.cc-ayuda.cc-ayuda--botones { gap: 4px 2px; }
.cc-mini {
  font-family: inherit;
  font-size: .6rem; letter-spacing: .04em; text-transform: uppercase;
  color: var(--texto-tenue);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radio-sm);
  padding: 3px 7px;
  display: inline-flex; align-items: center; gap: 5px;
  cursor: pointer; text-decoration: none;
  transition: color .15s, background .15s, border-color .15s;
}
.cc-mini:hover, .cc-mini:focus-visible {
  color: var(--texto); background: var(--bg3); border-color: var(--borde); outline: none;
}
/* Encendido: los dos interruptores del visor arrancan activos, así que el
   estado "prendido" tiene que ser el que se ve normal y el apagado el que
   se nota apagado — no al revés. */
.cc-mini[aria-pressed="true"] { color: var(--acento); border-color: var(--borde); }
.cc-mini[aria-pressed="false"] { opacity: .55; }
.cc-mini:disabled { opacity: .3; cursor: default; pointer-events: none; }
.cc-mini__ico { width: 14px; height: 14px; flex-shrink: 0; }

/* ── Sistema de piezas trapezoidales ───────────────────────────────────
   Todos los botones del sitio son trapecios que encajan entre sí. La
   inclinación NUNCA se elige a ojo: es la misma del pico ">" que separa las
   ventanas, que avanza --chevron (= alto·0,11) en media altura de ventana, o
   sea 0,22 de corrimiento horizontal por cada 1 de recorrido vertical. De ahí
   que cada botón declare su --sesgo como 0,22 × su propio alto: así todos
   comparten el ángulo aunque midan cosas distintas.

   Dos variantes, que puestas una al lado de la otra (o una encima de otra)
   dejan sus filos paralelos y encajan:
     --a   lado de ARRIBA más angosto (abajo más ancho)
     --b   lado de ARRIBA más ancho (abajo más angosto)

   Van sin redondeo, sombra NI BORDE: el recorte se lleva los bordes
   laterales, así que la silueta la tiene que sostener el relleno — un borde
   normal (1px solid var(--borde), el de .tp-boton y de .cc-mini en hover) se
   ve entero en los filos de arriba y abajo pero se corta a la mitad en los
   inclinados, dejando dos rayas de color asomando donde debería no haber
   nada. Se anula acá, UNA vez para toda la familia .cc-trapecio, en vez de
   confiar en que cada botón nuevo se acuerde de ponerlo transparente. */
.cc-trapecio { border-radius: 0; box-shadow: none; border-color: transparent; }
.cc-trapecio:hover, .cc-trapecio:focus-visible { border-color: transparent; }
/* "Vaciar fila" va en la barra del desarrollo, entre textos y no en un grupo
   de botones: no tiene con quién alternar, así que lleva la variante --a
   suelta. Su alto es el de un .cc-mini normal. */
#btnVaciarFila { --sesgo: 5px; }
/* El botón final del diálogo de exportar mide --alto-btn (28px), no los 22
   de un mini: 0,22 × 28 ≈ 6. El relleno lateral crece para que el rótulo no
   toque los filos inclinados. --a (no --b) a propósito: acá el borde de
   ABAJO tiene que salir recto, porque es el que queda pegado al borde
   inferior del modal — el filo picudo va arriba, donde sí hay margen. */
#btnExportPDF { --sesgo: 6px; padding-inline: 26px; }
/* El recorte de .cc-trapecio se lleva el borde (ver el comentario de esa
   familia): sin él, el hover de fondo oscuro que trae .tp-boton de base
   (var(--bg3)) queda casi del mismo tono que el fondo del modal (--bg2) y el
   botón se apaga contra él. Se pisa acá con la ID, igual que ya hace
   #btnAbrirExport para el mismo problema: en hover se aclara en vez de
   oscurecer. */
#btnExportPDF:hover, #btnExportPDF:focus-visible {
  opacity: .86;
  background: var(--acento);
  color: var(--acento-texto);
}
/* Pegado al borde INFERIOR del modal (cancela sus 24px de padding ahí, sin
   hueco), pero con un poco de aire respecto al borde DERECHO — el mismo
   margen de 10px que deja el botón de cerrar (✕) en su esquina, en vez de
   quedar pegado también ahí. text-align manda el botón a la derecha del
   espacio que le queda. flex-shrink:0: es hermano flex de .cc-modal__scroll
   (ver .cc-modal) y sin esto, con contenido largo, se encogería junto con
   ella en vez de dejarle todo el ajuste — el botón de exportar tiene que
   quedar siempre entero y siempre visible, nunca a medio scrollear. */
.cc-modal__pie {
  text-align: right;
  margin: 16px -14px -24px 0;
  flex-shrink: 0;
}
#btnComoFunciona { --sesgo: 5px; }
/* ── Toggle de idioma: dos piezas, siempre las dos visibles ────────────
   No es un botón que cambia de rótulo: son dos trapecios pegados, ES
   (--a, ancha ABAJO) a la izquierda y EN (--b, ancha ARRIBA) a la derecha,
   el mismo par que ya usan "Cargar imagen"/"Imágenes de muestra". El
   encendido se dice por COLOR (ver más abajo, .cc-idioma-seg), no por
   texto: así se lee de un vistazo cuál está activo, sin tener que leer.
   app.js sólo pone/quita aria-pressed; el color sale de ahí solo. */
.cc-idioma-toggle { display: flex; }
.cc-idioma-seg { --sesgo: 5px; }
/* SIN el hueco de 2px que llevan los demás pares trapezoidales del sitio:
   acá los dos filos tienen que ser la MISMA arista, no dos paralelas con
   aire entre medio. El recorte de --sesgo ya le come ese ancho a cada
   trapecio por su cuenta (el filo de ES sólo toca el borde de su caja en
   la esquina de ABAJO, y el de EN sólo en la de ARRIBA); sin este margen
   negativo, esos dos filos quedarían separados por 2×sesgo de nada. Al
   correr la caja de EN hacia la izquierda exactamente --sesgo, sus filos
   pasan a trazar la MISMA línea que el de ES — la geometría, no el ojo. */
.cc-idioma-seg[data-lang="en"] { margin-left: calc(-1 * var(--sesgo)); }

/* Instancias de escritorio: ancladas a la esquina superior de su propia
   ventana/modal (position: absolute contra ese contenedor), no fijas a la
   pantalla — cada una vive en el panel donde aparece. */
.cc-idioma-esquina {
  position: absolute;
  top: 0;
  z-index: 2;
}
#idiomaEscritorio { right: calc(var(--chevron-inf) + 10px); }
#idiomaModal { left: 10px; }
#idiomaModal .cc-idioma-seg {
  height: 26px;
  --sesgo: 6px;
  display: flex; align-items: center; justify-content: center;
}
/* El encendido se lee por TONO, no por texto: la mitad activa toma el
   fondo de acento (igual que un botón primario) y la otra se apaga a
   --texto-tenue sobre el fondo normal. Al cliquear se invierten: la que
   estaba apagada pasa a acento y la otra se apaga — nunca las dos a la vez
   ni ninguna, porque son exactamente los dos idiomas que existen.
   Especificidad: dos clases + atributo, para ganarle sin ambigüedad a
   .cc-mini[aria-pressed] y .cc-mini.cc-trapecio, que tienen la misma forma
   de selector pero un propósito distinto (ese es para Aristas/Eversión). */
.cc-mini.cc-idioma-seg { color: var(--texto-tenue); }
.cc-mini.cc-idioma-seg[aria-pressed="true"] {
  background: var(--acento);
  color: var(--acento-texto);
}
.cc-mini.cc-idioma-seg:hover, .cc-mini.cc-idioma-seg:focus-visible {
  background: var(--borde); color: var(--texto);
}
.cc-mini.cc-idioma-seg[aria-pressed="true"]:hover,
.cc-mini.cc-idioma-seg[aria-pressed="true"]:focus-visible {
  /* Ya está encendida: el hover atenúa en vez de tapar el color de acento,
     que es justo lo que dice "esta es la activa". */
  background: var(--acento); opacity: .86; color: var(--acento-texto);
}

.cc-trapecio--a,
.cc-ayuda--botones .cc-mini:nth-child(odd) {
  clip-path: polygon(var(--sesgo, 5px) 0, calc(100% - var(--sesgo, 5px)) 0, 100% 100%, 0 100%);
}
.cc-trapecio--b,
.cc-ayuda--botones .cc-mini:nth-child(even) {
  clip-path: polygon(0 0, 100% 0, calc(100% - var(--sesgo, 5px)) 100%, var(--sesgo, 5px) 100%);
}

/* ── Filo recto en los extremos de una fila de trapecios ────────────────
   El filo inclinado sólo tiene sentido donde un trapecio ENCAJA con el
   siguiente: en el extremo de la fila no hay con qué encajar, y ese filo
   suelto apuntando al vacío se ve como un error. Se corrige SÓLO ese lado
   (el otro sigue inclinado, para seguir encajando con su vecino) en cada
   botón que de verdad queda en punta de una fila — no en botones sueltos
   como el cierre ✕, que no tiene con quién encajar en ningún lado y ya se
   lee bien entero. "Vaciar fila" también va suelto, sin vecino, pero su
   filo DERECHO sí se endereza: linda con el borde de la ventana, no con
   otro botón, y ahí el mismo criterio aplica (nada con qué encajar =
   filo recto). El izquierdo sigue inclinado porque de ese lado sí hay texto
   de la barra de ayuda pegado. Tampoco se toca en los dos botones grandes de
   exportar EN ESCRITORIO (#btnAbrirExport/#btnExportOBJ): ahí van uno
   debajo del otro con un recorte a mano pensado en zigzag, sin extremo
   lateral que corregir (ver más abajo). En MÓVIL esos mismos dos botones sí
   quedan en fila uno junto al otro, y su filo recto se corrige aparte, dentro
   del recorte que ya tienen a mano para esa disposición (ver el media query
   de móvil).
   Selectores por ID donde hay uno único a mano; si no (el toggle de idioma,
   que se repite tres veces, y el enlace de "proyectos hermanos", que no
   tiene ID), por posición dentro de su contenedor — con suficiente
   especificidad para ganarle a la regla de arriba sin depender del orden. */
#btnCargarMovil,                       /* móvil: cae a la izquierda de su par */
#btnAbrirAcerca,                       /* ventana1: cae a la izquierda de su par */
#btnWireframe,                         /* ventana4: primero de los tres */
#idiomaMovil .cc-idioma-seg:first-child,      /* ES, antes de "Cómo funciona" */
#idiomaEscritorio .cc-idioma-seg:first-child  /* ES, sola a la izquierda */
{
  clip-path: polygon(0 0, calc(100% - var(--sesgo, 5px)) 0, 100% 100%, 0 100%);
}
#idiomaModal .cc-idioma-seg:first-child       /* ES, con los trapecios espejados */
{
  clip-path: polygon(0 0, 100% 0, calc(100% - var(--sesgo, 5px)) 100%, 0 100%);
}
#btnMuestrasMovil,                     /* móvil: cae a la derecha de su par */
#idiomaEscritorio .cc-idioma-seg:last-child   /* EN, sola a la derecha */
{
  clip-path: polygon(0 0, 100% 0, 100% 100%, var(--sesgo, 5px) 100%);
}
#btnComoFunciona,                      /* móvil: cierra la fila de la cabecera */
#btnVistaInicial,                      /* ventana4: último de los tres */
#btnVaciarFila,                        /* suelto, pero linda con la ventana */
#idiomaModal .cc-idioma-seg:last-child /* EN, con los trapecios espejados */
{
  clip-path: polygon(var(--sesgo, 5px) 0, 100% 0, 100% 100%, 0 100%);
}
/* El enlace de "proyectos hermanos": sin ID, y con la misma especificidad
   que la regla nth-child(even) de arriba (dos clases cada una) — se le suma
   :last-child para ganar de verdad y no depender de cuál quede más abajo en
   el archivo. */
.cc-info__ayuda .cc-mini.cc-mini--enlace:last-child {
  clip-path: polygon(0 0, 100% 0, 100% 100%, var(--sesgo, 5px) 100%);
}


/* ── Barras de botones: trapecios alternados ───────────────────────────
   Las dos barras de este tipo —la del visor (aristas, eversión, vista
   inicial) y la de info ("Acerca de" y el enlace)— alternan solas por
   posición, sin tener que marcar cada botón a mano.
   Fondo propio: un trapecio sin relleno no se leería como forma. */
.cc-ayuda--botones .cc-mini,
.cc-mini.cc-trapecio {
  background: var(--tecla);
  border-color: transparent;
  border-radius: 0;
  box-shadow: none;
  padding: 3px 12px;
}
/* El recorte se come los bordes laterales, así que el estado ya no puede
   apoyarse en border-color: lo dice el FONDO, que es lo que sí se ve entero
   dentro del trapecio.
   --borde (L26) y no --bg3 (L15): desde que estos botones tienen fondo
   propio (--tecla, L20), el hover heredado los OSCURECÍA — al revés de lo
   que hace en el resto del sitio, donde el fondo de partida es transparente
   y --bg3 sí es un paso hacia arriba. */
.cc-ayuda--botones .cc-mini:hover, .cc-ayuda--botones .cc-mini:focus-visible,
.cc-mini.cc-trapecio:hover, .cc-mini.cc-trapecio:focus-visible {
  background: var(--borde); color: var(--texto);
}
.cc-ayuda--botones .cc-mini[aria-pressed="true"] { border-color: transparent; }
.cc-mini--enlace { color: var(--texto-tenue); }
/* El logo reemplaza a la palabra "Perigami", así que se dimensiona como una
   palabra: el alto va en em y sigue al tamaño de letra del botón, en las tres
   anchuras, sin tener que repetirlo en cada consulta de medios.
   El alt dice "Perigami" — el nombre accesible del enlace tiene que seguir
   leyéndose "ver proyectos hermanos en Perigami". */
.cc-mini__logo {
/*  Va como transform y no como margen ni posición para que mover el
    logo NO cambie el alto del botón — si lo cambiara, el trapecio
    necesitaría otro sesgo y se movería toda la barra. */
  --logo-baja: 1px;
  transform: translateY(var(--logo-baja));

  height: 1.6em;
  width: auto;
  display: block;
  /* El archivo es blanco puro y el rótulo de al lado va en gris tenue; sin
     esto el logo grita al lado de su propia frase. Al pasar el puntero sube
     a pleno, como el texto. */
  opacity: .75;
  transition: opacity .15s;
}
.cc-mini--enlace:hover .cc-mini__logo,
.cc-mini--enlace:focus-visible .cc-mini__logo { opacity: 1; }
.cc-info__ayuda { max-width: calc(100% - 2 * var(--chevron-inf)); }
/* Sólo esta barra, y sólo en escritorio: "Acerca de" + el logo de proyectos
   hermanos leen mejor pegados al borde de la ventana, como un pie de página,
   que flotando centrados bajo un bloque de texto que ya está alineado a la
   izquierda. Las demás barras de ayuda (desarrollo, visor) siguen centradas
   — ahí SÍ tiene sentido, porque están debajo de contenido que también se
   centra (el canvas, los botones). Se revierte a centro en el bloque de
   tablet/teléfono más abajo, donde esta ventana pasa a ser un panel angosto
   y el pie centrado vuelve a leerse mejor. */
.cc-info__ayuda {
  justify-content: flex-start;
  align-self: flex-start;
  text-align: left;
  /* Alineada con el texto de arriba: .cc-info__cuerpo tiene padding-left
     var(--gap-m), pero esta barra es HERMANA suya (vive fuera, directo en
     la ventana) y trae su propio padding-left más angosto (var(--gap-s),
     parte del padding acortado de .cc-ayuda). Sin este empate, "Un
     caleidociclo hexagonal..." y "+info" arrancaban en columnas distintas
     aunque los dos estuvieran "a la izquierda". */
  padding-left: var(--gap-m);
}
/* En estas dos ventanas el borde derecho está recortado en pico. Ya no se
   usa un margen (desplazaba el centrado ahora que la barra mide lo que su
   contenido): se le pone tope de ancho para que, centrada, nunca llegue a
   la zona recortada. */
.cc-fuente__ayuda { max-width: calc(100% - 2 * var(--chevron-sup)); }

.cc-hint {
  position: absolute;
  left: 50%; bottom: 10px;
  transform: translateX(-50%);
  background: var(--tecla);
  border: 1px solid var(--borde);
  border-radius: var(--radio-sm);
  padding: 5px 10px;
  font-size: .7rem;
  color: var(--texto);
  max-width: 90%;
}
.cc-hint.error { color: var(--peligro); border-color: var(--peligro); }

/* ── fila inferior derecha: 4 visor + 5 exportar ──── */
/* El visor queda EN MEDIO de la fila, así que lleva las dos mitades del
   pico: punta hacia la derecha y muesca a la izquierda. Sin relleno: el
   lienzo ocupa toda la caja y el recorte le da la forma, que para un visor
   3D está bien (el modelo va centrado y encuadrado solo). Si llevara
   relleno, el renderer mediría clientWidth —que lo incluye— y dibujaría a
   un tamaño distinto del que ocupa el lienzo. */
.cc-win--visor {
  /* --bg-visor y no --bg2: es el mismo color exacto con que renderer.js
     limpia el lienzo WebGL (setClearColor). El lienzo NO llena la ventana
     entera —le resta alto la barra de controles de abajo—, así que si el
     fondo de la ventana fuera otro tono, se vería una franja de costura
     alrededor del canvas. Con los dos iguales, toda la ventana es un solo
     color parejo. */
  background: var(--bg-visor);
  width: calc(var(--col-inf-b) + var(--chevron-inf));
  flex-shrink: 0;
  margin-left: calc(-1 * var(--chevron-inf) - 0.5px);
  clip-path: polygon(
    0 0,
    calc(100% - var(--chevron-inf)) 0,
    100% 50%,
    calc(100% - var(--chevron-inf)) 100%,
    0 100%,
    var(--chevron-inf) 50%);
}
/* El lienzo en su propio contenedor, como en el desarrollo: la barra de
   controles de abajo le come alto, y renderer.js mide justo el contenedor
   del canvas para saber a qué tamaño dibujar. */
.cc-visor__lienzo { flex: 1; min-height: 0; position: relative; }
.cc-visor__lienzo canvas { width: 100%; height: 100%; display: block; touch-action: none; }
.cc-win--export {
  flex: 1;
  min-width: 0;
  background: var(--bg2);
  align-items: center;
  justify-content: center;
  padding: var(--gap-s);
  /* sólo el pico, sin sumarle el relleno normal: la columna es angosta y
     cada píxel de ancho cuenta para que quepan los rótulos */
  padding-left: var(--chevron-inf);
  margin-left: calc(-1 * var(--chevron-inf) - 0.5px);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, var(--chevron-inf) 50%);
}
/* Los dos botones se apilan (el de .obj arriba) y quedan del mismo ancho,
   para que se lean como un par y no como uno metido al lado del otro. */
/* El bloque de acciones se lleva todo el alto y centra su contenido, así que
   "Acerca de" —que va después— queda pegado al borde de abajo sin necesidad
   de posicionarlo. */
.cc-win--export { flex-direction: column; align-items: stretch; justify-content: flex-start; }
.cc-export__acciones {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* 6px: menos que --gap-s (8-12px), donde se veían sueltos, pero más que
     los 2px de las filas horizontales.
     El hueco vertical NO puede ser tan chico como el horizontal, aunque el
     sistema de piezas sea el mismo. En una fila, los filos que se enfrentan
     son los INCLINADOS, y al ser paralelos un hueco de 2px se lee como la
     juntura entre dos piezas. Apilados, en cambio, lo que se enfrenta es el
     lado de abajo de uno con el de arriba del otro: dos rectas horizontales
     idénticas, sin nada que sugiera encaje. Ahí 2px no parecen una juntura,
     parecen un error de impresión. */
  gap: 6px;
}
.cc-ico { width: 30px; height: 30px; flex-shrink: 0; }
/* Paralelogramos, con el ícono arriba y el rótulo debajo. Angostos y
   centrados: no ocupan el ancho de la columna, así el ícono es lo que se lee
   primero. El rótulo envuelve —"Descargar 3D (obj)" no cabe en un renglón a
   este ancho— y por eso el alto sale del aspect-ratio, no de una altura fija.

   La inclinación NO es un valor a ojo: es la misma del pico que separa esta
   ventana de la de al lado. Ese pico avanza --chevron-sup (= alto·0,11) en
   media altura de ventana, o sea 0,22 de corrimiento horizontal por cada 1
   de recorrido vertical. Sobre un botón de proporción 1,3:1 eso da
   0,22 / 1,3 ≈ 16,9 % del ancho — de ahí el 16.9%/83.1% de los recortes.
   Los dos se inclinan al REVÉS uno del otro, así que apilados forman un
   zigzag en vez de un bloque: el mismo gesto del pico, repetido. */
.cc-export-btn {
  width: clamp(104px, 74%, 156px);
  aspect-ratio: 1.3;
  height: auto;
  align-self: center;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
  /* El relleno lateral compensa lo que se lleva el recorte: sin él, el
     rótulo llegaría hasta el filo inclinado y se vería cortado. */
  padding: 8px 18%;
  font-size: .68rem;
  line-height: 1.2;
  white-space: normal;
  text-align: center;
  /* El recorte se come el borde y la sombra de los lados inclinados, así que
     la silueta la tiene que sostener el relleno. Y si el borde sólo puede
     verse arriba y abajo, deja de ser un contorno y pasa a ser una raya:
     transparente, que con background-clip de caja de borde deja pasar el
     relleno y el botón queda de un color y nada más. */
  border-radius: 0;
  box-shadow: none;
  border-color: transparent;
}
.cc-export-btn:active { box-shadow: none; }
/* El primero se inclina a la izquierda (arriba corrido hacia la izquierda) */
#btnAbrirExport { clip-path: polygon(0 0, 83.1% 0, 100% 100%, 16.9% 100%); }
/* El hover de .tp-boton--primario es un opacity:.86, pensado para un botón
   con borde sobre un fondo claro. Acá el recorte se llevó el borde y detrás
   está el fondo oscuro de la ventana, así que bajar la opacidad no aclaraba:
   ensuciaba el cian y se tragaba el rótulo y el ícono. En su lugar, el hover
   va a blanco pleno — que en un botón de acción principal se lee como "se
   enciende" y deja el contenido en máximo contraste. */
#btnAbrirExport:hover,
#btnAbrirExport:focus-visible {
  opacity: 1;
  background: var(--texto-brillante);
  border-color: transparent;
  color: var(--acento-texto);
}
/* "INFO" (cabecera móvil) y "+info" (barra de ayuda de ventana1): mismos
   colores que el CTA principal de exportar, para que salten a la vista en
   vez de leerse como un botón secundario más entre el resto. */
#btnComoFunciona, #btnAbrirAcerca {
  background: var(--acento);
  color: var(--acento-texto);
}
/* #btnComoFunciona es un .cc-trapecio: el recorte se lleva el borde, así
   que el hover se aclara en vez de oscurecer — mismo motivo y mismo
   tratamiento que #btnAbrirExport, arriba. */
#btnComoFunciona:hover, #btnComoFunciona:focus-visible {
  opacity: 1;
  background: var(--texto-brillante);
  color: var(--acento-texto);
}
/* #btnAbrirAcerca no está recortado (sigue siendo un .cc-mini normal, con
   su borde y su radio de esquina), así que le alcanza con el hover simple
   de .tp-boton--primario: atenuar en vez de aclarar. */
#btnAbrirAcerca:hover, #btnAbrirAcerca:focus-visible {
  opacity: .86;
  background: var(--acento);
  color: var(--acento-texto);
}

/* y el segundo a la derecha (▱), para que el zigzag cierre. */
#btnExportOBJ {
  clip-path: polygon(16.9% 0, 100% 0, 83.1% 100%, 0 100%);
  /* Sin borde que lo delimite, el fondo es lo ÚNICO que separa este botón del
     de la ventana, así que tiene que despegarse bien: --tecla (L20) en vez de
     --bg3 (L15), que contra el --bg2 de la ventana (L10) apenas se distinguía. */
  background: var(--tecla);
}
#btnExportOBJ:hover, #btnExportOBJ:focus-visible { background: var(--borde); }

/* ── Tiradores de redimensión ─────────────────────── */
.cc-grip {
  position: absolute;
  z-index: 20;
  background: transparent;
  transition: background .15s;
}
.cc-grip:hover, .cc-grip.is-drag { background: var(--acento); }

/* El del pico no se pinta de fondo (taparía el recorte): lo que responde es
   el trazo. preserveAspectRatio="none" lo estira al alto de la fila, y
   non-scaling-stroke evita que ese estirón deforme el grosor de la línea. */
.cc-grip--pico { background: none !important; }
.cc-grip--pico svg { width: 100%; height: 100%; display: block; overflow: visible; }
.cc-grip--pico polyline {
  stroke: var(--borde);
  stroke-width: 1;
  stroke-linejoin: round;
  transition: stroke .15s, stroke-width .15s;
}
.cc-grip--pico:hover polyline, .cc-grip--pico.is-drag polyline {
  stroke: var(--acento);
  /* El mismo grosor que toma el tirador horizontal al pasar el mouse o
     arrastrar (.cc-grip:hover, más abajo): ahí es el ANCHO de la franja
     rellena; acá, al ser una línea y no una franja, es el GROSOR del trazo
     — pero el valor es el mismo var(--grip), para que el engrosamiento se
     sienta parejo entre los dos tipos de tirador. */
  stroke-width: var(--grip);
}
.cc-grip--v { width: var(--grip); cursor: col-resize; }
.cc-grip--h { height: var(--grip); cursor: row-resize; }
/* posiciones: se calculan desde las mismas variables que la rejilla */
/* entre las dos filas, de lado a lado */
.cc-grip[data-grip="filaSplit"] { left: 0; right: 0; top: calc(var(--alto-fila1) - var(--grip)/2); }
/* dentro de la fila de arriba: ocupa TODO el ancho del pico, no una raya
   angosta, así que además de dibujarlo es una zona de agarre cómoda */
.cc-grip[data-grip="supSplit"] {
  top: 0; height: var(--alto-fila1);
  left: calc(var(--col-sup-a) - var(--chevron-sup)/2);
  width: var(--chevron-sup);
}
/* las dos divisiones de la fila de abajo, con el mismo pico */
.cc-grip[data-grip="infSplitA"] {
  top: var(--alto-fila1); bottom: 0;
  left: calc(var(--col-inf-a) - var(--chevron-inf)/2);
  width: var(--chevron-inf);
}
.cc-grip[data-grip="infSplitB"] {
  top: var(--alto-fila1); bottom: 0;
  left: calc(var(--col-inf-a) + var(--col-inf-b) - var(--chevron-inf)/2);
  width: var(--chevron-inf);
}
body.cc-resizing { user-select: none; }

/* ── Modales ──────────────────────────────────────── */
.cc-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.62);
  backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--gap-m);
  z-index: 100;
}
.cc-modal-overlay[hidden] { display: none; }

/* SIN overflow-y acá (a diferencia de antes): si .cc-modal fuera a la vez el
   contenedor con scroll Y el contenedor posicionador de .cc-modal__close
   (position:absolute), el botón de cerrar se movería CON el contenido al
   scrollear — su posición se calcula contra el padding-box de .cc-modal, y
   ese padding-box es justo lo que se desplaza cuando .cc-modal es quien
   scrollea. Por eso ahora .cc-modal sólo pone el tope de alto (max-height) y
   reparte con flexbox: el cierre queda absoluto y fijo, y quien de verdad
   scrollea es un HIJO de acá adentro (.cc-modal__scroll, .cc-modal__cuerpo o
   .cc-resultado, según el modal) — ver esas reglas más abajo. */
.cc-modal {
  position: relative;
  width: 100%;
  max-width: 560px;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  background: var(--bg2);
  border: 1px solid var(--borde);
  /* Sin redondear: el resto del sitio es todo picos y trapecios, y una
     esquina curva acá desentonaba con ese lenguaje. */
  padding: 28px 24px 24px;
  color: var(--texto);
  line-height: 1.6;
}
/* La región que de verdad scrollea, para los modales SIN .cc-modal__cuerpo
   propio (Exportar, Restaurar). min-height:0 es lo que de verdad habilita el
   scroll: un hijo flex por defecto no se encoge más allá del alto de su
   contenido (min-height:auto), así que sin esto .cc-modal terminaría
   creciendo más allá de max-height en vez de scrollear acá adentro. */
.cc-modal__scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
.cc-modal h2 {
  margin: 0 0 14px; color: var(--texto-brillante); font-size: .95rem;
  letter-spacing: .08em; text-transform: uppercase;
  /* Sólo hace algo cuando el h2 es HIJO DIRECTO de .cc-modal (el título de
     "Acerca de", fuera de .cc-modal__cuerpo a propósito — ver esa regla):
     sin esto, si el contenido no entra en max-height, flexbox lo encogería
     a él también en vez de dejarle todo el ajuste a la región que scrollea.
     En el resto de los modales el h2 vive DENTRO de .cc-modal__scroll/
     .cc-modal__cuerpo, no es un hijo flex, y esto no tiene ningún efecto. */
  flex-shrink: 0;
}
/* Cabecera con licencia del modal de exportar (ver actualizarBrandingPremium
   en app.js): el h2 de arriba queda sólo para lectores de pantalla (.cc-solo-
   lector, ver abajo), así que ESTE es el único título visible cuando aparece.
   [hidden] explícito: si no, una regla de display más específica que la del
   navegador lo dejaría visible igual (ver CLAUDE.md §9, trampa 7). */
.cc-modal__logo-plus {
  display: block;
  width: min(100%, 220px);
  height: auto;
  margin: 0 auto 16px;
}
.cc-modal__logo-plus[hidden] { display: none; }
/* Oculto para la vista, presente para el lector de pantalla: el patrón
   estándar de recortar a 1px en vez de display:none, que sí lo sacaría del
   árbol de accesibilidad. */
.cc-solo-lector {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.cc-modal p { margin: 0 0 12px; font-size: .84rem; }
.cc-modal ol { padding-left: 1.2em; font-size: .82rem; display: flex; flex-direction: column; gap: 5px; }

/* "Acerca de" lleva bastante más texto que el de exportar, y con formulas
   que no conviene que se partan en tres líneas.
   padding:0 — la ventana de exportar (.cc-modal a secas) sigue con SU
   padding de siempre; ésta lo pierde porque la cabecera (ver abajo) tiene
   que llegar hasta el borde, y .cc-modal__cuerpo se lo devuelve al resto
   del contenido. */
.cc-modal--ancho { max-width: 680px; padding: 0; }
/* La cabecera del "Acerca de": la misma foto del og:image, a RAS de los
   tres bordes que tocan el borde del modal (arriba, izquierda, derecha) —
   de ahí que h2/img vivan FUERA de .cc-modal__cuerpo, que es quien carga
   con el padding de todo lo demás. Sin recortar (height:auto, no cover):
   se ve la foto entera, tal cual es la miniatura que ya se comparte. */
.cc-modal--ancho h2 {
  margin: 0;
}
.cc-modal__header {
  display: block;
  width: 100%;
  height: auto;
}
/* Además del padding: en Acerca y Compra, ESTA es la región que scrollea
   (ver el comentario grande en .cc-modal sobre por qué .cc-modal ya no
   scrollea directo). */
.cc-modal__cuerpo { padding: 18px 24px 24px; flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.cc-modal h3 {
  margin: 22px 0 10px; color: var(--acento); font-size: .78rem;
  letter-spacing: .08em; text-transform: uppercase;
  border-top: 1px solid var(--borde); padding-top: 16px;
}
.cc-modal h4 {
  margin: 14px 0 5px; color: var(--texto); font-size: .8rem;
  font-weight: 900; letter-spacing: .02em;
}
.cc-modal code {
  font-family: inherit; font-size: .95em;
  background: var(--tecla); border-radius: 2px; padding: 1px 4px;
  color: var(--texto);
}
.cc-formula {
  text-align: center;
  background: var(--bg3);
  border-left: 2px solid var(--acento);
  border-radius: var(--radio-sm);
  padding: 10px 12px;
  font-size: .9rem !important;
  color: var(--texto) !important;
}
.cc-creditos { font-size: .76rem !important; color: var(--texto-tenue); }
.cc-creditos a { color: var(--acento); }
.cc-modal__close {
  position: absolute;
  top: 0; right: 10px;
  width: 26px; height: 26px;
  --sesgo: 6px;                    /* 0,22 × 26 ≈ 6 */
  border: 1px solid transparent;   /* el recorte se lleva los filos: manda el fondo */
  background: var(--tecla);
  color: var(--texto);
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
/* #btnCerrarComo y #btnCerrarMuestras reusan esta clase para sus paneles
   ("Cómo funciona" e "Imágenes"): mismo z-index defensivo que tenía
   .cc-panel__cerrar (ya sin uso), por si algo del contenido de arriba
   llegara a superponerse. */
#btnCerrarComo, #btnCerrarMuestras { z-index: 2; }
.cc-modal__close:hover, .cc-modal__close:focus-visible { background: var(--borde); color: var(--texto-brillante); outline: none; }


.cc-field { display: flex; flex-direction: column; gap: 4px; font-size: .78rem; color: var(--texto-tenue); margin-bottom: 12px; }
.cc-field span { font-weight: 700; letter-spacing: .05em; text-transform: uppercase; font-size: .7rem; }
/* Etiqueta y valor actual como una sola frase, alineada a la izquierda
   ("ANCHO DEL DESARROLLO 24 CM"); el slider va abajo, en su propia fila. */
.cc-field__cabecera { display: flex; justify-content: flex-start; align-items: baseline; gap: 5px; }
.cc-field select {
  background: var(--tecla); border: 1px solid var(--borde);
  border-radius: var(--radio-sm); color: var(--texto);
  padding: 8px 10px; font-family: inherit;
}
.cc-progress { width: 100%; accent-color: var(--acento); height: 6px; margin: 12px 0; }
/* La del OBJ vive en la ventana de exportar, no en un modal — y esa ventana
   cambia de columna (escritorio) a fila (móvil) entre puntos de corte. Fuera
   del flujo y pegada al borde de abajo, se ve igual en los dos sin repetir
   la regla por breakpoint. */
.cc-progress--flotante {
  position: absolute;
  left: var(--gap-s); right: var(--gap-s); bottom: 8px;
  width: auto; margin: 0;
}
.cc-field input[type="range"] { width: 100%; accent-color: var(--acento); }
.cc-field output { font-size: .75rem; color: var(--acento); }
.cc-nota { margin: -4px 0 14px; font-size: .72rem; color: var(--texto-tenue); }
/* Aviso de resolución: se ve, pero no grita — no impide exportar, sólo
   informa. Va en --peligro atenuado y no en el color de error a secas: es una
   advertencia, no un fallo. La barra lateral lo separa de la nota del tamaño
   de hoja, que es puramente informativa. */
.cc-nota--aviso {
  color: var(--peligro);
  border-left: 2px solid currentColor;
  padding-left: 8px;
  line-height: 1.45;
}
/* hidden pierde contra la regla de display de arriba si algún día la lleva:
   se declara explícito (ver el bug documentado en TinyPlanetStudio). */
.cc-nota[hidden] { display: none; }

/* Vista previa: la misma proporción de la hoja real, pero a tamaño de
   pantalla — el ancho manda y el alto sale solo de la imagen generada.
   margin-top igual al de .cc-field (14px): los interruptores no traen
   margen propio abajo (son inline-flex, pensados para ir seguidos), así que
   sin este margen la vista quedaba pegada al último. */
.cc-vista-previa {
  margin: 14px 0 14px;
  border: 1px solid var(--borde);
  background: var(--bg);
  /* Reserva el espacio ANTES de que la imagen exista: se genera en vivo
     (ver actualizarVistaPrevia en app.js) y hasta que ese render asíncrono
     termina, <img> no tiene src ni tamaño intrínseco — sin esto la caja
     colapsaba a 0 de alto cada vez que se abría el modal y el contenido
     pegaba un salto al aparecer. 490:200 es NET_W_TOTAL:NET_H (la
     proporción del desarrollo, ver printNet.js); la hoja real varía un poco
     de eso según el ancho elegido, pero sirve de aproximación estable.
     Una vez cargada, el propio <img> con height:auto manda igual. */
  aspect-ratio: 490 / 200;
}
.cc-vista-previa img { display: block; width: 100%; height: auto; }
.cc-vista-previa[hidden] { display: none; }

/* ── Aviso del tope gratuito → botón de compra ────────────────────────
   .cc-boton-compra hereda todo de .tp-boton/.tp-boton--primario/.cc-trapecio;
   sólo necesita separarse un poco de notaLicencia, que ya trae su propio
   margin-bottom (14px, ver .cc-nota) — sin este margin-top quedaban pegados.
   --sesgo y padding-inline son los mismos que #btnExportPDF (mismo alto de
   28px): así el trapecio sale idéntico, no una aproximación a ojo. */
.cc-boton-compra {
  margin: -4px 0 14px;
  --sesgo: 6px;
  padding-inline: 26px;
}
.cc-boton-compra[hidden] { display: none; }
/* Mismo criterio que #btnExportPDF (ver ahí el porqué): el recorte de
   .cc-trapecio se lleva el borde, así que el hover oscuro de .tp-boton de
   base se apagaría contra el fondo del modal si no se pisa acá. */
#btnAbrirCompra:hover, #btnAbrirCompra:focus-visible {
  opacity: .86;
  background: var(--acento);
  color: var(--acento-texto);
}

/* ── Modal de compra ───────────────────────────────────────────────────
   Dos tarjetas seleccionables (radios reales, ocultos con .cc-solo-lector:
   el <label> entero es el área de clic, y :has() pinta la que está marcada
   — degrada con gracia en un navegador sin :has(), que simplemente no
   resalta ninguna: el radio sigue siendo clickeable y accesible igual). */
.cc-compra-opciones {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 14px;
}
.cc-compra-opcion {
  flex: 1 1 200px;
  display: block;
  cursor: pointer;
  background: var(--tecla);
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  padding: 12px 14px;
}
.cc-compra-opcion:has(input:checked) {
  border-color: var(--acento);
  box-shadow: 0 0 0 1px var(--acento) inset;
}
.cc-compra-opcion:focus-within { outline: 2px solid var(--acento); outline-offset: 2px; }
.cc-compra-opcion__nombre {
  display: block;
  font-size: .78rem; font-weight: 700; letter-spacing: .04em;
  color: var(--texto-brillante);
}
.cc-compra-opcion__precio {
  display: block;
  font-size: 1.3rem; font-weight: 700;
  color: var(--acento);
  margin: 4px 0 8px;
}
.cc-compra-opcion__specs {
  margin: 0; padding-left: 1.1em;
  font-size: .72rem; color: var(--texto-tenue);
  display: flex; flex-direction: column; gap: 3px;
}
.cc-field--correo input[type="email"] {
  background: var(--tecla); border: 1px solid var(--borde);
  border-radius: var(--radio-sm); color: var(--texto);
  padding: 8px 10px; font-family: inherit; font-size: .85rem;
}
.cc-field--correo input[type="email"]:focus-visible { outline: 1px solid var(--acento); outline-offset: 1px; }
.cc-compra-pie { text-align: right; margin-top: 6px; }
/* Mismo hover que #btnExportPDF (ver ahí el porqué): el recorte de
   .cc-trapecio se lleva el borde, y sin esto el hover oscuro de .tp-boton de
   base se apaga contra el fondo del modal en vez de resaltar. */
#btnComprar:hover, #btnComprar:focus-visible {
  opacity: .86;
  background: var(--acento);
  color: var(--acento-texto);
}
/* Sólo en el modal de pago: pegado al borde INFERIOR (cancela los 24px de
   padding-bottom de .cc-modal__cuerpo, sin dejar hueco) — no toca
   modalRestaurar ni modalResultado, que comparten la misma clase pero con
   pies más cortos, donde ese aire de abajo sí conviene dejarlo. */
#modalCompra .cc-compra-pie { margin-bottom: -24px; }

/* Enlace de texto suelto ("¿Ya compraste?"): ni trapecio ni tecla, para que
   no compita con el botón primario de abajo por atención. */
.cc-link {
  display: block;
  background: none; border: none; padding: 0;
  margin: -6px 0 14px;
  font-family: inherit; font-size: .74rem;
  color: var(--acento); text-decoration: underline;
  cursor: pointer;
}
.cc-link:hover, .cc-link:focus-visible { color: var(--acento-2); }

/* ── Pantalla de resultado (compra aprobada/rechazada/restaurada/…) ────
   Un solo layout centrado; compra.js decide cuál de los tres íconos mostrar
   (los otros dos quedan `hidden`) y escribe el título/texto. Es, además, el
   único hijo en flujo de .cc-modal acá (el cierre es absoluto): la región
   que scrollea si el texto no entra en max-height (ver .cc-modal). */
.cc-resultado {
  text-align: center;
  padding: 8px 4px 4px;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
.cc-resultado__icono { width: 48px; height: 48px; margin: 0 auto 14px; }
.cc-resultado__icono--ok { color: var(--acento); }
.cc-resultado__icono--error { color: var(--peligro); }
/* Gira mientras se confirma contra el servidor — es el único de los tres que
   representa un estado EN CURSO, no un desenlace ya resuelto. */
.cc-resultado__icono--espera { color: var(--texto-tenue); animation: cc-girar 1.4s linear infinite; }
@keyframes cc-girar { to { transform: rotate(360deg); } }
.cc-resultado h2 {
  margin: 0 0 8px; color: var(--texto-brillante); font-size: .95rem;
  letter-spacing: .04em;
}
.cc-resultado p { margin: 0; font-size: .82rem; color: var(--texto-tenue); }
.cc-resultado .cc-compra-pie { text-align: center; margin-top: 18px; }

/* ══ MÓVIL: una ventana debajo de otra, al ancho completo ══ */
/* Piezas exclusivas de móvil/tablet: en escritorio no existen. */
.cc-movil { display: none; }

/* ══ Tamaños intermedios y chicos ═══════════════════════════════════
   Tres escalones, no dos. El corte no es "escritorio o móvil": lo que manda
   es que el DESARROLLO es una tira de 2,45:1 y necesita ANCHO.

   En pantallas chicas cambia algo más que el acomodo: la ventana de info y
   la de imágenes dejan de ocupar sitio y pasan a abrirse a pantalla
   completa. No se duplica su contenido — es el MISMO nodo, puesto en
   position:fixed. Lo que se gana con eso es que el visor 3D quepa en la
   primera pantalla sin desplazar, que es de lo que se trata.

   El regrupado se hace con `display: contents` en las filas: sus hijos pasan
   a ser hijos directos de .cc-app y se reordenan con `order`, sin tocar el
   HTML (que sigue en el orden que le conviene al escritorio).

   OJO: el orden visual deja de coincidir con el del DOM, así que la
   navegación por teclado sigue el orden de escritorio. Es un compromiso
   consciente. */

/* ── Intermedio (tablet) y teléfono ───────────────── */
@media (max-width: 1100px) {
  html, body { height: auto; overflow: auto; }
  .cc-app { display: flex; flex-direction: column; height: auto; }
  .cc-fila { display: contents; }
  .cc-grip { display: none; }
  .cc-movil { display: flex; }

  /* Sin frontera vertical entre pares fijos: fuera los picos y los márgenes
     negativos que los acompañaban. */
  .cc-win--fuente, .cc-win--red,
  .cc-win--info, .cc-win--visor, .cc-win--export {
    clip-path: none; margin-left: 0; padding-left: 0;
    width: 100%; height: auto;
  }
  .cc-win { width: 100%; margin: 0 0 -1px 0; }
  .cc-fuente__cuerpo, .cc-fuente__ayuda, .cc-info__ayuda { margin-right: 0; }
  /* Ya está su gemelo en la cabecera móvil persistente (#idiomaMovil);
     mostrar los dos a la vez al abrir este panel sería redundante. */
  #idiomaEscritorio { display: none; }
  /* La excepción de escritorio (ver arriba) no aplica acá: la ventana de
     info pasa a panel angosto y el pie vuelve a leerse mejor centrado. */
  .cc-info__ayuda { justify-content: center; align-self: center; text-align: center; }
  /* Acá la ventana se abre como panel a pantalla completa: el rótulo vuelve,
     porque es lo que dice qué se acaba de abrir. */
  .cc-fuente__cuerpo .cc-win__title { display: block; }
  .cc-info__cuerpo { padding-right: var(--gap-m); }

  /* Orden: lo que se ve primero es el objeto, no las herramientas. */
  .cc-movil__cab      { order: 1; }
  .cc-movil__acciones { order: 2; }
  .cc-win--red        { order: 3; }
  .cc-win--visor      { order: 4; }
  .cc-win--export     { order: 5; padding: var(--gap-s); }

  /* ── Exportación: una fila, alargados, ícono al lado del texto ─────
     Apilados y cuadrados ocupaban ~276 px de alto. En una fila con el
     ícono en línea bajan a ~48, y eso es lo que permite que quepan en la
     primera pantalla en vez de quedar debajo del pliegue. */
  .cc-win--export { flex-direction: row; align-items: center; justify-content: center; gap: var(--gap-s); }
  /* 2px, casi nada: los filos inclinados que se enfrentan son paralelos, así
     que la franja entre botones se lee como la juntura de dos piezas y no
     como separación — y para eso tiene que ser angosta. Va como valor fijo y
     no como --gap-s: ese token también es el relleno de varias barras, y
     bajarlo apretaría cosas que no tienen nada que ver con estos botones. */
  .cc-export__acciones { flex: 1; flex-direction: row; min-height: 0; gap: 2px; }
  /* Acá los botones van lado a lado y anchos, así que un paralelogramo
     entero se comería demasiado rótulo: sólo se inclinan los BORDES
     LATERALES, formando dos trapecios que encajan uno en otro — el primero
     con el lado de arriba más corto y el segundo con el de abajo más corto.
     Los dos filos del medio quedan paralelos, así que el hueco entre botones
     es parejo. El sesgo en px y no en % porque acá el ancho lo pone el
     contenedor: 10px sobre los ~46px de alto mantiene la misma inclinación
     del pico (0,22 de horizontal por 1 de vertical). */
  .cc-export-btn {
    width: auto; flex: 1;
    aspect-ratio: auto;
    min-height: 46px; height: auto;
    flex-direction: row;
    gap: 7px;
    padding: 6px 16px;
    font-size: .66rem;
    align-self: auto;
    border-radius: 0;
    box-shadow: none;
  }
  /* Descargar 2D (PDF) es el botón que más se usa, así que en móvil va a la
     DERECHA: más cerca del pulgar de quien sostiene el teléfono con esa
     mano. El orden visual se invierte con `order`, no moviendo el marcado
     — así el resto (escritorio, foco de teclado, lector de pantalla) no se
     entera. Los recortes se intercambian junto con la posición: cada uno
     lleva el filo recto del lado en el que de verdad queda. */
  #btnAbrirExport {
    order: 2;
    /* ahora a la derecha: lado inferior más corto, filo derecho recto. */
    clip-path: polygon(0 0, 100% 0, 100% 100%, 10px 100%);
  }
  #btnExportOBJ {
    order: 1;
    /* ahora a la izquierda: lado superior más corto, filo izquierdo recto. */
    clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 100%, 0 100%);
    background: var(--tecla);
  }
  .cc-ico { width: 19px; height: 19px; }

  /* ── Cabecera y acciones ─────────────────────────── */
  .cc-movil__cab {
    align-items: center; justify-content: space-between;
    gap: var(--gap-s);
    padding: var(--gap-s) var(--gap-m);
    border-bottom: 1px solid var(--borde);
  }
  /* Manda el ANCHO, no el alto: la versión móvil del logo es un rótulo de
     578 × 39 (14,7:1), así que fijarle 40px de alto lo dejaba de 589px de
     ancho — más que la pantalla entera de un teléfono. Con el ancho al
     mando, el alto sale de la proporción y ronda los 20px. */
  .cc-movil__logo { width: min(56vw, 300px); height: auto; display: block; }
  .cc-movil__cab-botones { display: flex; align-items: center; gap: 2px; }
  .cc-movil__acciones {
    gap: 2px;               /* ver el hueco de .cc-export__acciones */
    padding: var(--gap-s) var(--gap-m);
  }
  /* Mismos trapecios encajados que los botones de exportar de más abajo,
     pero al revés: acá el ANCHO va arriba en el primero. Los dos pares se
     leen entonces como un mismo sistema de piezas que embonan, y el orden
     invertido evita que las dos barras parezcan la misma repetida.
     El sesgo mantiene la inclinación del pico (0,22 de horizontal por 1 de
     vertical): 9px sobre los 42px de alto de estos botones. */
  .cc-movil__acciones .tp-boton {
    flex: 1; min-width: 0; height: 42px;
    font-size: .68rem; white-space: normal; line-height: 1.15; text-align: center;
    /* el recorte se lleva las esquinas redondeadas y la sombra: dejarlas
       puestas sólo deja ver medio borde, peor que no tenerlas */
    border-radius: 0;
    box-shadow: none;
    padding-inline: 14px;   /* deja el rótulo lejos de los filos inclinados */
  }
  /* El anillo de foco del navegador se dibuja FUERA de la caja, así que el
     clip-path se lo lleva entero y estos botones quedaban sin ninguna señal
     de foco para quien navega con teclado. La marca la da el fondo, que sí
     se ve completo dentro del trapecio. */
  .cc-movil__acciones .tp-boton:focus-visible {
    background: var(--borde);
    color: var(--texto-brillante);
    outline: none;
  }
  /* 0,22 × 42px de alto ≈ 9px. La forma la ponen las clases .cc-trapecio--a
     y --b del HTML; acá sólo va el sesgo que les toca a este tamaño. */
  .cc-movil__acciones .tp-boton { --sesgo: 9px; }
  .cc-ico-chico { width: 16px; height: 16px; flex-shrink: 0; }

  /* ── Desarrollo ──────────────────────────────────── */
  /* Tope de ancho: el desarrollo crece con el ancho disponible y en una
     tablet se comía 355 px de alto él solo, dejando al visor 3D con menos
     espacio que en un teléfono grande. A 560 px sus filas ya miden 43 px,
     de sobra para el dedo. */
  .cc-red-lienzo {
    aspect-ratio: 2.6;
    max-width: 560px;
    width: 100%;
    margin: 0 auto;
  }

  /* ── Visor 3D ────────────────────────────────────── */
  /* Lo que sobra de la primera pantalla. app.js mide lo OCUPADO por encima
     (cabecera, acciones, desarrollo) y la resta se hace acá, contra svh —
     el alto de pantalla CON la barra del navegador a la vista. Con vh la
     primera pantalla se pasaría de largo justo por esa barra, que es el
     desplazamiento que se quiere evitar.
     El max() es el piso: antes de quedar diminuto, prefiere desbordar un
     poco y que haya que desplazar. */
  .cc-visor__lienzo {
    height: max(180px, calc(100svh - var(--movil-ocupado, 300px)));
    flex: 0 0 auto;
  }
  /* Un dedo hacia abajo desplaza la página; hacia los lados gira el modelo.
     Con touch-action:none el visor se tragaba el gesto y dejaba al usuario
     sin poder desplazar. */
  .cc-visor__lienzo canvas { touch-action: pan-y; }

  body.cc-panel-abierto { overflow: hidden; }

  /* ── Ventana 2 como modal superpuesto (igual que "+info") ──
     Mismo patrón que ventana1, ver el comentario completo más abajo. Acá la
     tarjeta va más ancha (640 en vez de 560): el mosaico de muestras se
     achica con el ancho disponible (ajustarMosaico() lo mide en vivo, ver
     app.js), y una tarjeta angosta habría dejado las miniaturas muy chicas
     sin necesidad. */
  .cc-win--fuente {
    position: fixed; inset: 0; z-index: 60;
    margin: 0; border: none;
    background: rgba(0, 0, 0, .62);
    backdrop-filter: blur(6px);
    display: none;
    align-items: center; justify-content: center;
    padding: var(--gap-m);
    clip-path: none;
  }
  .cc-win--fuente.cc-abierto { display: flex; }
  /* Agarradera de "deslizar para cerrar" (ver cerrarConGesto en app.js):
     flotando sobre el padding de la tarjeta, pegada arriba del todo — igual
     que el cierre y el toggle de idioma, que ya usan position:absolute
     contra .cc-panel__tarjeta (relative) por el mismo motivo. 44px de alto
     como zona de agarre (el dedo necesita más que los 4px que se ven), la
     barrita visible la pinta ::after centrada adentro. */
  .cc-panel__agarradera {
    position: absolute; top: 0; left: 0; right: 0;
    height: 28px;
    display: flex; align-items: center; justify-content: center;
    touch-action: none;
    cursor: grab;
  }
  .cc-panel__agarradera::after {
    content: '';
    width: 36px; height: 4px;
    border-radius: 999px;
    background: var(--borde);
  }
  /* El rebote de vuelta cuando se suelta la agarradera sin pasar el umbral
     de cierre (ver app.js): mientras se arrastra, app.js apaga esta
     transición a mano (tiene que seguir al dedo sin demora) y la repone al
     soltar — ahí es cuando este valor entra a jugar. */
  .cc-panel__tarjeta { transition: transform .2s ease; }
  .cc-win--fuente .cc-panel__tarjeta {
    display: flex;
    flex-direction: column;
    position: relative;
    width: 100%;
    max-width: 640px;
    max-height: 88vh;
    overflow-y: auto;
    background: var(--bg2);
    border: 1px solid var(--borde);
    padding: 28px 24px 24px;
  }
  /* Igual que .cc-info__cuerpo en ventana1: el scroll y el padding ya los
     puso la tarjeta, así que el cuerpo pierde los suyos (eran del panel
     viejo, de alto fijo) y se deja medir por su contenido. */
  .cc-win--fuente .cc-fuente__cuerpo {
    flex: none;
    min-height: auto;
    margin-right: 0;
    padding: 0;
    overflow: visible;
  }
  /* Misma cuenta que la de ventana1: sin la tarjeta de por medio, esta barra
     estaba alineada a mano con el padding de escritorio de .cc-fuente__cuerpo
     (que ya no existe acá dentro); se resetea al valor por defecto. */
  .cc-win--fuente .cc-fuente__ayuda { padding-left: var(--gap-s); }

  /* ── Ventana 1 como modal superpuesto (igual que "+info") ──
     La sección ES el telón: fondo oscuro borroso, ocupa toda la pantalla
     pero no se ve como una página propia — .cc-panel__tarjeta (ver arriba,
     display:contents en escritorio) pasa a ser la tarjeta flotante y
     centrada de verdad, con su propio fondo/borde/recorte a 88vh, tal cual
     .cc-modal. */
  .cc-win--info {
    position: fixed; inset: 0; z-index: 60;
    margin: 0; border: none;
    background: rgba(0, 0, 0, .62);
    backdrop-filter: blur(6px);
    display: none;
    align-items: center; justify-content: center;
    padding: var(--gap-m);
    clip-path: none;   /* el pico de escritorio no aplica a un telón de fondo */
  }
  .cc-win--info.cc-abierto { display: flex; }
  .cc-win--info .cc-panel__tarjeta {
    display: flex;
    flex-direction: column;
    position: relative;
    width: 100%;
    max-width: 560px;
    max-height: 88vh;
    overflow-y: auto;
    background: var(--bg2);
    border: 1px solid var(--borde);
    /* El mismo padding que .cc-modal, salvo abajo: ese lo pone la propia
       barra de hints (ver más abajo), que es siempre lo último de la
       tarjeta — así queda pegada de verdad al borde, en vez de flotar a
       24px de él encima de su propio padding. */
    padding: 28px 24px 0;
  }
  /* Dentro de la tarjeta, el cuerpo ya no necesita ni su propio scroll (lo
     hace .cc-panel__tarjeta entera, igual que .cc-modal) ni el tamaño
     forzado a la ventana (flex:1/min-height:0 eran para el panel viejo, de
     alto fijo); acá el alto sale del contenido y la tarjeta se encarga del
     desborde. El padding también se anula: ya lo puso la tarjeta. */
  .cc-win--info .cc-info__cuerpo {
    flex: none;
    overflow: visible;
    padding: 0;
  }
  /* El desplazado a la izquierda de esta barra (padding-left: var(--gap-m),
     ver la regla de escritorio) estaba calibrado para alinearse con el
     padding-left de .cc-info__cuerpo — pero acá la barra vuelve a quedar
     CENTRADA (ver la regla general de tablet/teléfono), así que ya no hay
     nada con qué alinearla, y ese padding sólo le robaba ancho a "+info" /
     "proyectos hermanos" justo donde más apretados van. */
  /* La tarjeta ya no trae padding de abajo (ver esa regla): lo pone esta
     barra, que es siempre lo último — así queda pegada de verdad al borde
     en vez de flotar sobre el padding de la tarjeta. margin-top aparte,
     porque el intro de arriba también necesita su propio aire antes de la
     línea divisoria (border-top, ya la trae .cc-ayuda). */
  .cc-win--info .cc-info__ayuda {
    padding-left: 0;
    margin-top: 14px;
    padding-bottom: 14px;
  }
  /* "+info" y el enlace a proyectos hermanos envolvían a dos líneas dentro
     de la tarjeta angosta (560px, menos padding): acortar el texto no
     alcanzó solo, así que acá se aprieta el relleno lateral de estos dos
     botones en particular — sin tocar los de las demás barras (ventana4,
     "Vaciar fila"), que no tienen este problema. */
  .cc-win--info .cc-info__ayuda .cc-mini { padding-inline: 6px; }

  /* ── Los cuatro destinos, dentro del panel de muestras ── */
  .cc-destinos {
    display: grid; grid-template-columns: repeat(4, 1fr);
    gap: 6px; flex-shrink: 0;
  }
  .cc-destino {
    position: relative;
    aspect-ratio: 2 / 1;
    min-height: 44px;               /* objetivo cómodo para el dedo */
    background: var(--bg2);
    border: 1px dashed var(--borde);
    border-radius: var(--radio-sm);
    overflow: hidden; cursor: pointer; padding: 0;
    font-family: inherit; color: var(--texto-tenue);
    display: flex; align-items: center; justify-content: center;
  }
  .cc-destino--llena { border-style: solid; }
  .cc-destino--activa { border-color: var(--acento); box-shadow: inset 0 0 0 2px var(--acento); }
  .cc-destino img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .cc-destino__num {
    position: absolute; top: 2px; left: 5px;
    font-size: .62rem; font-weight: 700; color: var(--texto);
    text-shadow: 0 0 3px #000, 0 0 3px #000; pointer-events: none;
  }

  /* ── Mosaico de muestras dentro del panel ────────── */
  .cc-fuente__mosaico { flex-direction: column; align-items: stretch; }
  .cc-fuente__mosaico > * { height: auto; }
  .cc-gallery { flex-flow: row wrap; align-items: flex-start; align-content: flex-start; overflow: visible; }
  .cc-drop { aspect-ratio: auto; min-height: 62px; }
  .cc-drop__msg { flex-direction: row; }
  .cc-drop__msg br { display: none; }
  /* En el panel las miniaturas ya no compiten por espacio: más grandes, que
     es lo que hace falta para elegir con el dedo. */
  .cc-tile { height: auto; }
  .cc-tile__img { height: 72px; }
}

/* ── Horizontal: desarrollo y visor lado a lado ───────────────────────
   Con la pantalla acostada el alto es el recurso escaso y el ancho sobra.
   Apilados, en un iPad horizontal al visor le quedaban 181 px; repartidos
   en dos columnas se lleva 387. Se aplica desde 701 px para que entre
   también un teléfono acostado, donde apilar es directamente inviable. */
@media (min-width: 701px) and (max-width: 1100px) and (orientation: landscape) {
  .cc-app { flex-direction: row; flex-wrap: wrap; align-content: flex-start; }
  .cc-movil__cab, .cc-movil__acciones, .cc-win--export { width: 100%; }
  .cc-win--red   { width: 44%; }
  .cc-win--visor { width: 56%; }
  /* Los dos comparten el alto de la franja; el desarrollo no lo llena
     (va por proporción) y se centra en lo que le toca. */
  .cc-win--red, .cc-win--visor { height: max(180px, calc(100svh - var(--movil-ocupado, 260px))); }
  .cc-red-lienzo { margin: auto; }
}

/* ── Teléfono ─────────────────────────────────────── */
@media (max-width: 700px) {
  /* Más bajos acá, así que el sesgo baja con ellos para no cambiar el
     ángulo: 0,22 × 38 ≈ 8px. */
  .cc-movil__acciones .tp-boton {
    font-size: .62rem; height: 38px; padding-inline: 12px;
    --sesgo: 8px;                       /* 0,22 × 38 ≈ 8 */
  }
  .cc-movil__cab { padding: 6px var(--gap-s); }
  .cc-movil__acciones { padding: 6px var(--gap-s); }
  .cc-tile__img { height: 62px; }
  .cc-export-btn { font-size: .6rem; min-height: 42px; }
  .cc-ico { width: 17px; height: 17px; }
  /* Las barras de ayudas se aprietan y sueltan lo prescindible: en un
     teléfono cada renglón de barra se le resta al visor 3D. Sin esto, las
     cuatro ayudas del desarrollo envuelven a tres renglones (~46 px). */
  .cc-ayuda { font-size: .56rem; padding: 4px 6px; gap: 3px 10px; }
  .cc-ayuda__extra { display: none; }
  .cc-mini { font-size: .55rem; padding: 2px 5px; gap: 4px; }
  /* ~18px de alto acá: 0,22 × 18 ≈ 4px de sesgo, mismo ángulo. Sólo cambia
     la variable; la forma la siguen poniendo las clases de arriba. */
  .cc-ayuda--botones .cc-mini, .cc-mini.cc-trapecio { padding: 2px 9px; --sesgo: 4px; }
  .cc-mini__ico { width: 12px; height: 12px; }
}
