From f91187ba08914970d2635889f20e2f57138953c8 Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 9 Sep 2026 03:39:01 +0200 Subject: [PATCH] =?UTF-8?q?El=20mapa=20es=20de=20Iberoam=C3=A9rica:=20los?= =?UTF-8?q?=20veintid=C3=B3s,=20no=20veinte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit España y Portugal estaban fuera porque lo hice sobre un mapa de las Américas y lo di por bueno con una nota. Iberoamérica es también la Península, y España está en los datos: el bolero se adoptó ahí. El origen pasa a ser el mapa del mundo de Wikimedia, en dominio público. Cuatro cosas se rompieron por el camino y se ven todas en una captura: - El mundial está en coordenadas **relativas**, donde los números son desplazamientos y no posiciones. Medirlos como si lo fueran daba un encuadre de cuarenta mil por cincuenta y ocho mil unidades. - `d="…"` encajaba dentro de `id="Mexico_mainland"`, así que el trazado que se extraía era el nombre del país. Salía un mapa vacío sin que nada fallara. - Al pegar trazados relativos, el `m` de cada uno se sumaba al final del anterior. Ahora el primer salto de cada trazado es absoluto. - El `clip-path` se aplica **después** del `transform` del elemento, así que el recorte se movía con el mapa y el recuadro salía vacío. La Península va en su propio lienzo, sobre el Atlántico norte: como recuadro dentro del mapa acababa encima de Venezuela, y encima de la lista de países no pintaba nada. Y al lado, la lista de los veintidós con España primero y el resto alfabético, con cuántos géneros nacieron en cada uno y cuántos adoptó. Colores: el dorado mezclado con el fondo daba un marrón sucio. Entra `--c-dato` en la paleta, un verdemar de la misma familia que el marino, con dos intensidades para nacido y adoptado; el país elegido va en el color del texto, que es el único contraste que se distingue de los dos verdes en los dos temas. El radio del punto se mide contra el ancho de su lienzo: fijo, en la Península salía del tamaño de Castilla. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/mapa/BlankMap-Americas.svg | 2880 -------------------- scripts/mapa/BlankMap-World.svg | 3272 +++++++++++++++++++++++ scripts/mapa/generar.mjs | 207 +- src/app.css | 16 + src/lib/components/MapaDeGeneros.svelte | 310 ++- src/lib/mapa/iberoamerica.ts | 124 +- src/routes/generos/+page.svelte | 2 +- 7 files changed, 3821 insertions(+), 2990 deletions(-) delete mode 100644 scripts/mapa/BlankMap-Americas.svg create mode 100644 scripts/mapa/BlankMap-World.svg diff --git a/scripts/mapa/BlankMap-Americas.svg b/scripts/mapa/BlankMap-Americas.svg deleted file mode 100644 index f7e5cab..0000000 --- a/scripts/mapa/BlankMap-Americas.svg +++ /dev/null @@ -1,2880 +0,0 @@ - - - - - - image/svg+xml - - - Blank World Map - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/mapa/BlankMap-World.svg b/scripts/mapa/BlankMap-World.svg new file mode 100644 index 0000000..f29e15c --- /dev/null +++ b/scripts/mapa/BlankMap-World.svg @@ -0,0 +1,3272 @@ + + + World Map + + + + + Sudan + + + South Sudan + + + Georgia + + + Abkhazia + + + + + South Ossetia + + + + + + Peru + + + Burkina Faso + + + Libya + + + Belarus + + + Pakistan + + + Azad Jammu and Kashmir + + + + Indonesia + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Yemen + + + + + + + Madagascar + + + + + + Bolivia, Plurinational State of + + + + + Serbia + + + Kosovo + + + + + + Cote d'Ivoire + + + Algeria + + + Switzerland + + + Cameroon + + + North Macedonia, Republic of + + + Botswana + + + Kenya + + + Jordan + + + Mexico + + + + + + + + + + + + + + + + + + United Arab Emirates + + + + + Belize + + + + + Brazil + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sierra Leone + + + + + Mali + + + Congo, Democratic Republic of the + + + Italy + + + + + + + + Somalia + + + Somaliland + + + + Afghanistan + + + Bangladesh + + + + + + + + + + + + + + Dominican Republic + + + + + Guinea-Bissau + + + + + + + + + + + Ghana + + + Austria + + + Sweden + + + + + + + + + + + + + + + Turkey + + + + + + Uganda + + + Mozambique + + + + + + + + New Zealand + + + + + + + + + + + + Cuba + + + + + + + + + + + Venezuela, Bolivarian Republic of + + + + + + + + + + + + + + + + Portugal + + + + + + + + + + + Colombia + + + Mauritania + + + + + Angola + + + + + Germany + + + + + + + + Thailand + + + + + + + + + + + + Papua New Guinea + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Iraq + + + Croatia + + + + + + + + + + + + + + + + + + + + + Greenland + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Niger + + + Denmark + + + + + + + + + + + + + + + + + + Latvia + + + Romania + + + Zambia + + + Myanmar + + + + + + + + + + + + + + + + + + + + + + + Ethiopia + + + Guatemala + + + Suriname + + + Czech Republic + + + Chad + + + Albania + + + Finland + + + + + + + + + + + + + + + + + + + + + + Syrian Arab Republic + + + Kyrgyzstan + + + Solomon Islands + + + + + + + + + + + + + + + + + + + + + + + + + + + Oman + + + + + + Panama + + + + + + + + Argentina + + + + + + + + United Kingdom + + + + + + + + + + + + + + + + + + + + + + + + + + + + Costa Rica + + + + + Paraguay + + + Guinea + + + + + Ireland + + + + + + Nigeria + + + + + + Tunisia + + + + + Poland + + + Namibia + + + South Africa + + + Egypt + + + Tanzania, United Republic of + + + + + + + Saudi Arabia + + + + + + + + Vietnam, Socialist Republic of + + + + + + + + + + + + + Russian Federation + + + + + + + + + + + + + + Southern Kuril Islands + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Crimea + + + + Haiti + + + + + + + Bosnia and Herzegovina + + + India + + + + + + + + + + + + + + + Canada + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + El Salvador + + + Guyana + + + Belgium + + + Equatorial Guinea + + + + + Lesotho + + + Bulgaria + + + Burundi + + + Djibouti + + + Azerbaijan + + + + + + Artsakh, Republic of + + + + + + Iran, Islamic Republic of + + + + + + + Malaysia + + + + + + + + + + + + + + + + + + Philippines + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Uruguay + + + Congo, Republic of the + + + Estonia + + + + + + + + Rwanda + + + Armenia + + + Senegal + + + Togo + + + Spain + + + + + + + + + + + + + + + Gabon + + + + + Hungary + + + Malawi + + + Tajikistan + + + Cambodia + + + + + + + Korea, Republic of + + + + + + + + + + + + Honduras + + + + + Iceland + + + Nicaragua + + + Chile + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Morocco + + + + Western Sahara + + + Sahrawi Arab Democratic Republic + + + + + Liberia + + + Central African Republic + + + Slovakia + + + Lithuania + + + Zimbabwe + + + Sri Lanka + + + + + + Israel + + + + State of Palestine + + + Gaza Strip + + + West Bank + + + + + + Lao People's Democratic Republic + + + Korea, Democratic People's Republic of + + + Greece + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Turkmenistan + + + Ecuador + + + + + + + + + + + + + Benin + + + Slovenia + + + Norway + + Svalbard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Moldova, Republic of + + + Transnistria + + + + + + Ukraine + + + Donetsk People's Republic + + + + + Luhansk People's Republic + + + + + + Nepal + + + Eritrea + + + + + United States of America + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Kazakhstan + + + + + French Southern Territories + + + + + Uzbekistan + + + Mongolia + + + Bhutan + + + Antarctica + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Australia + + + + + + + + + + + + + + + + + + + + + + + Christmas Island + + + + + Cocos (Keeling) Islands + + + + + Heard Island and McDonald Islands + + + + + Norfolk Island + + + + + + China + + China, People's Republic of + + + + + + + + + + + + + + + + + + + + + + + Hong Kong + + + + + + + + + Macao + + + + + Taiwan + + + + + + + + + + France + + + + + + + + + + French Guiana + + + Guadeloupe + + + + + + + + + Martinique + + + + + Reunion + + + + + Mayotte + + + + + + Netherlands + + + + + + + + + + + + Bonaire, Sint Eustatius and Saba + + + + + + + + + + Lebanon + + + + + Montenegro + + + + + Eswatini + + + + + New Caledonia + + + + + + + + + + + + Fiji + + + + + + + + + + + + + + Kuwait + + + + + + + + Timor-Leste + + + + + + + + Bahamas + + + + + + + + + + + + + + + + + + + + + + + + Vanuatu + + + + + + + + + + + + + + + + + + + + Falkland Islands (Islas Malvinas) + + + + + + + + + + + + + + South Georgia and the South Sandwich Islands + + + + + Gambia + + + + + Qatar + + + + + Jamaica + + + + + Cyprus + + + Northern Cyprus + + + + + + + Puerto Rico + + + + + Brunei + + + + + + + + Trinidad and Tobago + + + + + + + + Cape Verde + + + + + + + + + + + + + + French Polynesia + + + + + + + + + + + + + + Samoa + + + + + + + + Luxembourg + + + + + Comoros + + + + + + + + + Mauritius + + + + + Faroe Islands + + + + + + + + + + + + Sao Tome and Principe + + + + + + + + Virgin Islands, U.S. + + + + + + + + Curaçao + + + + + Sint Maarten (Dutch Part) + + + + + Dominica + + + + + Tonga + + + + + + + + + Kiribati + + + + + + + + Micronesia, Federated States of + + + + + Bahrain + + + + + + Northern Mariana Islands + + + + + Palau + + + + + Seychelles, Republic of + + + + + British Indian Ocean Territory + + + + + Antigua and Barbuda + + + + + + + + Barbados + + + + + Turks and Caicos Islands + + + + + + + + + Saint Vincent and the Grenadines + + + + + Saint Lucia + + + + + Grenada + + + + + Malta + + + + + Maldives, Republic of + + + + + Cayman Islands + + + + + Saint Kitts and Nevis + + + + + + + + Montserrat + + + + + Saint Barthélemy + + + + + Niue + + + + + Saint Pierre and Miquelon + + + + + Cook Islands + + + + + Wallis and Futuna + + + + + American Samoa + + + + + Marshall Islands, Republic of the + + + + + Aruba + + + + + Liechtenstein + + + + + Virgin Islands, British + + + + + + + + Saint Helena, Ascension and Tristan da Cunha + + + + + Jersey + + + + + Anguilla + + + + + Saint Martin (French Part) + + + + + Guernsey + + + + + San Marino + + + + + Bermuda + + + + + Tuvalu + + + + + Nauru + + + + + Gibraltar + + + + + Pitcairn Islands + + + + + Monaco + + + + + Vatican City + + + + + Isle of Man + + + + + Guam + + + + + Singapore + + + + + Tokelau + + + + + + + + \ No newline at end of file diff --git a/scripts/mapa/generar.mjs b/scripts/mapa/generar.mjs index ee996c2..227be3e 100644 --- a/scripts/mapa/generar.mjs +++ b/scripts/mapa/generar.mjs @@ -25,11 +25,19 @@ import fs from 'node:fs'; -const ORIGEN = process.argv[2] ?? 'scripts/mapa/BlankMap-Americas.svg'; +const ORIGEN = process.argv[2] ?? 'scripts/mapa/BlankMap-World.svg'; const DESTINO = 'src/lib/mapa/iberoamerica.ts'; -/** Los veinte, en el orden en que se leen de norte a sur. */ +/** + * Los veintidós de Iberoamérica: los veinte de América más España y Portugal. + * + * Estaban fuera en la primera versión, hecha sobre un mapa de las Américas. + * Iberoamérica es también la Península —y España está en los datos: el bolero + * se adoptó ahí—, así que el origen pasa a ser el mapa del mundo. + */ const PAISES = [ + 'es', + 'pt', 'mx', 'gt', 'sv', @@ -55,6 +63,38 @@ const PAISES = [ /** Un trazado más pequeño que esto no se ve, y sí se nota en el peso. */ const MINIMO = 2; +/** + * El nombre en español de cada uno. + * + * Va aquí y no se lee de la tabla `pais`: el mapa dibuja los veintidós desde el + * primer día y la tabla solo tiene los países que ha traído alguna semilla. Un + * país sin nombre en la lista de al lado no se puede ni pulsar. + */ +const NOMBRES = { + es: 'España', + pt: 'Portugal', + mx: 'México', + gt: 'Guatemala', + sv: 'El Salvador', + hn: 'Honduras', + ni: 'Nicaragua', + cr: 'Costa Rica', + pa: 'Panamá', + cu: 'Cuba', + do: 'República Dominicana', + pr: 'Puerto Rico', + ve: 'Venezuela', + co: 'Colombia', + ec: 'Ecuador', + pe: 'Perú', + bo: 'Bolivia', + py: 'Paraguay', + cl: 'Chile', + ar: 'Argentina', + uy: 'Uruguay', + br: 'Brasil' +}; + const svg = fs.readFileSync(ORIGEN, 'utf8'); /* @@ -70,23 +110,99 @@ for (const [, cierre, etiqueta, atributos, suelta] of svg.matchAll( )) { if (etiqueta === 'g') { if (cierre) pila.pop(); - else pila.push(atributos.match(/id="([^"]+)"/)?.[1] ?? null); + else pila.push(atributos.match(/(?:^|\s)id="([^"]+)"/)?.[1] ?? null); continue; } if (cierre) continue; - const propio = atributos.match(/id="([^"]+)"/)?.[1] ?? null; + const propio = atributos.match(/(?:^|\s)id="([^"]+)"/)?.[1] ?? null; const codigo = [propio, ...[...pila].reverse()].find((c) => c && PAISES.includes(c)); - const d = atributos.match(/d="([^"]+)"/)?.[1]; + /* + * El límite del atributo importa: sin él, `d="…"` encaja dentro de + * `id="Mexico_mainland"` y el trazado que se extrae es el nombre del + * país. Salía un mapa vacío sin que nada fallara. + */ + const d = atributos.match(/(?:^|\s)d="([^"]+)"/)?.[1]; if (codigo && d) trozos[codigo].push(d); void suelta; } +/** + * Recorre un trazado y devuelve sus puntos, ya en absolutas. + * + * Hace falta porque el mapa del mundo está escrito en relativas —`m 1244,328 c + * 0.1,-0.12 …`— y ahí los números no son coordenadas sino desplazamientos: + * sumarlos como si lo fueran daba un rectángulo sin sentido y el encuadre salía + * infinito. + * + * No convierte el trazado, solo lo mide: el `d` se guarda tal cual, que dibuja + * igual y ocupa menos. + */ +function puntosDe(d) { + const piezas = d.match(/[a-zA-Z]|-?\d*\.?\d+(?:e[-+]?\d+)?/gi) ?? []; + const puntos = []; + + let x = 0; + let y = 0; + let inicioX = 0; + let inicioY = 0; + let comando = 'M'; + let i = 0; + + /** Cuántos números lleva cada comando. */ + const cuantos = { M: 2, L: 2, T: 2, H: 1, V: 1, C: 6, S: 4, Q: 4, A: 7, Z: 0 }; + + while (i < piezas.length) { + if (/[a-zA-Z]/.test(piezas[i])) { + comando = piezas[i]; + i++; + if (comando.toUpperCase() === 'Z') { + x = inicioX; + y = inicioY; + continue; + } + } + + const letra = comando.toUpperCase(); + const relativo = comando !== letra; + const n = cuantos[letra]; + if (n === undefined) break; + + const args = piezas.slice(i, i + n).map(Number); + i += n; + if (args.length < n) break; + + if (letra === 'H') x = relativo ? x + args[0] : args[0]; + else if (letra === 'V') y = relativo ? y + args[0] : args[0]; + else if (letra === 'A') { + // De la elipse solo interesa dónde acaba. + x = relativo ? x + args[5] : args[5]; + y = relativo ? y + args[6] : args[6]; + } else { + // El punto final es la última pareja; los de control quedan fuera del + // cálculo, y para medir un país la diferencia no se ve. + x = relativo ? x + args[n - 2] : args[n - 2]; + y = relativo ? y + args[n - 1] : args[n - 1]; + } + + if (letra === 'M') { + inicioX = x; + inicioY = y; + // Una `m` con más parejas sigue como `l`; una `M`, como `L`. + comando = relativo ? 'l' : 'L'; + } + + puntos.push([x, y]); + } + + return puntos; +} + /** El rectángulo que ocupa un trazado, para saber si merece la pena. */ function tamano(d) { - const numeros = [...d.matchAll(/-?\d+(?:\.\d+)?/g)].map((m) => Number(m[0])); - const xs = numeros.filter((_, i) => i % 2 === 0); - const ys = numeros.filter((_, i) => i % 2 === 1); + const puntos = puntosDe(d); + const xs = puntos.map((p) => p[0]); + const ys = puntos.map((p) => p[1]); if (xs.length === 0) return { ancho: 0, alto: 0, xs, ys }; return { ancho: Math.max(...xs) - Math.min(...xs), @@ -98,8 +214,21 @@ function tamano(d) { const unaDecimal = (d) => d.replace(/-?\d+\.\d+/g, (n) => String(Math.round(Number(n) * 10) / 10)); +/** + * Une varios trazados en uno. + * + * El primer salto de cada uno pasa a ser absoluto. Dentro de un `` la + * posición de partida es el origen, así que `m 1244,328` y `M 1244,328` son lo + * mismo; pero al pegar el segundo detrás del primero, su `m` se sumaba a donde + * había acabado el anterior y México acababa a cuarenta mil unidades de su + * sitio. El encuadre salía de cuarenta y dos mil por cincuenta y ocho mil. + */ +const unir = (trazados) => trazados.map((d) => d.replace(/^\s*m/, 'M')).join(' '); + const contorno = {}; const centro = {}; +/** El trazado mayor de cada país: es con el que se encuadra el inserto. */ +const principal = {}; let descartados = 0; for (const codigo of PAISES) { @@ -109,7 +238,7 @@ for (const codigo of PAISES) { if (!vale) descartados++; return vale; }); - contorno[codigo] = unaDecimal(buenos.join(' ')); + contorno[codigo] = unaDecimal(unir(buenos)); /* * Un punto donde agarrar el país. @@ -127,27 +256,48 @@ for (const codigo of PAISES) { const { xs, ys } = tamano(mayor); const medio = (ns) => ns.reduce((t, n) => t + n, 0) / ns.length; centro[codigo] = [medio(xs), medio(ys)].map((n) => Math.round(n * 10) / 10); + principal[codigo] = mayor; } -/* El encuadre sale de lo que queda, no del mapa entero. */ -const xs = []; -const ys = []; -for (const d of Object.values(contorno)) { - const t = tamano(d); - xs.push(...t.xs); - ys.push(...t.ys); +/** + * Dos encuadres, no uno. + * + * Iberoamérica son dos orillas y en medio hay un océano: con un solo encuadre, + * la mitad del dibujo es Atlántico vacío y todo lo demás queda diminuto. Se + * calcula uno para América y otro para la Península, y el mapa pinta el segundo + * en un recuadro —la misma solución que usa cualquier mapa de España con + * Canarias—. + */ +const IBERIA = ['es', 'pt']; +const MARGEN = 10; + +function encuadrar(codigos, soloPrincipal = false) { + const xs = []; + const ys = []; + for (const c of codigos) { + const t = tamano(soloPrincipal ? principal[c] : contorno[c]); + xs.push(...t.xs); + ys.push(...t.ys); + } + const x0 = Math.min(...xs) - MARGEN; + const y0 = Math.min(...ys) - MARGEN; + return [x0, y0, Math.max(...xs) - x0 + MARGEN, Math.max(...ys) - y0 + MARGEN] + .map((n) => n.toFixed(1)) + .join(' '); } -const MARGEN = 10; -const x0 = Math.min(...xs) - MARGEN; -const y0 = Math.min(...ys) - MARGEN; -const encuadre = [x0, y0, Math.max(...xs) - x0 + MARGEN, Math.max(...ys) - y0 + MARGEN] - .map((n) => n.toFixed(1)) - .join(' '); +const encuadre = encuadrar(PAISES.filter((c) => !IBERIA.includes(c))); +/* + * El inserto encuadra solo la parte continental. Con Canarias dentro, el + * rectángulo se estiraba mil kilómetros al suroeste y la Península quedaba del + * tamaño de una mota. Las islas se siguen dibujando; lo que cambia es el + * encuadre. + */ +const encuadreIberia = encuadrar(IBERIA, true); const cuerpo = PAISES.map( (c) => - `\t{ codigo: '${c.toUpperCase()}', centro: [${centro[c].join(', ')}], d: '${contorno[c]}' }` + `\t{ codigo: '${c.toUpperCase()}', nombre: '${NOMBRES[c]}', centro: [${centro[c].join(', ')}], d: '${contorno[c]}' }` ).join(',\n'); fs.writeFileSync( @@ -168,15 +318,23 @@ fs.writeFileSync( export interface PaisDelMapa { /** ISO 3166-1 alfa-2, como en la tabla \`pais\`. */ codigo: string; + /** En español. Lo lleva el mapa porque dibuja países que aún no están en la base. */ + nombre: string; /** Un punto dentro del país, para colgar de él lo que haya que pulsar. */ centro: [number, number]; /** El contorno, con todas sus islas en un solo trazado. */ d: string; } -/** El encuadre justo de los veinte países, no el del mapa original. */ +/** El encuadre de la orilla americana. */ export const ENCUADRE = '${encuadre}'; +/** El de la Península, que va en su propio recuadro. */ +export const ENCUADRE_IBERIA = '${encuadreIberia}'; + +/** Quiénes van en ese recuadro. */ +export const IBERIA = ['ES', 'PT']; + export const PAISES: PaisDelMapa[] = [ ${cuerpo} ]; @@ -187,4 +345,5 @@ const kb = (fs.statSync(DESTINO).size / 1024).toFixed(0); console.log(` países ${PAISES.length}`); console.log(` descartados ${descartados} trazados por debajo de ${MINIMO} unidades`); console.log(` encuadre ${encuadre}`); +console.log(` — península ${encuadreIberia}`); console.log(` tamaño ${kb} KB`); diff --git a/src/app.css b/src/app.css index 85f750c..e4ec166 100644 --- a/src/app.css +++ b/src/app.css @@ -52,6 +52,18 @@ --c-accent-brillo: oklch(0.94 0.095 96); --c-accent-sombra: oklch(0.63 0.13 72); + /* + * El verdemar: el tercer color de la paleta, para datos. + * + * El mapa necesitaba dos intensidades de algo que no fuera el dorado, y + * mezclarlo con el fondo marino daba barro —literalmente un marrón sucio—. + * Este es de la misma familia que el marino, un paso hacia el verde, así que + * conviven sin pelearse; y deja el dorado para lo que se pulsa y lo que se + * elige, que es su trabajo en el resto del sitio. + */ + --c-dato: oklch(0.78 0.13 192); + --c-dato-tenue: oklch(0.52 0.085 200); + --c-danger: oklch(0.72 0.16 22); --c-focus: oklch(0.84 0.12 88); @@ -199,6 +211,10 @@ --c-accent-brillo: oklch(0.9 0.1 92); --c-accent-sombra: oklch(0.6 0.12 74); + /* En claro se invierten: el dato pintado tiene que ser oscuro sobre papel. */ + --c-dato: oklch(0.52 0.09 200); + --c-dato-tenue: oklch(0.82 0.045 200); + --c-danger: oklch(0.5 0.17 25); --c-focus: oklch(0.5 0.13 264); } diff --git a/src/lib/components/MapaDeGeneros.svelte b/src/lib/components/MapaDeGeneros.svelte index e2c5e84..9030920 100644 --- a/src/lib/components/MapaDeGeneros.svelte +++ b/src/lib/components/MapaDeGeneros.svelte @@ -11,18 +11,18 @@ * SVG, no un `` con un `onclick`: así entra en el orden de tabulación * y se anuncia con su nombre. */ - import { ENCUADRE, PAISES } from '$lib/mapa/iberoamerica'; + import { ENCUADRE, ENCUADRE_IBERIA, IBERIA, PAISES } from '$lib/mapa/iberoamerica'; import type { GeneroEnElMapa } from '$lib/server/taller'; interface Props { generos: GeneroEnElMapa[]; /** El país elegido, en ISO alfa-2. Vacío si no hay ninguno. */ - pais: string; + paisElegido: string; /** Se llama al pulsar un país; el mismo otra vez lo deselecciona. */ alElegir: (codigo: string) => void; } - let { generos, pais, alElegir }: Props = $props(); + let { generos, paisElegido, alElegir }: Props = $props(); /** Lo que tiene cada país, para saber cómo pintarlo y qué anunciar. */ const porPais = $derived.by(() => { @@ -38,6 +38,56 @@ return mapa; }); + /* + * La lista de al lado: España primero y el resto por orden alfabético. Es la + * misma información que el mapa, en la forma en que se busca un país cuando + * ya se sabe cuál —y la única que sirve si el mapa no se puede usar—. + */ + const enLista = $derived.by(() => { + const resto = PAISES.filter((p) => p.codigo !== 'ES').sort((a, b) => + a.nombre.localeCompare(b.nombre, 'es') + ); + const espana = PAISES.find((p) => p.codigo === 'ES'); + return espana ? [espana, ...resto] : resto; + }); + + /** El ancho de un encuadre, para medir con él lo que se dibuja encima. */ + const anchoDe = (encuadre: string) => Number(encuadre.split(' ')[2]); + + const enAmerica = PAISES.filter((p) => !IBERIA.includes(p.codigo)); + const enIberia = PAISES.filter((p) => IBERIA.includes(p.codigo)); + + /* + * El recuadro de la Península, colocado a mano dentro del mismo lienzo. + * + * Se probó con un `` anidado, que es lo que parece corresponder, y no + * pintaba nada: sus porcentajes se miden contra el viewport y no contra el + * `viewBox` del padre. Con una traslación y una escala calculadas de los dos + * encuadres, el sitio es exactamente el que se pide. + */ + const numeros = (encuadre: string) => encuadre.split(' ').map(Number); + + const inserto = (() => { + const [ax, ay, aAncho] = numeros(ENCUADRE); + const [ix, iy, iAncho, iAlto] = numeros(ENCUADRE_IBERIA); + + const escala = (aAncho * 0.44) / iAncho; + const margen = 8; + const x = ax + aAncho - iAncho * escala - margen; + const y = ay + margen; + + return { + escala, + transform: `translate(${(x - ix * escala).toFixed(1)} ${(y - iy * escala).toFixed(1)}) scale(${escala.toFixed(3)})`, + marco: { + x: x.toFixed(1), + y: y.toFixed(1), + ancho: (iAncho * escala).toFixed(1), + alto: (iAlto * escala).toFixed(1) + } + }; + })(); + /** Lo que lee un lector de pantalla y lo que sale al pasar el ratón. */ function resumen(codigo: string): string { const suyo = porPais[codigo]; @@ -50,46 +100,144 @@ } -
- - {#each PAISES as p (p.codigo)} +
+
+ + {#each enAmerica as p (p.codigo)} + {@render pais(p, anchoDe(ENCUADRE))} + {/each} + + + + + {#each enIberia as p (p.codigo)} + {@render pais(p, anchoDe(ENCUADRE_IBERIA))} + {/each} + +
+ + +
    + {#each enLista as p (p.codigo)} {@const suyo = porPais[p.codigo]} - {#if suyo} - - 0} - class:elegido={pais === p.codigo} - role="button" - tabindex="0" - aria-pressed={pais === p.codigo} - aria-label={resumen(p.codigo)} +
  • + +
  • {/each} - +
+{#snippet pais(p: (typeof PAISES)[number], anchoDelLienzo: number)} + {@const suyo = porPais[p.codigo]} + {#if suyo} + + 0} + class:elegido={paisElegido === p.codigo} + role="button" + tabindex="0" + aria-pressed={paisElegido === p.codigo} + aria-label={resumen(p.codigo)} + onclick={() => alElegir(p.codigo)} + onkeydown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + alElegir(p.codigo); + } + }} + > + {resumen(p.codigo)} + + + + + {:else} + + + {/if} +{/snippet} +