/* cistio.com — mecanografiado sobre papel.
   Sin JavaScript, sin recursos externos, sin fuentes remotas. */

:root {
  --papel:    #f2ebda;  /* papel crema, algo amarilleado por los años */
  --sombra:   #e2d7bf;  /* el mismo papel donde la luz no llega */
  --tinta:    #2c2722;  /* negro cálido de cinta entintada */
  --huella:   rgba(44, 39, 34, 0.18);
  --grano:    0.055;    /* cuánto grano deja ver el papel */
}

@media (prefers-color-scheme: dark) {
  :root {
    /* La misma hoja, leída a media luz. No un tema oscuro: papel en penumbra. */
    --papel:  #211d18;
    --sombra: #171410;
    --tinta:  #cfc4ad;
    --huella: rgba(0, 0, 0, 0.35);
    --grano:  0.09;
  }
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
  height: 100%;
  background: var(--papel);
}

.hoja {
  position: relative;
  height: 100%;
  display: grid;
  place-items: center;
  overflow: hidden;
  padding: 2rem;
  /* El papel no es plano: se ahueca hacia los bordes. */
  background:
    radial-gradient(ellipse at 50% 40%, var(--papel) 40%, var(--sombra) 140%);
}

/* Grano del papel. Con gradientes repetidos siempre acaba viéndose una
   rejilla —da igual lo primos que sean los periodos—, así que el grano es
   ruido fractal de verdad: un SVG con feTurbulence embebido como data URI.
   La CSP lo permite porque `img-src` admite `data:`. */
.fibras {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: var(--grano);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}

.mecanografiado {
  position: relative;
  z-index: 1;
  font-family: "Courier New", Courier, "Nimbus Mono PS", monospace;
  font-size: clamp(1.6rem, 9vw, 4.2rem);
  font-weight: 700;
  /* El paso fijo del carro: cada tipo ocupa lo mismo, quepa o no. */
  letter-spacing: 0.08em;
  text-indent: 0.08em;
  color: var(--tinta);
  white-space: nowrap;
}

/* Cada letra lleva su propio desvío, giro, tinta y momento de golpe. El
   transform vive en variables porque los keyframes también lo tocan: si la
   animación lo redefiniera sin más, se llevaría por delante el temblor. */
.mecanografiado span {
  display: inline-block;
  --dy:  0em;
  --rot: 0deg;
  --ent: 1;      /* cuánta tinta recibió el tipo */
  --t:   0s;     /* cuándo lo golpea la mecanógrafa */
  transform: translateY(var(--dy)) rotate(var(--rot));
  opacity: var(--ent);
  text-shadow: 0.02em 0.02em 0 var(--huella);
  animation: golpe 0.07s steps(2, end) var(--t) backwards;
}

/* El tipo llega lanzado y se detiene contra el papel: aparece de golpe, sin
   fundido. Un fundido suave sería una pantalla encendiéndose, no un martillo. */
@keyframes golpe {
  from {
    opacity: 0;
    transform: translateY(calc(var(--dy) - 0.06em)) rotate(var(--rot)) scale(1.22);
  }
  to {
    opacity: var(--ent);
    transform: translateY(var(--dy)) rotate(var(--rot)) scale(1);
  }
}

/* La primera letra nunca se anima, y es deliberado: garantiza que hay algo
   pintado en el primer fotograma. Si el navegador congela las animaciones
   —pestaña en segundo plano— el resto no llegará, pero la página no se queda
   en blanco y el First Contentful Paint se registra igual. */
.mecanografiado span:nth-child(1) { animation: none; }

/* Ni los desvíos ni los tiempos siguen progresión alguna: en cuanto se
   ordenan, el ojo detecta el patrón y deja de parecer una mecanógrafa para
   parecer un efecto. Los intervalos van de 90 a 190 ms, el pulso de alguien
   que escribe con soltura pero no es un metrónomo. */
.mecanografiado span:nth-child(1)  { --dy:  0.030em; --rot: -1.2deg; --ent: 0.88; --t: 0s;    }
.mecanografiado span:nth-child(2)  { --dy: -0.055em; --rot:  1.6deg; --ent: 1;    --t: 0.13s; }
.mecanografiado span:nth-child(3)  { --dy:  0.062em; --rot:  0.7deg; --ent: 0.78; --t: 0.24s; }
.mecanografiado span:nth-child(4)  { --dy: -0.018em; --rot: -1.9deg; --ent: 0.95; --t: 0.43s; }
.mecanografiado span:nth-child(5)  { --dy:  0.075em; --rot:  0.4deg; --ent: 0.81; --t: 0.52s; }
.mecanografiado span:nth-child(6)  { --dy: -0.040em; --rot: -0.9deg; --ent: 1;    --t: 0.66s; }
.mecanografiado span:nth-child(7)  { --dy:  0.088em; --rot:  2.1deg; --ent: 0.78; --t: 0.85s; }
.mecanografiado span:nth-child(8)  { --dy: -0.028em; --rot:  1.1deg; --ent: 0.92; --t: 0.94s; }
.mecanografiado span:nth-child(9)  { --dy:  0.048em; --rot: -1.5deg; --ent: 0.85; --t: 1.06s; }
.mecanografiado span:nth-child(10) { --dy: -0.068em; --rot:  0.8deg; --ent: 0.98; --t: 1.19s; }

/* Las letras que recibieron más tinta se empastan un poco en el contorno. */
.mecanografiado span:nth-child(2),
.mecanografiado span:nth-child(6),
.mecanografiado span:nth-child(10) {
  text-shadow: 0.02em 0.02em 0 var(--huella), 0 0 0.035em var(--huella);
}

/* Donde se detiene el carro, esperando el siguiente tipo. Aparece cuando la
   última letra ya ha caído. */
.carro {
  display: inline-block;
  width: 0.07em;
  height: 0.9em;
  margin-left: 0.06em;
  vertical-align: -0.06em;
  background: var(--tinta);
  opacity: 0;
  animation: esperar 1.06s steps(1) 1.32s infinite;
}

@keyframes esperar {
  0%, 49%   { opacity: 0.7; }
  50%, 100% { opacity: 0; }
}

/* Quien prefiera no ver movimiento recibe la línea ya escrita. */
@media (prefers-reduced-motion: reduce) {
  .mecanografiado span { animation: none; }
  .carro { animation: none; opacity: 0.7; }
}
