¡Felicitaciones por el Año Nuevo 2025! Hace unos años escribí sobre cómo hacer un árbol de Navidad con funciones; esta vez, el cuento tratará sobre un árbol de Navidad con símbolos Unicode. La restricción es que debe haber música, y el resultado debe caber en un código QR.

Árbol de Navidad decorado con nieve cayendo, creado con HTML y JavaScript.
Código HTML y JS para un Árbol de Navidad interactivo.

Idea y limitaciones

Los navegadores modernos admiten dataUrl, un formato especial que almacena todos los datos directamente en la URL. Pueden ser imágenes, texto y cualquier otro formato de datos. De todo esto, solo nos interesa el texto; examinémoslo más de cerca:

data:[<media-type>][;base64],<data>

Dado que dataUrl se inserta en la barra de direcciones, esto impone restricciones a los símbolos que se pueden usar: [a-zA-Z0-9$-_.+!*'()] . Entre estos símbolos no hay comillas angulares necesarias para las etiquetas html, #, necesaria para el color, espacios y muchos otros símbolos. Dichos símbolos se reemplazarán con secuencias de escape de URL que comienzan con % (%20 – espacio).

A pesar de que los navegadores procesan correctamente las comillas angulares, el espacio y el símbolo # interrumpen el análisis (en el espacio – Chrome, en # – Firefox), como resultado, el usuario llega a la página de búsqueda, y no a la página almacenada en dataUrl.

Una de las soluciones es usar base64, que permite escribir cualquier símbolo, a costa de un mayor volumen en bytes.

El dataUrl resultante ocupa 22 caracteres y comienza con:

data:text/html;base64,

En el código QR más grande, como máximo se pueden escribir 2953 caracteres; teóricamente, el tamaño del código QR no está limitado, pero 2953 caracteres es la compatibilidad garantizada en la mayoría de los escáneres.

Resto: 2953 – 22 = 2931 caracteres de codificación base64, lo que equivale a 2931 * 6/8 = 2198 bytes o caracteres ASCII.

2198 caracteres, no es tan poco. ¡Vamos!

Maquetación de la página

Html estático:

<html>
<head>
  <meta name=viewport content=width=device-width,initial-scale=1.0>
  <style>
    body {
        overflow: hidden;
        display: flex;
        justify-content: center
    }
    #X {
        margin: auto;
        font-size: calc(min(70vw, 40vh));
        line-height: 1em;
        position: relative
    }
    #Y, .A {
        position: absolute
    }
  </style>
</head>
<body>
<div class=A style=z-index:9>
  Por <a href=https://codigonautas.com/ target=_blank>CodigoNautas.com</a> 2024<br>
  Haz clic abajo para reproducir
</div>
<div id=Y></div>
<div id=X></div>
</body>
<script>
</script>
</html>

Para empezar, alinearemos todo al centro en el cuerpo y ocultaremos todo lo que esté fuera de la pantalla: overflow hidden, display flex, justify-content center.

En el bloque div con id Y estarán los copos de nieve, y con id X estará el árbol de Navidad, los regalos, los muñecos de nieve y el texto «2025«. Para que el árbol de Navidad se vea bonito tanto en monitores anchos como en teléfonos, es necesario limitar el tamaño de la fuente (que implícitamente es el tamaño del árbol de Navidad) al mínimo del 70% del ancho de la pantalla y el 40% de la altura de la pantalla (calc(min(70vw, 40vh))) – en la computadora y en el teléfono, el árbol de Navidad se ubicará en el centro de la pantalla y no se saldrá de sus bordes.

Asegúrate de especificar position relative para colocar los regalos y los muñecos de nieve junto al árbol de Navidad.

Los regalos y los muñecos de nieve son, en esencia, código html repetido, por lo que para reducir los caracteres, generaremos los regalos y los muñecos de nieve a través de js y los colocaremos en innerHTML del bloque X.

// función para convertir un número a formato hex, necesaria para el color de los muñecos de nieve y los copos de nieve
let S = e => e.toString(16);

// como hemos establecido el id del bloque, podemos acceder directamente, sin document.getElementById
X.innerHTML =
// árbol de Navidad
'&#127876;' +
// coordenadas x de los regalos
[8, 23, 57, 72]
  .map(e => `<div class=A style=font-size:20%;line-height:20%;`
       +`bottom:1%;left:${e}%;z-index:1>&#127873;</div>`)
  .join('')
// coordenadas x e y de los muñecos de nieve
+ [[82, 10], [78, 65], [95, 38]] 
  .map(([a, b], i) => `<div class=A style=font-size:50%;`
       // el color del muñeco de nieve depende del número de orden 
       +`bottom:-${a}%;left:${b}%;color:#${S(8 + i * 3)}df;z-index:2>&#9731;</div>`)
  .join('')
+ `<div class=A style=font-size:40%;top:-80%;text-align:center;`
  + `width:100%;font-family:sans-serif;color:#9df>2025</div>`;

Como puede verse, las líneas y las mismas subcadenas se repiten muchas veces: div, z-index, color, las extraeremos por separado y las sustituiremos mediante concatenación.

let A='<div class=A style=font-size:',W=';z-index:',J=';color:#';

X.innerHTML =
'&#127876;' + 
[8, 23, 57, 72]
  .map(e => A + '20%;line-height:20%;bottom:1%;left:' + e + '%' + W + '1>&#127873;</div>')
  .join('') + 
[[82, 10], [78, 65], [95, 38]]
  .map(([a, b], i) => A + '50%;bottom:-' + a + '%;left:' + b + '%' + J + S(8 + i * 3) + 'df' + W + '2>&#9731;</div>')
  .join('') + 
A + '40%;top:-80%;text-align:center;width:100%;font-family:sans-serif' + J + '9df>2025</div>';

Bastante óptimo.

Procederemos de manera similar con los copos de nieve, pero los actualizaremos cada 20 ms en el bloque Y.

let V = 0;

setInterval(e => {
  V++;
  e = '';
  for (let i = 50; i < 100; i++) 
    e += A + `${2 + i % 7}vh;top:${(i * 57 - 10 + V / i * 7) % 105}vh;left:${(i * 23 + V / i * 5) % 107 - 53}vw`
      + J + S(i % 7 + 5) + S(i % 5 + 9) + 'f' + W + `${i % 5}>&#10052;</div>`;
  Y.innerHTML = e
}, 20);

Lo que sucede aquí:

Cada 20 ms se llama una función que incrementa V en una unidad y actualiza todos los copos de nieve en el campo. El copo de nieve es &#10052. Cada copo de nieve está en su propio bloque div, con un tamaño de fuente individual: 2 + i % 7 y una posición pseudoaleatoria.

La posición para cada una de las coordenadas se calcula en función del punto inicial, que solo depende del número de orden del copo de nieve y del «tiempo», la variable V, dividida por el número de orden del copo de nieve. Debido a esto, cada copo de nieve tiene su propio tamaño, velocidad y trayectoria de movimiento. Los números para la multiplicación y para encontrar el resto de la división se han seleccionado para que el ojo no perciba el patrón. Esto es lo que sucederá si cambias los valores en un par de unidades:

[Imagen mostrando copos de nieve con diferentes multiplicadores]

DataUrl actual: cópialo y pégalo en la barra de direcciones de tu navegador.

data:text/html;base64,PGh0bWw+CjxoZWFkPgogIDxtZXRhIG5hbWU9dmlld3BvcnQgY29udGVudD13aWR0aD1kZXZpY2Utd2lkdGgsaW5pdGlhbC1zY2FsZT0xLjA+CiAgPHN0eWxlPgogICAgYm9keSB7CiAgICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsKICAgICAgICBkaXNwbGF5OiBmbGV4OwogICAgICAgIGp1c3RpZnktY29udGVudDogY2VudGVyCiAgICB9CiAgICAjWCB7CiAgICAgICAgbWFyZ2luOiBhdXRvOwogICAgICAgIGZvbnQtc2l6ZTogY2FsYyhtaW4oNzB2dywgNDB2aCkpOwogICAgICAgIGxpbmUtaGVpZ2h0OiAxZW07CiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlCiAgICB9CiAgICAjWSwgLkEgewogICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZQogICAgfQogIDwvc3R5bGU+CjwvaGVhZD4KPGJvZHk+CjxkaXYgY2xhc3M9QSBzdHlsZT16LWluZGV4Ojk+CiAgUG9yIDxhIGhyZWY9aHR0cHM6Ly9jb2RpZ29uYXV0YXMuY29tLyB0YXJnZXQ9X2JsYW5rPkNvZGlnb05hdXRhcy5jb208L2E+IDIwMjQ8YnI+CiAgSGF6IGNsaWMgYWJham8gcGFyYSByZXByb2R1Y2lyCjwvZGl2Pgo8ZGl2IGlkPVk+PC9kaXY+CjxkaXYgaWQ9WD48L2Rpdj4KPC9ib2R5Pgo8c2NyaXB0PgpsZXQgUyA9IGUgPT4gZS50b1N0cmluZygxNik7ClguaW5uZXJIVE1MID0gJyYjMTI3ODc2OycgKwpbOCwgMjMsIDU3LCA3Ml0KICAubWFwKGUgPT4gYDxkaXYgY2xhc3M9QSBzdHlsZT1mb250LXNpemU6MjAlO2xpbmUtaGVpZ2h0OjIwJTtib3R0b206MSU7bGVmdDoke2V9JTt6LWluZGV4OjE+JiMxMjc4NzM7PC9kaXY+YCkKICAuam9pbignJykgKwpbWzgyLCAxMF0sIFs3OCwgNjVdLCBbOTUsIDM4XV0KICAubWFwKChbYSwgYl0sIGkpID0+IGA8ZGl2IGNsYXNzPUEgc3R5bGU9Zm9udC1zaXplOjUwJTtib3R0b206LSR7YX0lO2xlZnQ6JHtifSU7Y29sb3I6IyR7Uyg4ICsgaSAqIDMpfWRmO3otaW5kZXg6Mj4mIzk3MzE7PC9kaXY+YCkKICAuam9pbignJykgKwpgPGRpdiBjbGFzcz1BIHN0eWxlPWZvbnQtc2l6ZTo0MCU7dG9wOi04MCU7dGV4dC1hbGlnbjpjZW50ZXI7d2lkdGg6MTAwJTtmb250LWZhbWlseTpzYW5zLXNlcmlmO2NvbG9yOiM5ZGY+MjAyNTwvZGl2PmA7CgpsZXQgQT0nPGRpdiBjbGFzcz1BIHN0eWxlPWZvbnQtc2l6ZTonLFc9Jzt6LWluZGV4OicsSj0nO2NvbG9yOiMnOwoKWC5pbm5lckhUTUwgPQonJiMxMjc4NzY7JyArIApbOCwgMjMsIDU3LCA3Ml0KICAubWFwKGUgPT4gQSArICcyMCU7bGluZS1oZWlnaHQ6MjAlO2JvdHRvbToxJTtsZWZ0OicgKyBlICsgJyUnICsgVyArICcxPiYjMTI3ODczOzwvZGl2PicpCiAgLmpvaW4oJycpICsgCltbODIsIDEwXSwgWzc4LCA2NV0sIFs5NSwgMzhdXQogIC5tYXAoKFthLCBiXSwgaSkgPT4gQSArICc1MCU7Ym90dG9tOi0nICsgYSArICclO2xlZnQ6JyArIGIgKyAnJScgKyBKICsgUyg4ICsgaSAqIDMpICsgJ2RmJyArIFcgKyAnMj4mIzk3MzE7PC9kaXY+JykKICAuam9pbignJykgKyAKQSArICc0MCU7dG9wOi04MCU7dGV4dC1hbGlnbjpjZW50ZXI7d2lkdGg6MTAwJTtmb250LWZhbWlseTpzYW5zLXNlcmlmJyArIEogKyAnOWRmPjIwMjU8L2Rpdj4nOwoKbGV0IFYgPSAwOwoKc2V0SW50ZXJ2YWwoZSA9PiB7CiAgVisrOwogIGUgPSAnJzsKICBmb3IgKGxldCBpID0gNTA7IGkgPCAxMDA7IGkrKykgCiAgICBlICs9IEEgKyBgJHsyICsgaSAlIDd9dmg7dG9wOiR7KGkgKiA1NyAtIDEwICsgViAvIGkgKiA3KSAlIDEwNX12aDtsZWZ0OiR7KGkgKiAyMyArIFYgLyBpICogNSkgJSAxMDcgLSA1M312d2AKICAgICAgKyBKICsgUyhpICUgNyArIDUpICsgUyhpICUgNSArIDkpICsgJ2YnICsgVyArIGAke2kgJSA1fT4mIzEwMDUyOzwvZGl2PmA7CiAgWS5pbm5lckhUTUwgPSBlCn0sIDIwKTsKPC9zY3JpcHQ+CjwvaHRtbD4K

1440 caracteres de 2953, o 1041 de 2198 – ¡aún hay espacio!

Música

  • Arbolito – hay.
  • Copos de nieve – hay.
  • Falta música navideña.

Después de algunas reflexiones, la elección recayó en la melodía Carol of the Bells. En el secuenciador online se encontró un archivo midi adecuado con una pequeña cantidad de notas.

Comencemos con la generación de una nota de piano. Para empezar, el volumen de la nota no es lineal y cambia aproximadamente así:

[Enlace a información adicional sobre ADSR]

En segundo lugar, la nota tiene muchos armónicos. Los armónicos se atenúan al aumentar la frecuencia. Para simplificar, solo tendremos en cuenta la nota y 2 armónicos (x2 y x3).

El volumen de la nota cambiará según la siguiente fórmula:

Gráfica que muestra la relación entre la nota y la amplitud según una fórmula matemática.
Representación gráfica de la fórmula de nota y amplitud.

Suficientemente cerca de ADSR.

Lo mismo en js:

let F = (N, j, D) => 
Math.sin(27.5 * Math.pow(1.059463, N) * j / 3820)
* Math.exp(-j / 2500 / D) * Math.min(1, j / 250);

N – número de nota, j – tic actual, D – longitud de la nota (en fracciones, múltiplos de 0.25)

El número mágico 3820 se obtiene dividiendo la frecuencia de muestreo 24000 entre 2 pi: 24000 / 6.2832 = 3820.

27.5 * Math.pow(1.059463, N) – esta es la fórmula de la frecuencia de la nota. La frecuencia de la nota se duplica cada 12 notas, es decir, conociendo la frecuencia de la primera nota, se pueden calcular las frecuencias de todas las notas. 1.059463 – raíz duodécima de 2.

Para convertir .mid a una versión más compacta, escribí un script simple que genera una cadena de pares de símbolos. El primer símbolo de cada par es el número de la nota, el segundo es la duración y el retraso después del inicio de la nota anterior. Para mayor compacidad, agregué un diccionario de duraciones, por lo que deberá volver a hacerse para cada archivo .mid.

Código en Python

import mido
import struct


midi_file = mido.MidiFile('sound.mid')
notes = []
curr_play = {}
time = 0

for i, track in enumerate(midi_file.tracks):
    print(f"Дорожка {i}: {track.name if track.name else '<без имени>'}")
    for msg in track:
        time += msg.time
        if msg.type == 'note_on':
            if msg.note not in curr_play:
                curr_play[msg.note] = time
        elif msg.type == 'note_off':
            if msg.note in curr_play:
                start = curr_play[msg.note]
                delta = time - start
                del curr_play[msg.note]
                notes.append([msg.note, start, delta])
notes.sort(key=lambda x: x[1])
print(len(notes))
binary = bytes()
prev = 0

# duración de las notas
m = {1: 0, 2: 1, 6: 2, 8: 3, 12: 4, 30: 5}
for e in notes:
    note, start, duration = e
    start //= 96
    duration //= 96
    delta = start - prev
    prev = start
    binary += struct.pack('BB', note - 21, 35 + delta * len(m) + m[duration])

print(binary)
print(prev / 96 * 24000)

Se obtiene una cadena como esta

let M = `C$B/C)@*C0B/C)@*C0B/C)@*C0B/C)@*C04%B/C)@*C02%B/C)@*C00%B/C)@*C0/%B/C)@*C04%B/C)@*C02%B/C)@*C00%B/C)@*C0/%B/C)@*C0(%B/C)@*C0&%B/C)@*C0$%B/C)@*C0/%B/C)@*G04%E/G)C*G02%E/G)C*G00%E/G)C*G0/%E/G)C*L04%L/L)J)H)G*2%G/G)E)C)E*0%E/E)G)E)C*/%B/C)@*;//'=)?)@)B)C)E)G)E*C0G//'I)K)L)N)O)Q)S)Q*O0O04%N/O)L*O02%N/O)L*O00%N/O)L*O0/%N/O)L*O04%N/O)L*O02%N/O)L*O00%N/O)L*O0/%N/O)L*O04%N/O)L*O02%N/O)L*O00%N/O)L*O0/%N/O)L*O04%N/O)L*L02%L/L)L)H)G*0%G/G)E)C)E*/%E/E)G)E)C*/%B/C)@*G//'I)K)L)N)O)Q)S)Q*O0G//'I)K)L)N)O)Q)S)Q*O0O04%N/O)L*O02%N/O)L*O00%N/O)L*O0/%N/O)L*S04%Q/S)O*S02%Q/S)O*S00%Q/S)O*S0/(Q/S)O*O0N/O)L*O0N/O)L*O0N/O)L*O0N/O)L*O04(N/O)L*O0N/O)L*O0N/O)L*O0N/O)L,`

Tiene muchas repeticiones 🙂

Reproductor musical

Ahora necesitamos reproducir la melodía; al cargar la página, esto es imposible, solo después de la acción del usuario, por ejemplo, haciendo clic en el bloque X, es lo suficientemente grande como para que el usuario pueda tocarlo con el dedo 🙂

Código del reproductor en js, minificado.

X.onclick = (
  a, // este es el único parámetro que no está indefinido, el evento de clic
  l, t, i, j, N, I, D, W,  // todos indefinidos
  P = 0,
  f = new AudioContext /* js permite llamar al constructor sin paréntesis */) => {
  X.onclick = null;  // protección contra clics repetidos

  // t - búfer de audio creado de 2400000 muestras y una frecuencia de muestreo de 24 kHz
  // l - Float32Array, una matriz de muestras
  l = (t = f.createBuffer(1, 24e5, 24e3)).getChannelData(0);

  // bucle para todas las notas, M - cadena de notas
  for (i = 0; i < M.length; i++) {
    // el primer símbolo es la nota, no perdemos espacio y pasamos inmediatamente al siguiente símbolo
    N = M[i++].charCodeAt();
    // el segundo es la duración y el retraso
    I = M[i].charCodeAt() - 35;
    // la duración se toma del "diccionario"
    D = [1, 2, 6, 8, 12, 30][I % 6];
    // y el retraso se calcula directamente, para el midi tomado no supera los 2
    W = I / 6 | 0;
    // momento de inicio de la nota actual. 6e3 = 6000 = 1/4 de segundo,
    // a una frecuencia de muestreo de 24 kHz
    P += W * 6e3;

    // generación de la nota
    for (j = 0; j < D * 6e3; j++)
      // agregamos la nota y dos armónicos al búfer
      l[P + j] += F(N, j, D) * .35 + F(N + 12, j, D) * .1 + F(N + 24, j, D) * .05
  }
  // creamos AudioBufferSourceNode, que se puede reproducir,
  // le pasamos el búfer en el que escribimos las muestras
  (i = f.createBufferSource()).buffer = t;
  // conectamos con la salida
  i.connect(f.destination);
  // reproducimos desde el principio
  i.start(0);
};

DataUrl resultante: cópialo y pégalo en la barra de direcciones de tu navegador.

data:text/html;base64,PGh0bWw+CjxoZWFkPgogIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsaW5pdGlhbC1zY2FsZT0xLjAiPgogIDxzdHlsZT4KICAgIGJvZHkgeyBvdmVyZmxvdzogaGlkZGVuOyBkaXNwbGF5OiBmbGV4OyBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsgfQogICAgI1ggeyBtYXJnaW46IGF1dG87IGZvbnQtc2l6ZTogY2FsYyhtaW4oNzB2dywgNDB2aCkpOyBsaW5lLWhlaWdodDogMWVtOyBwb3NpdGlvbjogcmVsYXRpdmU7IH0KICAgICNZLCAuQSB7IHBvc2l0aW9uOiBhYnNvbHV0ZTsgfQogIDwvc3R5bGU+CjwvaGVhZD4KPGJvZHk+CjxkaXYgY2xhc3M9IkEiIHN0eWxlPSJ6LWluZGV4OjkiPlBvciA8YSBocmVmPSJodHRwczovL2NvZGlnb25hdXRhcy5jb20vIiB0YXJnZXQ9Il9ibGFuayI+Q29kaWdvTmF1dGFzLmNvbTwvYT4gMjAyNDxicj5IYXogY2xpYyBhYmFqbyBwYXJhIHJlcHJvZHVjaXI8L2Rpdj4KPGRpdiBpZD0iWSI+PC9kaXY+PGRpdiBpZD0iWCI+PC9kaXY+CjxzY3JpcHQ+CiAgbGV0IFMgPSBlID0+IGUudG9TdHJpbmcoMTYpOwogIFguaW5uZXJIVE1MID0gJyYjMTI3ODc2OycgKyBbOCwyMyw1Nyw3Ml0ubWFwKGUgPT4gYDxkaXYgY2xhc3M9QSBzdHlsZT1mb250LXNpemU6MjAlO2xpbmUtaGVpZ2h0OjIwJTtib3R0b206MSU7bGVmdDoke2V9JTt6LWluZGV4OjE+JiMxMjc4NzM7PC9kaXY+YCkuam9pbignJykgKwogICAgW1s4MiwxMF0sWzc4LDY1XSxbOTUsMzhdXS5tYXAoKFthLGJdLGkpID0+IGA8ZGl2IGNsYXNzPUEgc3R5bGU9Zm9udC1zaXplOjUwJTtib3R0b206LSR7YX0lO2xlZnQ6JHtifSU7Y29sb3I6IyR7Uyg4K2kqMyl9ZGY7ei1pbmRleDoyPiYjOTczMTs8L2Rpdj5gKS5qb2luKCcnKSArCiAgICBgPGRpdiBjbGFzcz1BIHN0eWxlPWZvbnQtc2l6ZTo0MCU7dG9wOi04MCU7dGV4dC1hbGlnbjpjZW50ZXI7d2lkdGg6MTAwJTtmb250LWZhbWlseTpzYW5zLXNlcmlmO2NvbG9yOiM5ZGY+MjAyNTwvZGl2PmA7CgogIGxldCBBPSc8ZGl2IGNsYXNzPUEgc3R5bGU9Zm9udC1zaXplOicsVz0nO3otaW5kZXg6JyxKPSc7Y29sb3I6Iyc7CiAgWC5pbm5lckhUTUwgPSAnJiMxMjc4NzY7JyArIFs4LDIzLDU3LDcyXS5tYXAoZSA9PiBBICsgJzIwJTtsaW5lLWhlaWdodDoyMCU7Ym90dG9tOjElO2xlZnQ6JyArIGUgKyAnJScgKyBXICsgJzE+JiMxMjc4NzM7PC9kaXY+Jykuam9pbignJykgKyAKICAgIFtbODIsMTBdLFs3OCw2NV0sWzk1LDM4XV0ubWFwKChbYSxiXSxpKSA9PiBBICsgJzUwJTtib3R0b206LScgKyBhICsgJyU7bGVmdDonICsgYiArICclJyArIEogKyBTKDgraSozKSArICdkZicgKyBXICsgJzI+JiM5NzMxOzwvZGl2PicpLmpvaW4oJycpICsgCiAgICBBICsgJzQwJTt0b3A6LTgwJTt0ZXh0LWFsaWduOmNlbnRlcjt3aWR0aDoxMDAlO2ZvbnQtZmFtaWx5OnNhbnMtc2VyaWYnICsgSiArICc5ZGY+MjAyNTwvZGl2Pic7CgogIGxldCBWID0gMDsKICBzZXRJbnRlcnZhbChlID0+IHsKICAgIFYrKzsKICAgIGUgPSAnJzsKICAgIGZvciAobGV0IGkgPSA1MDsgaSA8IDEwMDsgaSsrKSAKICAgICAgZSArPSBBICsgYCR7MiArIGkgJSA3fXZoO3RvcDokeyhpICogNTcgLSAxMCArIFYgLyBpICogNykgJSAxMDV9dmg7bGVmdDokeyhpICogMjMgKyBWIC8gaSAqIDUpICUgMTA3IC0gNTN9dndgCiAgICAgICAgKyBKICsgUyhpICUgNyArIDUpICsgUyhpICUgNSArIDkpICsgJ2YnICsgVyArIGAke2kgJSA1fT4mIzEwMDUyOzwvZGl2PmA7CiAgICBZLmlubmVySFRNTCA9IGUKICB9LCAyMCk7CgogIGxldCBGID0gKE4saixEKSA9PiBNYXRoLnNpbigyNy41ICogTWF0aC5wb3coMS4wNTk0NjMsIE4pICogaiAvIDM4MjApICogTWF0aC5leHAoLWogLyAyNTAwIC8gRCkgKiBNYXRoLm1pbigxLCBqIC8gMjUwKTsKICBsZXQgTSA9IGBDJEIvQylAKkMwQi9DKUAqQzBCL0MpQCpDMEIvQylAKkMwNCVCL0MpQCpDMDIlQi9DKUAqQzAwJUIvQylAKkMwLyVCL0MpQCpDMDQlQi9DKUAqQzAyJUIvQylAKkMwMCVCL0MpQCpDMC8lQi9DKUAqQzAoJUIvQylAKkMwJiVCL0MpQCpDMCQlQi9DKUAqQzAvJUIvQylAKkcwNCVFL0cpQypHMDIlRS9HKUMqRzAwJUUvRylDKkcwLyVFL0cpQypMMDQlTC9MKUopSClHKjIlRy9HKUUpQylFKjAlRS9FKUcpRSlDKi8lQi9DKUAqOy8vJz0pPylAKUIpQylFKUcpRSpDMEcvLydJKUspTClOKU8pUSlTKVEqTzBPMDQlTi9PKUwqTzAyJU4vTylMKk8wMCVOL08pTCpPMC8lTi9PKUwqTzA0JU4vTylMKk8wMiVOL08pTCpPMDAlTi9PKUwqTzAvJU4vTylMKk8wNCVOL08pTCpPMDIlTi9PKUwqTzAwJU4vTylMKk8wLyVOL08pTCpPMDQlTi9PKUwqTDAyJUwvTClMKUgpRyowJUcvRylFKUMpRSovJUUvRSlHKUUpQyovJUIvQylAKkcvLydJKUspTClOKU8pUSlTKVEqTzBHLy8nSSlLKUwpTilPKVEpUylRKk8wTzA0JU4vTylMKk8wMiVOL08pTCpPMDAlTi9PKUwqTzAvJU4vTylMKlMwNCVRL1MpTypTMDIlUS9TKU8qUzAwJVEvUylPKlMwLyhRL1MpTypPME4vTylMKk8wTi9PKUwqTzBOL08pTCpPME4vTylMKk8wNChOL08pTCpPME4vTylMKk8wTi9PKUwqTzBOL08pTCxgOwoKICBYLm9uY2xpY2sgPSAoYSxsLHQsaSxqLE4sSSxELFcsUD0wLGY9bmV3IEF1ZGlvQ29udGV4dCkgPT4gewogICAgWC5vbmNsaWNrID0gbnVsbDsKICAgIGwgPSAodCA9IGYuY3JlYXRlQnVmZmVyKDEsIDI0ZTUsIDI0ZTMpKS5nZXRDaGFubmVsRGF0YSgwKTsKICAgIGZvciAoaSA9IDA7IGkgPCBNLmxlbmd0aDsgaSsrKSB7CiAgICAgIE4gPSBNW2krK10uY2hhckNvZGVBdCgpOwogICAgICBJID0gTVtpXS5jaGFyQ29kZUF0KCkgLSAzNTsKICAgICAgRCA9IFsxLCAyLCA2LCA4LCAxMiwgMzBdW0kgJSA2XTsKICAgICAgVyA9IEkgLyA2IHwgMDsKICAgICAgUCArPSBXICogNmUzOwogICAgICBmb3IgKGogPSAwOyBqIDwgRCAqIDZlMzsgaisrKQogICAgICAgIGxbUCArIGpdICs9IEYoTiwgaiwgRCkgKiAuMzUgKyBGKE4gKyAxMiwgaiwgRCkgKiAuMSArIEYoTiArIDI0LCBqLCBEKSAqIC4wNQogICAgfQogICAgKGkgPSBmLmNyZWF0ZUJ1ZmZlclNvdXJjZSgpKS5idWZmZXIgPSB0OwogICAgaS5jb25uZWN0KGYuZGVzdGluYXRpb24pOwogICAgaS5zdGFydCgwKTsKICB9Owo8L3NjcmlwdD4KPC9ib2R5Pgo8L2h0bWw+Cg==

4200 caracteres, lo excedimos. Pero al menos funciona. Cuando lo mejoremos te daremos la forma de código QR:

¡Feliz Año Nuevo a todos!

P.S. Por alguna razón, en Firefox el árbol de Navidad se pega a la parte superior; en los navegadores similares a Chrome (Chrome, Edge, Yandex Browser) todo funciona perfectamente.

Dado que el árbol de Navidad, los regalos y los muñecos de nieve son símbolos Unicode, el diseño del árbol de Navidad cambia de un navegador a otro, de un sistema operativo a otro. ¡Muéstrale tu árbol de Navidad único a tus seres queridos!

Categorizado en:

HTML,