PageSourceSearch

https://www.goyanetwork.com/network.js

js goyanetwork.com collected 2026-09-25 21:53:38 UTC 285,968 bytes, 4,934 lines download raw bytes

1  // ====== I18N ======
2  let CURRENT_LANG = localStorage.getItem('lang') || 'en';
3  function getDataFileForLang(lang) {
4    return (lang === 'es') ? 'goya_network_ES.json' : 'goya_network.json';
5  }
6
7  const I18N = {
8    en: {
9      BTN_NETWORK:"Network", BTN_ABOUT:"About", BTN_PEOPLE:"People", BTN_PARTNERS:"Partners",
10      BTN_ARTWORKS:"Goya's work", BTN_BIB:"Bibliography", BTN_CITE:"How to cite", BTN_MENTIONS:"Mentions", BTN_NEWS:"News", BTN_RELEVANT_LINKS:"Relevant links", BTN_CONTACT:"Contact",
11      FUNDING_PARTNERS: "Funding Partners",
12      ACADEMIC_PARTNERS: "Academic Partners",
13      GERDA_HENKEL_NAME: "Gerda Henkel Foundation",
14      SEARCH_PH:"Search...", FILTER_PROF_ALL:"All professions", FILTER_NAT_ALL:"All nationalities",
15      NEW_NODES_TITLE: "New members",
16      MEMBERS_TITLE:"All members (A-Z by surname)", LOADING:"Loading network…", LAST_UPDATE:"Last update", HELP_TEXT_1: "Click a node (an individual) or edge (a connection between two individuals) to view the data they contain.", HELP_TEXT_2: "(It might take a few seconds for the website to show the network)", SITE_TITLE: "Goya Network", SITE_SUBTITLE: "Mapping Goya's Impact in 19th-Century France and Spain", 
17      BTN_SEARCH: "Search",
18      BTN_CLUSTER_TOGGLE_ON: "Cluster edges: On",
19      BTN_CLUSTER_TOGGLE_OFF: "Cluster edges: Off",
20      SELECT_CLUSTER_OPTION: "Select cluster...",
21      
22      CONTRIBUTIONS_TITLE: "Contributions", 
23      CONTRIBUTIONS_TEXT: `
24      <p>The Goya Network is conceived as a collaborative, crowd-sourced database.</p>
25      <p>Contributions and suggestions are welcome and encouraged, and contributors will be credited if requested.</p>
26      `,
27      
28      PARTNERS_TITLE: "Partners",
29      
30      RIGHTS_LABEL: "Rights:",
31      NOTARIAL_PROTECTION: "Protected by notarial deed (Madrid, 2025)",
32      ACCESS_FORM: "Access form",
33
34      CITE_TITLE: "How to cite The Goya Network",
35      BIB_TITLE: "Bibliography", BIB_INTRO: "This section lists the bibliographic sources used throughout the project. References in nodes and edges appear as short citations (e.g. Adhémar 1958).",
36
37      //professions
38      "Actor":"Actor","Archaeologist":"Archaeologist","Anthropologist":"Anthropologist","Architect":"Architect", "Aristocrat":"Aristocrat","Art critic":"Art critic", "Art dealer":"Art dealer","Art patron":"Art patron","Art historian":"Art historian","Art theorist":"Art theorist","Artist":"Artist","Banker":"Banker","Bibliographer":"Bibliographer","Bibliophile":"Bibliophile","Businessman":"Businessman","Caricaturist":"Caricaturist","Ceramist":"Ceramist","Civil servant":"Civil servant","Clergyman":"Clergyman","Collector":"Collector","Composer":"Composer","Critic":"Critic", "Curator":"Curator", "Designer":"Designer","Diplomat":"Diplomat","Draughtsman":"Draughtsman","Economist":"Econimist","Editor":"Editor", "Engineer":"Engineer", "Engraver":"Engraver","Essayist":"Essaysit","Femme de lettres":"Femme de lettres","Hispanist":"Hispanist", "Historian":"Historian","Illustrator":"Illustrator", "Journalist":"Journalist","Jurist":"Jurist","Lawyer":"Lawyer","Librarian":"Librarian","Librettist":"Librettist","Lithographer":"Lithographer","Lithographic printer":"Lithographic printer","Medical doctor":"Medical doctor","Military member":"Military member","Musician":"Musician","Novelist":"Novelist","Painter":"Painter","Philanthropist":"Philanthropist","Philosopher":"Philosopher","Print editor":"Print editor","Print dealer":"Print dealer","Photographer":"Photographer","Pianist":"Pianist","Playwright":"Playwright","Poet":"Poet","Politician":"Politician", "Printer":"Printer", "Real estate agent":"Real estate agent","Salonnière":"Salonnière","Scholar":"Scholar","Scientist":"Scientist","Sculptor":"Sculptor","Secret agent":"Secret agent","Singer":"Singer","Socialite":"Socialite","Teacher":"Teacher","Translator":"Translator","Traveller":"Traveller", "Writer":"Writer",
39      
40      //nationalities
41      "Spanish":"Spanish","French":"French","British":"British","English":"English","Scottish":"Scottish","Irish":"Irish","Italian":"Italian","German":"German","Swiss":"Swiss","Belgian":"Belgian","Dutch":"Dutch","Austrian":"Austrian","Portuguese":"Portuguese","American":"American", "Cuban":"Cuban", "Polish":"Polish", "German / Czech":"German / Czech",
42
43      "Life dates":"Life dates","Sex":"Sex","male":"male","female":"female","Full name":"Full name","Also known as":"Also known as","Other names":"Other names","Pseudonyms":"Pseudonyms", "Profession":"Profession","Nationality":"Nationality","Born in":"Born in","Dies in":"Dies in","Considered as":"Considered as", "Political views":"Political views","Military activity":"Military activity","Participates in":"Participates in", "Aristocratic titles":"Aristocratic titles","Ties with slavery/colonialism":"Ties with slavery/colonialism","Curiosities":"Curiosities", "Parents":"Parents","Siblings":"Siblings","Married to":"Married to","Partners/lovers":"Partners/lovers","Children":"Children", "Friends":"Friends","Rivals":"Rivals","Correspondence":"Correspondence","Meets":"Meets","Studies in":"Studies in", "Works as":"Works as","Works for":"Works for","Influenced by":"Influenced by","Follower of":"Follower of","Masters":"Masters","Students":"Students", "Member of":"Member of","Founder of":"Founder of","Editor of":"Editor of","Writes in":"Writes in","Collaborates with":"Collaborates with", "Registered in":"Registered in","Mentioned in the French press":"Mentioned in the French press","Decorations/awards":"Decorations/awards", "Tertulia":"Tertulia","Literary salon or tertulia":"Literary salon or tertulia","Author of":"Author of","Collection":"Collection", "Patronage":"Patronage","Portraits":"Portraits","Exhibitions":"Exhibitions","Bibliography":"Bibliography","Websites":"Websites", "Podcasts":"Podcasts","Newspaper archive":"Newspaper archive","Archives":"Archives","Added":"Added","Last modified":"Last modified", "Identity and Personal Data":"Identity and Personal Data", "Family and Personal Relationships":"Family and Personal Relationships", "Education, Profession and Institutions":"Education, Profession and Institutions", "Artistic/Literary Production and Collection":"Artistic/Literary Production and Collection", "Exhibitions and Public Reception":"Exhibitions and Public Reception", "Relationship with Goya":"Relationship with Goya", "Copies after Spanish artworks":"Copies after Spanish artworks", "Sources and Documentation":"Sources and Documentation", "Connections":"Connections", "Artistic Activity, Art Collecting and Patronage":"Artistic Activity, Art Collecting and Patronage", "Geographic Presence and Movement":"Geographic Presence and Movement", "Relationship to Goya and His Work":"Relationship to Goya and His Work", 
44      "Portrayed by":"Portrayed by", "Image source":"Image source", "In Spain":"In Spain", "Knows Goya's works":"Knows Goya's works", "Address":"Address", "Link to Goya's work":"Link to Goya's work", "Interest in Spanish art":"Interest in Spanish art", "Mentions of Goya":"Mentions of Goya", "Collector of Goya's works":"Collector of Goya's works", "In Paris":"In Paris", "mentions": "Mentions", "Conferences/seminars":"Conferences/seminars", "Trips":"Trips", "Lives in":"Lives in", "Patrons": "Patrons", "Collector of":"Collector of", "Copies after Goya":"Copies after Goya", "Writes about Goya":"Writes about Goya", "Mentions of Goya":"Mentions of Goya", "Connection": "Connection", "Type of connection": "Type of connection", "Type of relationship": "Type of relationship", "Know each other since": "Know each other since", "Search": "Search", "Collaborations": "Collaborations", "They met": "They met", "direct": "direct", "Artworks bought/sold": "Artworks bought/sold", "Interactions": "Interactions", "Employment/patronage": "Employment/patronage", "Between": "Between", "and": "and", "Between [X] and [Y]": "Between [X] and [Y]", "In France": "In France", "In Italy": "In Italy", "direct": "direct", "secondary": "secondary", "portraitist-sitter": "portraitist-sitter", "employer-employee": "employer-employee", "family": "family", "friends": "friends", "acquaintances": "acquaintances", "acquaintances?": "acquaintances?", "direct?": "direct?", "secondary?": "secondary?", "master-student": "master-student", "partners/lovers": "lovers", "artworks influenced by Goya": "influenced by Goya", "knows Goya's works": "knows Goya's works", "Sales": "Sales", "colleagues": "colleagues", "copies": "copies", "Nodes": "Nodes", "Connections": "Connections", "Access form": "Access form", "Unknown": "unknown", "Visits the Prado Museum": "Visits the Prado Museum", "Views on art and cultural heritage":"Views on art and cultural heritage",
45
46      ARTWORKS_TITLE: "Goya's work",
47      ARTWORKS_BODY: `
48      <p>
49      <strong>The issue of attribution in Goya:</strong><br>
50      When studying Goya, it is important to bear in mind that, due to the enormous demand for his work since the artist's death (which far exceeds the supply of authentic pieces), countless copies, imitations and pastiches have been produced, particularly in 19th-century Spain and France. Many of these continue to be exhibited as genuine 'goyas' (more euphemistically, 'attributed to Goya', or even 'Goya's workshop') in museums around the world, driven by economic interests and s
50ocial status. New 'goyas' appear every year in art galleries, dealers, and private collections.
51      </p>
52
53      <p>
54      <strong>Catalogues raisonnés, key exhibition catalogues, and biography</strong> (see the Bibliography section):<br>
55      <ul>
56        <li><strong>Full oeuvre:</strong> Juliet Wilson-Bareau & Pierre Gassier. <em>Vie et œuvre de Francisco Goya. L'œuvre complet illustré. Peintures, dessins, gravure</em>, 1970 (French); <em>The Life and Complete Work of Francisco Goya: With a Catalogue Raisonné of the Paintings, Drawings and Engravings</em>, 1971 (English); <em>Vida y obra de Francisco Goya: reproducción de su obra completa, pinturas, dibujos y grabados</em>, 1974 (Spanish). See also the catalogue of the retrospective exhibition <i>Goya</i>, Martin Schwander (ed.), Fondation Beyeler, 2021 (English/Spanish/German).</li>
57        <li><strong>Drawings:</strong> Manuela Mena-Marqués & José Manuel Matilla, <em>Catálogo Razonado de los Dibujos de Francisco de Goya, vol. 2 (1771-1792)</em>, with G. Maurer, G. Solache, J. Carrete, J. Wilson & V. Albarrán, 2018 (Spanish); <em>Goya: dibujos. 'Solo la voluntad me sobra'</em>, exh. cat., 2019 (Spanish/English); and Pierre Gassier, <em>Les dessins de Goya. Les albums</em>, 1973 (French), <em>Francisco Goya. Drawings. The Complete Albums</em>, 1973 (English).</li>
58        <li><strong>Prints:</strong> Tomas Harris, <em>Goya. Engravings and Lithographs</em>, 1964. Also Juan Carrete Parrondo & Jesusa Vega, <em>Goya grabador</em>, 1991.</li>
59        <li><strong>Biography</strong>: Janis Tomlinson, <em>Goya: A Portrait of the Artist</em>, 2020 Princeton UP (English), 2022 Cátedra (Spanish).</li>
60      </ul>
61      </p>
62
63      <p>
64      <strong>Online resources:</strong><br>
65      <ul>
66        <li><strong>Wikipedia:</strong> a comprehensive, crowd-sourced overview of Goya's works displayed in a table, showing title, image, date, location and attribution. <a href="https://en.wikipedia.org/wiki/List_of_works_by_Francisco_Goya" target="_blank"> List of Works by Francisco de Goya</a> (English) - Paintings and Prints; <a href="https://es.wikipedia.org/wiki/Anexo:Cuadros_de_Goya" target="_blank"> Cuadros de Goya</a> (Spanish) - Paintings (non exhaustive).</li><br>
67        <li><a href="https://fundaciongoyaenaragon.es/" target="_blank"><strong>Fundación Goya en Aragón:</strong></a> an online catalogue of Goya's works collated by the Fundación Goya en Aragón (part of the Government of Aragón), that includes descriptions, provenance, bibliography, and images. This catalogue, however, collects <em>all</em> works ever attributed to Goya, including copies and pastiches.</li><br>
68      </ul>
69      </p>
70
71      <p>
72      <strong>GOYA COLLECTIONS: museums, libraries, churches, and private collections:</strong><br>
73        <br>
74        SPAIN
75        <li><a href="https://www.museodelprado.es/coleccion/artista/goya-y-lucientes-francisco-de/39568a17-81b5-4d6f-84fa-12db60780812" target="_blank"><strong>Museo del Prado</strong></a> (<em>Goya en el Prado</em>): artworks (paintings, prints and drawings) held at the Prado Museum, documents, printed references, and digital library.</li>
76        <li><a href="https://www.realacademiabellasartessanfernando.com/goya/la-academia-y-goya/" target="_blank"><strong>Real Academia de Bellas Artes de San Fernando</strong></a> (RABASF): history of Goya's link with the Academy, catalogue of works, <em>Calcografía Nacional</em> (production of Goya's prints).</li>
77        <li><strong>Patrimonio Nacional</strong> (Spanish national heritage): different paintings such as <a href="https://www.patrimonionacional.es/colecciones-reales/pintura/retrato-de-carlos-iv-como-cazador" target="_blank"><em>Portrait of Carlos IV as a Hunter</em></a> and <a href="https://www.galeriadelascoleccionesreales.es/obra-de-arte/retrato-de-maria-luisa-de-parma-en-traje-de-corte/19602330-5985-4e7e-81f7-25dce124b6a9" target="_blank"><em>Portrait of María Luisa de Parma in Court Dress</em></a>, as well as all the tapestries based on Goya's cartoons, such as <a href="https://www.patrimonionacional.es/colecciones-reales/textiles-y-tapiceria/la-era-o-el-verano" target="_blank"><em>The Summer</em></a>.</li>
78        <li><a href="https://www.bne.es/es/blog/blog-bne/post-80" target="_blank"><strong>Biblioteca Nacional de España</strong></a> (BNE): history of Goya's collections and catalogue.</li>
79        <li><a href="https://coleccion.bde.es/wca/es/secciones/coleccion/artistas/francisco-de-goya-y-lucientes-896.obras.html" target="_blank"><strong>Banco de España</strong></a>
79 (Madrid): paintings (such as the portraits of Francisco Cabarrús, Count of Floridablanca, XI Count of Altamira and Carlos III) and prints (<em>Desastres de la guerra</em>, <em>Tauromaquia</em>, <em>Disparates</em>).</li>
80        <li><a href="https://www.museolazarogaldiano.es/obras-goya-museo-lazaro-galdiano" target="_blank"><strong>Museo Lázaro Galdiano</strong></a> (Madrid): its collection includes the paintings <i>El Aquelarre</i> (1797-98) and <i>Las Brujas/El conjuro</i> (1797-98), the oil sketches <i>Santa Isabel de Portugal curando a una enferma</i> (c.1799) and <i>San Hermenegildo en la prisión</i> (1800), and the preparatory sketch <i>El verano</i> (1786); prints like <i>El can</i> (c.1820-27); and drawings like <i>Felipe III</i> (1778), <i>Retrato ecuestre del Conde-Duque de Olivares</i> (c.1776-78), <i>Portrait of Giambattista Casti</i> (c.1802-03), <i>Modo de volar</i> (1815-16), <i>Mendigo ciego con un perro</i> (c.1824) and <i>Mendigo con acordeón</i> (c.1824-28).</li>
81        <li><a href="https://www.museothyssen.org/coleccion/artistas/goya-francisco" target="_blank"><strong>Museo Thyssen Bornemisza</strong></a> (Madrid): biography and collection, which includes the paintings <i>Retrato de Asensio Julià</i> (c.1798), <i>Retrato de Fernando VII</i> (1814-15), and <i>El Tío Paquete</i> (c.1819-20).</li>
82        <li><a href="https://museogoya.fundacionibercaja.es/goya-en-zaragoza.php" target="_blank"><strong>Museo Goya - Colección Fundación Ibercaja - Museo Camón Aznar</strong></a> (Zaragoza).</li>
83        <li><a href="https://fundacionfuendetodosgoya.org/museo-del-grabado/" target="_blank"><strong>Museo del Grabado</strong></a> (Fuendetodos, Zaragoza): museum of printmaking at Goya's birth house.</li>
84        <li><strong>CHURCHES AND CATHEDRALS</strong>: Ermita de San Antonio de la Florida (Madrid), Real Basílica de San Francisco el Grande (Madrid), Valencia Cathedral, Sevilla Cathedral, Basílica del Pilar (Zaragoza), Cartuja del Aula Dei (Zaragoza).</li>
85        <li><strong>PRIVATE COLLECTIONS</strong> (selection): colección Abelló; colección Marqués de la Romana; Duke of Alba (Palacio de Liria); Dukes of Osuna (Alameda de Osuna); Carderera collection; Fundación María Cristina Masaveu Peterson; Fundación BBVA (Madrid, Palacio del Marqués de Salamanca): <a href="https://www.coleccionbbva.com/es/pintura/443-retrato-de-carlos-iii-cazador/" target="_blank"><em>Carlos III as a Hunter</em></a> and <a href="https://www.coleccionbbva.com/es/pintura/444-don-pantaleon-perez-de-nenin/" target="_blank"><em>Portrait of Don Pantaleón Pérez de Nenín</em></a>.</li><br>
86        
87        FRANCE<br>
88        <li><a href="https://collections.louvre.fr/en/recherche?q=Francisco%20de%20Goya" target="_blank"><strong>Musée du Louvre</strong>:</a> paintings, prints, and drawings.</li>
89        <li><a href="https://www.museegoya.fr/fr/search?p=1&query=Goya" target="_blank"><strong>Musée Goya Castres</strong>:</a> paintings, prints, copies after Goya, and documents. Its collection includes <a href="https://www.museegoya.fr/fr/notice/894-5-4-l-assemblee-de-la-compagnie-royale-des-philippines-dite-la-junte-des-philippines-7dc13026-e99a-4d4d-a924-afa2204705e9" target="_blank"><i>La Junta de Filipinas</i></a> (L'Assemblée de la Compagnie Royale des Philippines) (1815) (894-5-4), the <a href="https://www.museegoya.fr/fr/notice/894-5-3-portrait-de-francisco-del-mazo-a3f16ab8-79d5-46ea-b3e8-5fb553468f2c" target="_blank"><i>Portrait of Francisco del Mazo</i></a> (1815-19) (894-5-3), and a <a href="https://www.museegoya.fr/fr/notice/894-5-2-autoportrait-aux-lunettes-36227304-cf57-402d-a357-5c5647a9aa7c" target="_blank"><i>Self-portrait with Glasses</i></a> (c.1800) (894-5-2).</li>
90        <li><a href="https://gallica.bnf.fr/selections/fr/html/francisco-de-goya" target="_blank"><strong>Bibliothèque nationale de France</strong></a> (BnF): history of the Goya collections, and catalogue (prints).</li>
91        <li>Palais des Beaux-arts de Lille: <a href="https://pba.lille.fr/Collections/Chefs-d-OEuvre/Peintures-XVI-sup-e-sup-XXI-sup-e-sup-siecles/Les-Vieilles-Le-Temps" target="_blank"><em>Les vieilles/Le temps</em></a> and <a href="https://pba.lille.fr/Collections/Chefs-d-OEuvre/Peintures-XVI-sup-e-sup-XXI-sup-e-sup-siecles/Les-Jeunes-La-Lettre" target="_blank"><em>Les jeunes/La lettre</em></a>. Also a set of the <em>Caprichos</em>.</li>
92        <li><a href="https://www.mbaa.besancon.fr/les-collections/peinture/" target="_blank"><strong>Musée des Beaux-Arts de Besançon</strong></a>: <em>Cannibals watching human remains</em> and <em>Cannibals preparing their victims</em>
92.</li>
93        <li>Musée des Beaux-Arts de Strasbourg: <a href="https://musees-strasbourg.skin-web.org/document/portrait-de-don-bernardo-iriarte/5ee338d7461cda28a3ab1e90?q=Goya&pos=14&pgn=0" target="_blank"><em>Portrait de Don Bernardo de Iriarte</em></a>, as well as prints (after Velázquez, <em>Caprichos</em>, <em>Disparates</em>, isolated prints like <em>El agarrotado</em>).</li>
94        <li><a href="https://www.musee-agen.fr/recherche?q=Goya&tx_indexedsearch%5Bsubmit_button%5D=OK" target="_blank"><strong>Musée des Beaux-Arts d'Agen</strong></a>: some paintings attributed to Goya, such as a self-portrait and <em>Le Ballon</em>.</li>
95        <li><a href="https://mbh.bayonne.fr/" target="_blank"><strong>Musée Bonnat-Helleu</strong></a> (Bayonne): works such as the drawing <em>Dos mujeres abrazándose</em> and portrait of Goya with glasses (attributed).</li>
96        <li>PRIVATE COLLECTIONS: Paris collection (the twin portraits of Javier Goya and Gumersinda Goicoechea).</li><br>
97
98        USA<br>
99        <li><a href="https://collections.mfa.org/search/Objects/*/Goya/images?page=1" target="_blank"><strong>Boston Museum of Fine Arts</strong></a> (MoFA): paintings (<a href="https://www.metmuseum.org/art/collection/search?q=Goya&sortBy=Relevance&searchField=ArtistCulture" target="_blank"><em>Portrait of a Young Man in Brown, possibly Javier Goya</em></a>, 48.558), <a href="https://collections.mfa.org/collections/315210/dpdprints-by-goya/objects" target="_blank">prints</a> and <a href="https://collections.mfa.org/collections/315215/dpddrawings-by-goya;jsessionid=7527C5D8C33C9415AF5A65EC964E77C1/objects" target="_blank">drawings</a>.</li>
100        <li><a href="https://www.metmuseum.org/art/collection/search?q=Goya&sortBy=Relevance&searchField=ArtistCulture" target="_blank"><strong>Metropolitan Museum</strong></a> (New York): paintings, prints, and drawings.</li>
101        <li><a href="https://www.nortonsimon.org/art/in-focus/goya" target="_blank"><strong>Norton Simon Museum</strong></a> (Pasadena): history of the Goya collections, and catalogue.</li>
102        <li><a href="https://www.frick.org/art/artists/goya" target="_blank"><strong>The Frick Collection</strong></a> (New York): history of the Goya collections, and catalogue.</li>
103        <li><a href="https://hispanicsociety.emuseum.com/search/Goya*" target="_blank"><strong>The Hispanic Society of America</strong></a> (New York): Goya collection, including paintings (<i>Portrait of the Duchess of Alba in black</i>, 1797, A102; <i>Portrait of Pedro Mocarte</i>, c.1805, A1890; <i>Portrait of Don Manuel Lapeña</i>, 1799, A99), prints (<i>Los Caprichos</i>), </i>and drawings (<i>Majas fighting, a Mano observing</i>, 1796-07, A3309; <i>Majas y majos conversing</i>, 1796-97, A3310; <i>Hay pulgas?</i>, 1796-97, A3316; <i>Buena gente somos los moralistas</i>, 1796-97, A3317; <i>Juan Fernández Navarrete</i>, 1798, LA2223; <i>Tiene prisa de escapar</i>, 1808-14, A3318; <i>Torture of a man</i>, c.1812-20, A3312; <i>Peasant carrying woman</i>, 1812-20, A3315; <i>Tuti li mundi</i>, c.1808-14, A3314; <i>Regocijo</i>, 1819-23, A3308; <i>Old man on a swing</i>, 1823-28, A3313; <i>Woman with 2 children</i>, 1823-28, A3311).</li>
104        <li><a href="https://www.nga.gov/artists/1353-francisco-goya" target="_blank"><strong>National Gallery of Art</strong></a> (Washington, D.C.): paintings, prints, and drawings.</li>
105        <li><a href="https://meadowsmuseumdallas.org/collections/pages/objects-1/portfolio/?records=9&query=mfs%20any%20%22Goya%22&sort=0" target="_blank"><strong>The Meadows Museum</strong></a> (Dallas): paintings (including copies and pastiches).</li>
106        <li><a href="https://www.artic.edu/artists/40615/francisco-jose-de-goya-y-lucientes" target="_blank"><strong>Art Institute of Chicago</strong></a>: paintings, prints, and drawings (including pastiches).</li>
107        <li><a href="https://harvardartmuseums.org/collections/person/23169?person=23169" target="_blank"><strong>Harvard Art Museums</strong></a> (Cambridge, Mass.): paintings, prints and drawings (including pastiches).</li>
108        <li><strong>Museo de Arte de Ponce</strong> (Puerto Rico): <a href="https://museoarteponce.org/obras/retrato-de-martin-zapater-portrait-of-martin-zapater/" target="_blank"><em>Portrait of Martín Zapater</em></a>.</li><br>
109
110        UK<br>
111        <li><a href="https://www.nationalgallery.org.uk/artists/francisco-de-goya" target="_blank"><strong>National Gallery</strong></a> (London): <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-a-picnic" target="_blank"><em>A Picnic</em></a>, <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-a-scene-from-the-forcibly-bewitched" target="_blank"><em>A Scene from 'The Forcibly Bewitched'</em></a>, <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-don-andres-del-peral" target="_blank"><em>Don Andrés del Peral</em></a>, and <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-the-duke-of-wellington" target="_blank"><em>The Duke of Wellington</em></a>. Also <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-dona-isabel-de-porcel" target="_blank"><em>Portrait of Isabel de Porcel</em></a> (attributed).</li>
112        <li><a href="https://thebowesmuseum.org.uk/discover-the-spanish-collection-held-by-the-bowes-museum/" target="_blank"><strong>The Bowes Museum</strong></a> (Durham): paintings including the portrait of Juan Meléndez Valdé
112s and <i>Interior of a Prison</i>.</li>
113        <li><strong>English Heritage</strong>: The Wellington Collection, Apsley House (London) (<a href="https://artuk.org/discover/artworks/equestrian-portrait-of-the-1st-duke-of-wellington-17691852-144183" target="_blank">Equestrian Portrait of the 1st Duke of Wellington</a>).</li>
114        <li><a href="https://artuk.org/discover/artworks/el-medico-the-doctor-210121" target="_blank"><strong>National Galleries of Scotland</strong></a> (Edinburgh): <em>The Doctor</em>.</li>
115        <li><strong>The Courtauld Gallery</strong> (London): the drawing <a href="https://courtauld.ac.uk/highlights/cantar-y-bailar-singing-and-dancing/" target="_blank"><em>Singing and Dancing</em></a> and the painting <a href="https://courtauld.ac.uk/highlights/portrait-of-francisco-de-saavedra/" target="_blank"><em>Portrait of Francisco de Saavedra</em></a>.</li>
116        <li><strong>Pollok House</strong> (Glasgow): series of small 6 paintings of scenes with children, including <a href="https://artuk.org/discover/artworks/boys-playing-at-see-saw-84159" target="_blank"><em>Boys Playing at See-Saw</em></a> and <a href="https://artuk.org/discover/artworks/boys-playing-at-soldiers-84160" target="_blank"><em>Boys Playing at Soldiers</em></a>.</li>
117        <li><a href="https://artuk.org/discover/artworks/disparate-de-miedo-261946" target="_blank"><strong>The Higgins</strong></a> (Bedford): 'Disparate de miedo' (<em>Disparates</em>).</li><br> 
118
119        IRELAND<br>
120        <li><strong>National Gallery of Ireland</strong> (Dublin): <a href="https://www.nationalgallery.ie/art-and-artists/highlights-collection/dona-antonia-zarate-francisco-jose-de-goya-y-lucientes-1746" target="_blank"><em>Portrait of Doña Antonia de Zárate</em></a>.</li><br>
121
122        SWITZERLAND<br>
123        <li><strong>Emil Bührle Collection</strong> (Zurich): <a href="https://buehrle.ch/en/artworks/procession-in-valencia/" target="_blank"><em>Procession in Valencia</em></a>.</li>
124        <li>PRIVATE COLLECTIONS: Rothschild (including the authentic version of <em>Majas on the Balcony</em>).</li><br>
125
126        GERMANY<br>
127        <li><a href="https://recherche.smb.museum/?language=de&limit=15&sort=relevance&controls=none&conditions=AND%2BinvolvedParties%2B%22Francisco+Jos%C3%A9+de+Goya+y+Lucientes%22&collectionKey=NG*" target="_blank"><strong>Staatliche Museen zu Berlin</strong></a>: catalogue of artworks, including <em>La Cucaña</em>.</li>
128        <li><a href="https://www.sammlung.pinakothek.de/de/search?phrase=Goya#filters={}" target="_blank"><strong>Alte Pinakothek</strong></a> (Munich): paintings, prints, and drawings (including pastiches).</li><br>
129
130        ITALY<br>
131        <li><strong>Uffizi</strong> (Florence): <a href="https://www.virtualuffizi.com/es/retrato-de-la-condesa-de-chinc%C3%B3n.html" target="_blank"><em>Equestrian portrait of María Teresa de Vallabriga</em></a>.</li>
132        <li><strong>Fondazione Magnani Rocca</strong> (Parma): <a href="https://www.magnanirocca.it/goya-la-famiglia-dell-infante-don-luis/" target="_blank"><em>The Family of Infante Don Luis</em></a>.</li><br>
133
134      </p>
135      `,
136
137      PEOPLE_TITLE: "People",
138      PEOPLE_BODY: `
139      <p><strong>Project Director:</strong> Paula Fayos-Pérez, postdoctoral researcher. PhD in History of Art (University of Cambridge, 2019). Associate member of ARCHE (Université de Strasbourg) and SU+MA (Universidad Complutense de Madrid).</p>
140
141      <p><strong>Scientific Committee:</strong> The content of the Goya Network is periodically reviewed and discussed with the members of a scientific committee composed of external consultants renowned for their scholarly prestige, who provide guidance and advice on specific aspects of the project.</p>
142      <ul style="margin-top: 0.5rem; line-height: 1.6;">
143        <li><strong>Jean Michel Massing</strong>: Professor Emeritus of Art History at the University of Cambridge, King's College. Specialist in the history of printmaking, iconography, and African art.</li> <br>
144        <li><strong>Aurélie Pétiot</strong>: Lecturer at the Université Paris Nanterre. Specialist in nineteenth-century art, decorative arts, gender studies, and the Pre-Raphaelites.</li> <br>
145        <li><strong>Stephanie Loeb Stepanek</strong>: Curator Emerita of Prints and Drawings at the Museum of Fine Arts, Boston. Specialist in Goya's prints and the history of Goya collecting in the United States.</li> <br>
146        <li><strong>Anita L. Verő</strong>: PhD in Artificial Intelligence (University of Cambridge, 2022), computer scientist and founder of <a href="https://www.consonant-ai.com" target="_blank">ConsonantAI</a>. Support with the technical development of the website.</li>
147      </ul>
148
149      <p class="section-heading">Contributors</p>
150      <ul>
151        <li><strong>Manuela B. Mena-Marqués</strong>: PhD in Art History (Complutense University of Madrid, 1976). Curator of the Prints and Drawings Department (1979-1981), Deputy Director (1981-1996) and Head of the Curatorial Department for 18th-Century Painting and Goya at the Prado Museum (1996-2019). Contribution: feedback on the website (June 2025).</li> <br>
152        <li><strong>Joëlle Arches</strong>, Chief Curator and Director of the <a href="https://www.museegoya.fr/" target="_blank">Musée Goya</a> and Jean Jaurès Museum, and <strong>Cécile Berthoumieu</strong>, Senior Heritage Curator (Attachée principale de conservation du patrimoine). Contribution: website feedback; suggested adding Marcel Briguiboul to the network (September 2025).</li> <br>
153        <li><strong>Stephanie Loeb Stepanek</strong>: Curator Emerita of Prints and Drawings, Boston Museum of Fine Arts. Contribution: information on an early edition of <i>Disasters of War</i>, printed by Laureano Potenciano (October 2025).</li>
154      </ul>
155      `,
156
157      ABOUT_TITLE: "About the Goya Network",
158      ABOUT_BODY: `
159
160      <p><strong>Introduction:</strong> Throughout the 18th and 19th centuries, networks of artists, writers, collectors and intellectuals were crucial for artistic production, as well as for the dissemination and understanding of historical figures. Mapping these connections allows us to better understand the dynamics of sociability and the circulation of ideas. In recent years, network analysis has become an important tool in Digital Humanities for illuminating historical artistic milieus (see the <a href="#" onclick="showSection('relevant-links'); return false;">Relevant links</a> section for examples of similar projects).</p> 
161
162      <p><em>The Goya Network</em> builds on these methods, offering an interactive database that maps the social and professional relationships surrounding the Spanish artist Francisco de Goya, focusing on his connections with artists, critics, and collectors in 19th-century Spain and France. Unlike traditional studies of his work, this project emphasizes the relational context that shaped Goya's legacy, offering a novel tool for the study of historical artists' social networks. As a 'living' database, it facilitates cross-disciplinary collaboration and serves as both a research tool and a public resource, providing insight into the scope of Goya's influence and the reception of his work.</p>
163
164      <p><strong>Origin:</strong> This is a research-based digital project developed by Paula Fayos-Pérez as part of her postdoctoral work on Goya's social and professional networks. It is a continuation of the research done during her PhD (University of Cambridge, 2018) <a href="https://www.repository.cam.ac.u
164k/items/8d9751ce-aca1-4d35-9436-0820ecbc98d4" target="_blank">(repository)</a>, which was later transformed into a book: <a href="https://www.ceeh.es/en/publicacion/goyas-caprichos-in-nineteenth-century-france-politics-of-the-grotesque/" target="_blank"><em>Goya's Caprichos in Nineteenth-Century France. Politics of the Grotesque</em></a> (CEEH, 2024).</p>
165
166      <p><strong>Funding:</strong> This project was initiated with the support of a 2024 Leonardo Grant (<a href="https://www.redleonardo.es/beneficiario/paula-fayos-perez/" target="_blank">Beca Leonardo</a>) for Scientific Research and Cultural Creation, funded by <a href="https://www.fbbva.es/" target="_blank">BBVA Foundation.</a> BBVA Foundation takes no responsibility for the opinions, statements and contents of this project, which are entirely the responsibility of its authors.</p>
167
168      <p>The project is currently supported by a research grant from <a href="https://www.gerda-henkel-stiftung.de/" target="_blank">Gerda Henkel Stiftung (Gerda Henkel Foundation).</a></p>
169
170      <p><strong>User guide:</strong> There is data contained in each node (individual) and edge (connection between two individuals). On the nodes, users can see biographic information as well as artistic/literary production, art collection and link to Goya's work. On the edges, one can see the relationship type, correspondence, collaborations, and others. Edges in grey/silver colour are direct connections (friends, colleagues, relatives, acquaintances, etc.), while edges in golden colour are secondary connections (e.g. artists and writers who copied or wrote about Goya but never met him in person). The size of the nodes is proportional to the number of edges (connections) they hold.</p>
171
172      <p><strong>Nature of the project:</strong> The Goya Network is conceived as a collaborative, crowd-sourced database with the aim of becoming a useful tool for scholars, students and the general public of Goya. We welcome contributions by art historians, historians, Hispanists, lecturers and curators specialised in Goya, 19th-century Spain, 19th-century France and in any of the members of the network (e.g. Federico Madrazo, Prosper Mérimée, Eugène Delacroix, etc.). To submit a contribution, please see the Contributions section on the website below.</p>
173
174      <p><strong>Methodology:</strong> <u>Art historical research</u>: based on systematic analysis of bibliographic and archival sources (such as correspondence) from 19th-century France and Spain. Network nodes and edges were constructed through careful cross-referencing of academic publications (monographs, peer-reviewed articles, exhibition catalogues), institutional websites and online collection databases of museums and cultural foundations, and verified digital resources.<br>
175      <u>Web development</u>: built using HTML and JavaScript, with structured data in JSON, and version control and deployment through GitHub, developed with the assistance of ChatGPT (OpenAI), DeepSeek (High-Flyer), and Claude (Anthropic). Initial website setup in collaboration with Anita Verő <a href="https://www.consonant-ai.com" target="_blank">(ConsonantAI)</a>.</p>
176
177      <p><strong>Copyright and License:</strong> The Goya Network is protected by notarial deed (Madrid, 2025), documenting the research and development process from 2018 to 2025.  
178      Its code and structure are licensed under <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" style="color:#66ccff;">CC BY-NC 4.0</a>, allowing non-commercial academic use with proper attribution to the author.</p>
179
180      <p><strong>Data and cookies:</strong> This website uses cookies exclusively for Google Analytics, which anonymously tracks visitor behaviour. We use this information only to understand how the site is being used and to improve its content and navigation. You can opt out of Google Analytics cookies across all websites by installing the <a href="https://tools.google.com/dlpage/gaoptout" target="_blank" style="color:#66ccff;">Google Analytics Opt-out Browser Add-on</a>
180. For more information on how Google uses cookies, visit the <a href="https://developers.google.com/analytics/devguides/collection/analyticsjs/cookie-usage" target="_blank" style="color:#66ccff;">Cookies & Google Analytics</a> page.</p>
181      `,
182
183      NEWS_TITLE: "News",
184      NEWS_BODY: `
185      <ul style="padding-left:1.2rem; line-height:1.7;">
186        <li>
187          <strong>July 2026.</strong> The Goya Network has been presented at the <a href="https://www.eventbrite.co.uk/e/goya-network-tickets-1991696763635?aff=oddtdtcreator" target="_blank">Instituto Cervantes London</a>, in an event chaired by Manuela B. Mena Marqués.
188        </li>
189        <li>
190          <strong>May 2026.</strong> The research project developping the Goya Network has now received funding from the <a href="https://www.gerda-henkel-stiftung.de/" target="_blank">Gerda Henkel foundation (Germany)</a>.
191        </li>
192        <li>
193          <strong>March 2026.</strong> The Goya Network continues to expand with new members and partnerships. New partnership: <a href="https://crimic-sorbonne.fr/" target="_blank">CRIMIC (Sorbonne Université)</a>.
194        </li>
195        <li>
196          <strong>September 2025.</strong> A new partnership has been established with <a href="https://artes-uk.org/" target="_blank">ARTES (Iberian & Latin American Visual Culture Group) (UK)</a>.
197        </li>
198        <li>
199          <strong>September 2024.</strong> The Goya Network obtains funding from the <a href="https://www.redleonardo.es/noticias/paula-fayos-crea-goya-network-beca-leonardo/" target="_blank">BBVA Foundation (Leonardo Grant)</a>.
200      </li>
201      </ul>
202      `,
203
204      MENTIONS_TITLE: "Mentions",
205      MENTIONS_BODY: `
206      <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Academic articles</h3>
207      <ul style="padding-left:1.2rem; line-height:1.7;">
208        <li><strong><i>Leonardo</i> (MIT Press): forthcoming</strong> – Paula Fayos Pérez & Anita Verő, “The Goya Network: Mapping Goya's Impact in 19th-Century France and Spain” (in English).</li>
209      </ul>
210
211      <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Conferences and presentations</h3>
212      <ul style="padding-left:1.2rem; line-height:1.7;">
213        <li><strong>Instituto Cervantes London (2026)</strong> – 'Goya Network: Francisco de Goya's influence on 19th-century French art and literature' (in English) <a href="https://www.eventbrite.co.uk/e/goya-network-tickets-1991696763635?aff=oddtdtcreator" target="_blank">(see event)</a>.</li>  
214        <li><strong>Prado Joven 2025 (Museo del Prado)</strong> – 'El impacto de Goya en el siglo XIX: de Gautier a Madrazo' (in Spanish) <a href="https://www.museodelprado.es/recurso/programa-joven-2025/570e6e48-01fc-48b4-b8f6-270e3b3141f3" target="_blank">(program)</a>. <a href="https://www.youtube.com/watch?v=jSGfYz08mP83" target="_blank">Watch video</a>.</li>
215        <li><strong>Congreso Humanidades Digitales Hispánicas 2025 (Universidad de Valencia)</strong> – 'La Red Goya: Humanidades Digitales para mapear la recepción de Goya en el siglo XIX' (in Spanish) <a href="https://hdh2025.uv.es/static/files/Programa_Congreso_1_8.pdf" target="_blank">(view program)</a>.</li>
216        <li><strong>Asociación Humanidades Digitales Hispánicas (2025)</strong> – 'Un café con Paula Fayos Pérez. La Red Goya: Humanidades Digitales para mapear la recepción de Goya en el siglo XIX' (in Spanish) <a href="https://humanidadesdigitaleshispanicas.es/cafe-con-paula-fayos-perez-16-de-diciembre-de-2025/" target="_blank">(see event)</a>.</li>
217      </ul>
218
219      <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Online presence</h3>
220      <ul style="padding-left:1.2rem; line-height:1.7;">
221        <li><strong>ARTES UK (Iberian & Latin American Visual Culture Group)</strong> – <a href="https://artes-uk.org/links-2/" target="_blank">the Goya Network included among the links</a> (in English).</li>
222        <li><strong>XIX (Société des études romantiques & dix-neuviémistes)</strong> – <a href="https://serd.hypotheses.org/19526" target="_blank">The Goya Network (Réseau Goya)</a> (in French).</li>
223        <li><strong>Daumier.de</strong> – <a href="https://www.daumier.de/2025/07/30/das-goya-network/" target="_blank">Das Goya Network</a> (in German).</li>
224        <li><strong>Red Leonardo</strong> – <a href="https://www.redleonardo.es/noticias/paula-fayos-crea-goya-network-beca-leonardo/?fbclid=IwY2xjawMuhCNleHRuA2FlbQIxMQBicmlkETFmVHVybzVkRTNnNEZ3bGExAR7q3T7lseZC1CU_pp-vGJWXCjVU8C_gUFvqiVPRd7efUEbjLWnIKRb_WU9Ieg_aem_JIlJ6CO77pnEaRFtx1uKmQ" target="_blank">'Paula Fayos crea la Goya Network [...]'</a> (in Spanish).</li>
225      </ul>
226
227      <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Interviews</h3>
228      <ul style="padding-left:1.2rem; line-height:1.7;">
229        <li><strong><i>Mas De Arte</i></strong> – <a href="https://masdearte.com/especiales/paula-fayos-perez/" target="_blank">Paula Fayos Pérez in Especiales/Fichados</a> (in Spanish).</li>
230        <li><strong><i>El País</i></strong> – forthcoming (in Spanish).</li>
231      </ul>
232      `,
233
234      RELEVANT_LINKS_TITLE: "Relevant links & resources",
235      RELEVANT_LINKS_BODY: `
236
237      <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Goya</h3>
238      <ul style="padding-left:1.2rem; line-height:1.6;">
239        <li><a href="https://www.museodelprado.es/coleccion/artista/francisco-de-goya-y-lucientes/39568a17-81b5-4d6f-84fa-12db60780812" target="_blank"><strong>Goya en el Prado </strong></a> – A digital resource of the artist to learn about his paintings, prints and drawings in the museum, as well as documents, print references and a digital library (in Spanish).</li>
240        <li><a href="https://www.realacademiabellasartessanfernando.com/goya/la-academia-y-goya/" target="_blank"><strong>Real Academia de Bellas Artes de San Fernando: La Academia y Goya</strong></a> (in Spanish) – The History of Goya with the Royal Academy of Fine Arts in Madrid, as well as an overview of the Goya collections, particularly his print series.</li>
241      </ul>
242
243      <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">19th Century / France</h3>
244      <ul style="padding-left:1.2rem; line-height:1.6;">
245        <li><a href="https://www.musee-delacroix.fr/fr/informations-complementaires/liens/societe-des-amis-du-musee" target="_blank"><strong>Société des Amis du musée Delacroix</strong></a> – Dedicated to the study and promotion of Eugène Delacroix's work and legacy (in French).</li>
246        <li><a href="https://prosper-merimee.com/" target="_blank"><strong>Société Mérimée</strong></a> – An organisation and academic journal (<i>Cahiers Mérimée</i>)dedicated to the figure of Prosper Mérimée (in French).</li>
247        <li><a href="https://www.societedesetudesmarcelinedesbordesvalmore.fr/" target="_blank"><strong>Société des études Marceline Desbordes-Valmore</strong></a> – Promoting the study of the 19th-century French poet and novelist (in French).</li>
248        <li><a href="https://www.theophilegautier.fr/" target="_blank"><strong>
248Société Théophile Gautier</strong></a> – An association dedicated to the life, work, and literary legacy of Théophile Gautier (in French).</li>
249        <li><a href="https://www.daumier.de/" target="_blank"><strong>Daumier.de</strong></a> – Dedicated to the study and promotion of Honoré Daumier's work and legacy (in German).</li>
250        <li><a href="https://serd.hypotheses.org/" target="_blank"><strong>XIX Société des études romantiques & dix-neuvièmistes</strong></a> – A society dedicated to the study of Romanticism and the 19th century (in French).</li>
251        <li><a href="https://19cwwfw.org/" target="_blank"><strong>Nineteenth-Century Women Writers in the Francophone World</strong></a> (by Bénédicte Monicat and Morgane Haesen) – A database of female writers in the 19th century in the francophone world (in English).</li>
252        <li><a href="https://19thc-artworldwide.org/" target="_blank"><strong>Nineteenth-Century Art Worldwide</strong></a> – A journal dedicated to the study of 19th-century art and visual culture (in English).</li>
253      </ul>
254
255      <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">General databases</h3>
256      <ul style="padding-left:1.2rem; line-height:1.6;">
257        <li><a href="https://historia-hispanica.rah.es/" target="_blank"><strong>Real Academia de la Historia (RAH): Historia Hispánica</strong></a> – A digital portal created by the Spanish Royal Academy of History that uses a map to show the global history of Spain and the territories that were part of the Spanish administration (Spanish/English).</li>
258        <li><a href="https://www.getty.edu/" target="_blank"><strong>J. Paul Getty Trust</strong></a> – Includes the Getty Museum, Getty Research Institute, and extensive art historical databases (in English).</li>
259        <li><a href="https://www.bnf.fr/" target="_blank"><strong>Bibliothèque nationale de France (BnF)</strong></a> – France's National Library with digitized collections including letters, manuscripts, and prints (in French/English/Spanish).</li>
260        <li><a href="https://www.bne.es/" target="_blank"><strong>Biblioteca Nacional de España (BNE)</strong></a> – Spain's National Library with extensive digital resources (in Spanish).</li>
261      </ul>
262
263      <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Other Digital Humanities (DH) Projects</h3>
264      <ul style="padding-left:1.2rem; line-height:1.6;">
265        <li><a href="https://arkivet.thorvaldsensmuseum.dk/en" target="_blank"><strong>Thorvaldsen Letter Archive</strong></a> – Mapping the correspondence and social networks of the neoclassical sculptor Bertel Thorvaldsen (English/Dansk).</li>
266        <li><a href="http://republicofletters.stanford.edu/casestudies/index.html" target="_blank"><strong>Mapping the Republic of Letters</strong></a> (University of Stanford) – An interactive visualization of intellectual networks in early modern Europe (in English).</li>
267        <li><a href="http://emlo.bodleian.ox.ac.uk/home" target="_blank"><strong>Early Modern Letters Online (EMLO)</strong></a> (University of Oxford) – A catalog and analysis of correspondence from the early modern period (in English).</li>
268        <li><a href="https://mappingrome.com/" target="_blank"><strong>Mapping Rome</strong></a> – Visualizing social, economic, and cultural networks in ancient Rome (in English).</li>
269        <li><a href="https://vangoghworldwide.org/" target="_blank"><strong>Van Gogh Worldwide</strong></a> – A collaborative platform connecting Van Gogh's works, letters, and cultural impact across institutions worldwide (in English).</li>
270        <li><a href="https://womensbios.lib.virginia.edu/index.html" target="_blank"><strong>Collective Biographies of Women</strong></a> (Alison Booth, University of Virginia) – Aggregating biographical data on women across multiple disciplines and time periods (in English).</li>
271      </ul>
272      `,
273
274      CONTACT_TITLE: "Contact",
275      CONTACT_BODY: `
276      <p>For collaborations, suggestions, or contributions:</p>
277
278      <p style="margin-top:0.5rem;">
279        <a href="mailto:[email protected]" style="font-weight:bold;">
280          [email protected]
281        </a>
282      </p>
283
284      <p style="margin-top:1.5rem;">
285        See also the contributions form at the bottom of the page.
286      </p>
287      `,
288    },
289    es: {
290      BTN_NETWORK:"Red", BTN_ABOUT:"Acerca de", BTN_PEOPLE:"Equipo", BTN_PARTNERS:"Colaboradores",
291      BTN_ARTWORKS:"Obra de Goya", BTN_BIB:"Bibliografía", BTN_CITE:"Cómo citar", BTN_MENTIONS:"Menciones", BTN_NEWS:"Noticias", BTN_RELEVANT_LINKS:"Enlaces relevantes", BTN_CONTACT:"Contacto",
292      FUNDING_PARTNERS: "Entidades financiadoras",
293      ACADEMIC_PARTNERS: "Colaboradores académicos",
294      GERDA_HENKEL_NAME: "Fundación Gerda Henkel",
295      SEARCH_PH:"Buscar…", FILTER_PROF_ALL:"Todas las profesiones", FILTER_NAT_ALL:"Todas las nacionalidades",
296      NEW_NODES_TITLE: "Nuevos miembros",
297      MEMBERS_TITLE:"Todos los miembros (A-Z por apellido)", LOADING:"Cargando la red…", LAST_UPDATE:"Última actualización", HELP_TEXT_1: "Haz clic en un nodo (un individuo) o en una arista (una conexión entre dos individuos) para ver los datos que contienen.", HELP_TEXT_2: "(La red puede tardar unos segundos en mostrarse)", SITE_TITLE: "Red Goya", SITE_SUBTITLE: "Cartografiar el impacto de Goya en la Francia y la España del siglo XIX", 
298      BTN_SEARCH: "Buscar",
299      BTN_CLUSTER_TOGGLE_ON: "Clúster: activado",
300      BTN_CLUSTER_TOGGLE_OFF: "Clúster: desactivado",
301      SELECT_CLUSTER_OPTION: "Selecciona clúster...",
302      
303      CONTRIBUTIONS_TITLE: "Contribuciones", 
304      CONTRIBUTIONS_TEXT: `
305      <p>La Red Goya se concibe como una base de datos colaborativa y abierta.</p>
306      <p>Las contribuciones y sugerencias son bienvenidas, y los colaboradores serán acreditados si así lo desean.</p>
307      `,
308      
309      PARTNERS_TITLE: "Colaboradores",
310
311      NOTARIAL_PROTECTION: "Protegido por acta notarial (Madrid, 2025)",
312      ACCESS_FORM: "Formulario de acceso",
313      RIGHTS_LABEL: "Derechos:",
314
315      CITE_TITLE: "Cómo citar la Red Goya",
316      BIB_TITLE: "Bibliografía", BIB_INTRO: "Esta sección reúne las fuentes bibliográficas empleadas a lo largo del proyecto. Las referencias que aparecen en los no
316dos y en las conexiones se presentan en forma de citas abreviadas (por ejemplo, Adhémar 1958).",
317
318      //profesiones
319      "Actor":"Actor/Actriz","Archaeologist":"Arqueólogo/a","Anthropologist":"Antropólogo/a","Architect":"Arquitecto/a",  "Aristocrat":"Aristócrata","Art critic":"Crítico de arte","Art dealer":"Marchante de arte","Art patron":"Mecenas","Art historian":"Historiador/a del arte","Art theorist":"Teórico/a del arte","Artist":"Artista","Banker":"Banquero/a","Bibliographer":"Bibliógrafo","Bibliophile":"Bibliófilo","Businessman":"Empresario/a","Caricaturist":"Caricaturista","Ceramist":"Ceramista","Civil servant":"Funcionario","Clergyman":"Clérigo","Collector":"Coleccionista","Composer":"Compositor/a","Critic":"Crítico/a", "Curator":"Conservador de museo", "Designer":"Diseñador/a","Diplomat":"Diplomático/a","Draughtsman":"Dibujante","Economist":"Economista","Editor":"Editor/a", "Engineer":"Ingniero","Engraver":"Grabador/a","Essayist":"Ensayista","Femme de lettres":"Mujer de letras","Hispanist":"Hispanista", "Historian":"Historiador","Illustrator":"Ilustrador", "Printer":"Impresor","Journalist":"Periodista","Jurist":"Jurista","Lawyer":"Abogado","Librarian":"Bibliotecario/a","Librettist":"Libretista","Lithographer":"Litógrafo/a","Lithographic printer":"Impresor/a litográfico/a","Medical doctor":"Médico/a","Military member":"Militar","Musician":"Músico/a","Novelist":"Novelista","Painter":"Pintor/a","Philanthropist":"Filántropo/a","Philosopher":"Filósofo/a","Photographer":"Fotógrafo/a","Pianist":"Pianista","Playwright":"Dramaturgo/a","Poet":"Poeta","Politician":"Político/a","Print editor":"Editor de grabados","Print dealer":"Marchante de grabados", "Real estate agent":"Agente inmobiliario/a","Salonnière":"Salonnière","Scholar":"Académico/a","Scientist":"Científico/a","Sculptor":"Escultor/a","Secret agent":"Agente secreto/a","Singer":"Cantante","Socialite":"Figura social","Teacher":"Profesor","Translator":"Traductor/a", "Traveller":"Viajero", "Writer":"Escritor/a",
320      
321      //nacionalidades
322      "Spanish":"Española","French":"Francesa","British":"Británica","English":"Inglesa","Scottish":"Escocesa","Irish":"Irlandesa","Italian":"Italiana","German":"Alemana","Swiss":"Suiza","Belgian":"Belga","Dutch":"Neerlandesa","Austrian":"Austríaca","Portuguese":"Portuguesa","American":"Estadounidense", "Cuban":"Cubana", "Polish":"Polaca", "German / Czech":"Alemana / Checa",
323
324      //Categorías en nodos y edges
325      "Life dates":"Fechas de vida","Sex":"Sexo","male":"hombre","female":"mujer","Full name":"Nombre completo","Also known as":"También conocido/a como","Other names":"Otros nombres","Pseudonyms":"Seudónimos", "Profession":"Profesión","Nationality":"Nacionalidad","Born in":"Nace en","Dies in":"Fallece en","Considered as":"Considerado/a como", "Political views":"Ideas políticas","Military activity":"Actividad militar","Participates in":"Participa en", "Aristocratic titles":"Títulos nobiliarios","Ties with slavery/colonialism":"Vínculos con esclavitud/colonialismo","Curiosities":"Curiosidades", "Parents":"Padres","Siblings":"Hermanos/as","Married to":"Casado/a con","Partners/lovers":"Parejas/amantes","Children":"Hijos/as", "Friends":"Amigos/as","Rivals":"Rivales","Correspondence":"Correspondencia","Meets":"Encuentros","Studies in":"Estudia en", "Works as":"Trabaja como","Works for":"Trabaja para","Influenced by":"Influido/a por","Follower of":"Seguidor/a de","Masters":"Maestros/as","Students":"Alumnos/as", "Member of":"Miembro de","Founder of":"Fundador/a de","Editor of":"Editor/a de","Writes in":"Escribe en","Collaborates with":"Colabora con", "Registered in":"Registrado/a en","Mentioned in the French press":"Mencionado/a en la prensa francesa","Decorations/awards":"Condecoraciones/premios", "Literary salon or tertulia":"Salón literario o tertulia","Author of":"Autor/a de","Collection":"Colección", "Patronage":"Mecenazgo","Portraits":"Retratos","Exhibitions":"Exposiciones", "Bibliography":"Bibliografía","Websites":"Páginas web", "Podcasts":"Podcasts","Newspaper archive":"Hemeroteca","Archives":"Archivos","Added":"Añadido","Last modified":"Última modificación", "Identity and Personal Data":"Identidad y datos personales", "Social and Political Identity":"Identidad social y política", "Family and Personal Relationships":"Familia y relaciones personales", "Education, Profession and Institutions":"Formación, profesión e instituciones", "Artistic/Literary Production and Collection":"Producción artística/literaria y colección", "Exhibitions and Public Reception":"Exposiciones y recepción pública", "Relationship with Goya":"Relación con Goya", "Copies after Spanish artworks":"Copias de obras españolas", 
326      "Sources and Documentation":"Fuentes y documentación", "Connections":"Conexiones", "Artistic Activity, Art Collecting and Patronage":"Actividad artística, coleccionismo y mecenazgo", "Geographic Presence and Movement":"Presencia geográfica y desplazamientos", "Relationship to Goya and His Work":"Relación con Goya y su obra", "Portrayed by":"Retratado/a por", "Views on art and cultural heritage":"Reflexiones sobre arte y patrimonio cultural", "Image source":"Origen de la imagen", "In Spain":"En España", "Knows Goya's works":"Conoce la obra de Goya", "Address":"Dirección", "Link to Goya's work":"Vínculo con la obra de Goya", "Interest in Spanish art":"Interés en el arte español", "Mentions of Goya":"Menciones de Goya", "Collector of Goya's works":"Coleccionista de la obra de Goya", "In Paris":"En París", "mentions": "Menciones", "Conferences/seminars":"Conferencias/seminarios", "Trips":"Viajes", "Lives in":"Vive en", "Patrons": "Mecenas", "Collector of":"Coleccionista de", "Copies after Goya":"Copias de Goya", "Writes about Goya":"Escribe sobre Goya", "Mentions of Goya":"Menciones de Goya", "Connection": "Conexión", "Type of connection": "Tipo de conexión", "Type of relationship": "Tipo de relación", "Know each other since": "Se conocen desde", "Search": "Buscar", "Collaborations": "Colaboraciones", "They met": "Se encontraron", "direct": "directa", "Artworks bought/sold": "Compraventa de obras", "Interactions": "Interacciones", "Employment/patronage": "Empleo/patronazgo", "Between": "Entre", "and": "y", "Between [X] and [Y]": "Entre [X] y [Y]", "In France": "En Francia", "In Italy": "En Italia", "direct": "directa", "secondary": "secundaria", "acquaintances?": "¿conocidos?", "direct?": "¿directa?", "secondary?": "¿secundaria?", "portraitist-sitter": "retratista-retratado", "employer-employee": "empleador-empleado", "family": "familia", "friends": "amigos", "acquaintances": "conocidos", "master-student": "maestro-alumno", "partners/lovers": "parejas/amantes", "artworks influenced by Goya": "influenciado por Goya", "knows Goya's works": "conoce la obra de Goya", "Sales": "Ventas", "colleagues": "colegas", "copies": "copias", "Nodes": "Nodos", "Connections": "Conexiones", "Access form": "Acceder al formulario", "Unknown": "desconocida", "Visits the Prado Museum": "Visita el Museo del Prado", "Art collection":"Colección de arte", "Artworks bought/sold/given":"Compraventa/cesión de obras", "Artworks influenced by Goya":"Obras influenciadas por Goya", "BnF":"BnF", "Commissions":"Encargos", "Commissions Goya with":"Encarga a Goya", "Common friends":"Amigos comunes", "Comparisons":"Comparaciones", "Connection Strength":"Intensidad de la conexión", "Criticism":"Crítica", "Discovers Goya's works":"Descubre la obra de Goya", "Disseminates Goya's works":"Difunde la obra de Goya", "Exchanges":"Intercambios", "Feuds":"Enemistades", "Getty ULAN":"Getty ULAN", "In Madrid":"En Madrid", "Influence":"Influencia", "Influence of Goya":"Influencia de Goya", "Introductions":"Presentaciones", "Link to Goya":"Vínculo con Goya", "Married in":"Casado/a en", "Mentions the Prado commentaries":"Menciona los comentarios del Prado", "Museum collections":"Colecciones de museos", "Quotations of Goya":"Citas de Goya", "RAH":"RAH", "Rivalry":"Rivalidad", "Shared":"Compartido", "Shows Goya":"Muestra a Goya", "They shared":"Compartieron", "Wikidata":"Wikidata",
327
328      ARTWORKS_TITLE: "La obra de Goya",
329      ARTWORKS_BODY: `
330      <p>
331      <strong>La cuestión de la atribución en Goya:</strong><br>
332      Al estudiar a Goya conviene tener presente que, debido a la enorme demanda de su obra desde la muerte del artista (que supera con creces la oferta de piezas auténticas), se han producido innumerables copias, imitaciones y pastiches, especialmente en la España y la Francia del siglo XIX. Muchas de estas continúan exhibiéndose como auténticos «Goyas» (más eufemísticamente, «atribuido a Goya», o incluso «taller de Goya») en museos de todo el mund
332o, impulsadas por intereses económicos y por el prestigio social. Y cada año aparecen nuevos «Goyas» en galerías, entre marchantes y en colecciones privadas.
333      </p>
334
335      <p>
336      <strong>Catálogos razonados, catálogos de exposiciones fundamentales y biografía</strong> (véase la sección Bibliography):<br>
337      <ul>
338        <li><strong>Obra completa:</strong> Juliet Wilson-Bareau & Pierre Gassier. <em>Vie et œuvre de Francisco Goya. L'œuvre complet illustré. Peintures, dessins, gravure</em>, 1970 (francés); <em>The Life and Complete Work of Francisco Goya: With a Catalogue Raisonné of the Paintings, Drawings and Engravings</em>, 1971 (inglés); <em>Vida y obra de Francisco Goya: reproducción de su obra completa, pinturas, dibujos y grabados</em>, 1974 (español). Véase también el catálogo de la exposición retrospectiva <i>Goya</i>, Martin Schwander (ed.), Fondation Beyeler, 2021 (inglés/español/alemán).</li>
339        <li><strong>Dibujos:</strong> Manuela Mena-Marqués & José Manuel Matilla, <em>Catálogo Razonado de los Dibujos de Francisco de Goya, vol. 2 (1771-1792)</em>, con G. Maurer, G. Solache, J. Carrete, J. Wilson & V. Albarrán, 2018 (español); <em>Goya: dibujos. 'Solo la voluntad me sobra'</em>, cat. exp., 2019 (español/inglés); y Pierre Gassier, <em>Les dessins de Goya. Les albums</em>, 1973 (francés), <em>Francisco Goya. Drawings. The Complete Albums</em>, 1973 (inglés).</li>
340        <li><strong>Estampas:</strong> Tomas Harris, <em>Goya. Engravings and Lithographs</em>, 1964. Véase también Juan Carrete Parrondo & Jesusa Vega, <em>Goya grabador</em>, 1991.</li>
341        <li><strong>Biografía</strong>: Janis Tomlinson, <em>Goya: A Portrait of the Artist</em>, 2020 Princeton UP (inglés), 2022 Cátedra (español).</li>
342      </ul>
343      </p>
344
345      <p>
346      <strong>Recursos en línea:</strong><br>
347      <ul>
348        <li><strong>Wikipedia:</strong> una panorámica amplia y colaborativa de las obras de Goya presentada en forma de tabla, con título, imagen, fecha, localización y atribución. <a href="https://en.wikipedia.org/wiki/List_of_works_by_Francisco_Goya" target="_blank"> List of Works by Francisco de Goya</a> (inglés) – pinturas y estampas; <a href="https://es.wikipedia.org/wiki/Anexo:Cuadros_de_Goya" target="_blank"> Cuadros de Goya</a> (español) – pintura (no exhaustivo).</li><br>
349        <li><a href="https://fundaciongoyaenaragon.es/" target="_blank"><strong>Fundación Goya en Aragón:</strong></a> catálogo en línea de la obra de Goya elaborado por la Fundación Goya en Aragón (Gobierno de Aragón), que incluye descripciones, procedencia, bibliografía e imágenes. Este catálogo, sin embargo, reúne <em>todas</em> las obras que en algún momento se han atribuido a Goya, incluidas copias y pastiches.</li><br>
350      </ul>
351      </p>
352
353      <p>
354      <strong>COLECCIONES GOYA: museos, bibliotecas, iglesias y colecciones privadas:</strong><br>
355        <br>
356        ESPAÑA
357        <li><a href="https://www.museodelprado.es/coleccion/artista/goya-y-lucientes-francisco-de/39568a17-81b5-4d6f-84fa-12db60780812" target="_blank"><strong>Museo del Prado</strong></a> (<em>Goya en el Prado</em>): obras (pintura, estampa y dibujo) conservadas en el Museo del Prado, documentos, referencias impresas y biblioteca digital.</li>
358        <li><a href="https://www.realacademiabellasartessanfernando.com/goya/la-academia-y-goya/" target="_blank"><strong>Real Academia de Bellas Artes de San Fernando</strong></a> (RABASF): historia del vínculo de Goya con la Academia, catálogo de obras, <em>Calcografía Nacional</em> (producción de las estampas de Goya).</li>
359        <li><strong>Patrimonio Nacional</strong> (patrimonio nacional español): pinturas como <a href="https://www.patrimonionacional.es/colecciones-reales/pintura/retrato-de-carlos-iv-como-cazador" target="_blank"><em>Portrait of Carlos IV as a Hunter</em></a> y <a href="https://www.galeriadelascoleccionesreales.es/obra-de-arte/retrato-de-maria-luisa-de-parma-en-traje-de-corte/19602330-5985-4e7e-81f7-25dce124b6a9" target="_blank"><em>Portrait of María Luisa de Parma in Court Dress</em></a>, así como los tapices basados en cartones de Goya, como <a href="https://www.patrimonionacional.es/colecciones-reales/textiles-y-tapiceria/la-era-o-el-verano" target="_blank"><em>The Summer</em></a>.</li>
360        <li><a href="https://www.bne.es/es/blog/blog-bne/post-80" target="_blank"><strong>Biblioteca Nacional de España</strong></a> (BNE): historia de las colecciones de Goya y catálogo.</li>
361        <li><a href="https://coleccion.bde.es/wca/es/secciones/coleccion/artistas/francisco-de-goya-y-lucientes-896.obras.html" target="_blank"><strong>Banco de España</strong></a>
361 (Madrid): pinturas (como los retratos de Francisco Cabarrús, Conde de Floridablanca, XI Conde de Altamira y Carlos III) y estampas (<em>Desastres de la guerra</em>, <em>Tauromaquia</em>, <em>Disparates</em>).</li>
362        <li><a href="https://www.museolazarogaldiano.es/obras-goya-museo-lazaro-galdiano" target="_blank"><strong>Museo Lázaro Galdiano</strong></a> (Madrid): su colección incluye las pinturas <i>El Aquelarre</i> (1797-98) y <i>Las Brujas/El conjuro</i> (1797-98), los bocetos al óleo <i>Santa Isabel de Portugal curando a una enferma</i> (c.1799) y <i>San Hermenegildo en la prisión</i> (1800), y el boceto preparatorio <i>El verano</i> (1786); estampas como <i>El can</i> (c.1820-27); y dibujos como <i>Felipe III</i> (1778), <i>Retrato ecuestre del Conde-Duque de Olivares</i> (c.1776-78), <i>Portrait of Giambattista Casti</i> (c.1802-03), <i>Modo de volar</i> (1815-16), <i>Mendigo ciego con un perro</i> (c.1824) y <i>Mendigo con acordeón</i> (c.1824-28).</li>
363        <li><a href="https://www.museothyssen.org/coleccion/artistas/goya-francisco" target="_blank"><strong>Museo Thyssen Bornemisza</strong></a> (Madrid): biografía y colección, que incluye <i>Retrato de Asensio Julià</i> (c.1798), <i>Retrato de Fernando VII</i> (1814-15) y <i>El Tío Paquete</i> (c.1819-20).</li>
364        <li><a href="https://museogoya.fundacionibercaja.es/goya-en-zaragoza.php" target="_blank"><strong>Museo Goya - Colección Fundación Ibercaja - Museo Camón Aznar</strong></a> (Zaragoza).</li>
365        <li><a href="https://fundacionfuendetodosgoya.org/museo-del-grabado/" target="_blank"><strong>Museo del Grabado</strong></a> (Fuendetodos, Zaragoza): museo de obra gráfica en la casa natal de Goya.</li>
366        <li><strong>IGLESIAS Y CATEDRALES</strong>: Ermita de San Antonio de la Florida (Madrid), Real Basílica de San Francisco el Grande (Madrid), Catedral de Valencia, Catedral de Sevilla, Basílica del Pilar (Zaragoza), Cartuja del Aula Dei (Zaragoza).</li>
367        <li><strong>COLECCIONES PRIVADAS</strong> (selección): colección Abelló; colección Marqués de la Romana; Duke of Alba (Palacio de Liria); Dukes of Osuna (Alameda de Osuna); colección Carderera; Fundación María Cristina Masaveu Peterson; Fundación BBVA (Madrid, Palacio del Marqués de Salamanca): <a href="https://www.coleccionbbva.com/es/pintura/443-retrato-de-carlos-iii-cazador/" target="_blank"><em>Carlos III as a Hunter</em></a> y <a href="https://www.coleccionbbva.com/es/pintura/444-don-pantaleon-perez-de-nenin/" target="_blank"><em>Portrait of Don Pantaleón Pérez de Nenín</em></a>.</li><br>
368        
369        FRANCIA<br>
370        <li><a href="https://collections.louvre.fr/en/recherche?q=Francisco%20de%20Goya" target="_blank"><strong>Musée du Louvre</strong>:</a> pinturas, estampas y dibujos.</li>
371        <li><a href="https://www.museegoya.fr/fr/search?p=1&query=Goya" target="_blank"><strong>Musée Goya Castres</strong>:</a> pinturas, estampas, copias tras Goya y documentos. Su colección incluye <a href="https://www.museegoya.fr/fr/notice/894-5-4-l-assemblee-de-la-compagnie-royale-des-philippines-dite-la-junte-des-philippines-7dc13026-e99a-4d4d-a924-afa2204705e9" target="_blank"><i>La Junta de Filipinas</i></a> (L'Assemblée de la Compagnie Royale des Philippines) (1815) (894-5-4), el <a href="https://www.museegoya.fr/fr/notice/894-5-3-portrait-de-francisco-del-mazo-a3f16ab8-79d5-46ea-b3e8-5fb553468f2c" target="_blank"><i>Portrait of Francisco del Mazo</i></a> (1815-19) (894-5-3), y el <a href="https://www.museegoya.fr/fr/notice/894-5-2-autoportrait-aux-lunettes-36227304-cf57-402d-a357-5c5647a9aa7c" target="_blank"><i>Self-portrait with Glasses</i></a> (c.1800) (894-5-2).</li>
372        <li><a href="https://gallica.bnf.fr/selections/fr/html/francisco-de-goya" target="_blank"><strong>Bibliothèque nationale de France</strong></a> (BnF): historia de las colecciones de Goya y catálogo (estampas).</li>
373        <li>Palais des Beaux-arts de Lille: <a href="https://pba.lille.fr/Collections/Chefs-d-OEuvre/Peintures-XVI-sup-e-sup-XXI-sup-e-sup-siecles/Les-Vieilles-Le-Temps" target="_blank"><em>Les vieilles/Le temps</em></a> y <a href="https://pba.lille.fr/Collections/Chefs-d-OEuvre/Peintures-XVI-sup-e-sup-XXI-sup-e-sup-siecles/Les-Jeunes-La-Lettre" target="_blank"><em>Les jeunes/La lettre</em></a>. También un conjunto de los <em>Caprichos</em>.</li>
374        <li><a href="https://www.mbaa.besancon.fr/les-collections/peinture/" target="_blank"><strong>Musée des Beaux-Arts de Besançon</strong></a>: <em>Cannibals watching human remains</em> y <em>Cannibals preparing their victims</em>
374.</li>
375        <li>Musée des Beaux-Arts de Strasbourg: <a href="https://musees-strasbourg.skin-web.org/document/portrait-de-don-bernardo-iriarte/5ee338d7461cda28a3ab1e90?q=Goya&pos=14&pgn=0" target="_blank"><em>Portrait de Don Bernardo de Iriarte</em></a>, así como estampas (tras Velázquez, <em>Caprichos</em>, <em>Disparates</em>, estampas sueltas como <em>El agarrotado</em>).</li>
376        <li><a href="https://www.musee-agen.fr/recherche?q=Goya&tx_indexedsearch%5Bsubmit_button%5D=OK" target="_blank"><strong>Musée des Beaux-Arts d'Agen</strong></a>: algunas pinturas atribuidas a Goya, como un autorretrato y <em>Le Ballon</em>.</li>
377        <li><a href="https://mbh.bayonne.fr/" target="_blank"><strong>Musée Bonnat-Helleu</strong></a> (Bayona): obras como el dibujo <em>Dos mujeres abrazándose</em> y un retrato de Goya con gafas (atribuido).</li>
378        <li>COLECCIONES PRIVADAS: colección parisina desconocida (los retratos gemelos de Javier Goya y Gumersinda Goicoechea).</li><br>
379
380        EE. UU.<br>
381        <li><a href="https://collections.mfa.org/search/Objects/*/Goya/images?page=1" target="_blank"><strong>Boston Museum of Fine Arts</strong></a> (MoFA): pinturas (<a href="https://www.metmuseum.org/art/collection/search?q=Goya&sortBy=Relevance&searchField=ArtistCulture" target="_blank"><em>Portrait of a Young Man in Brown, possibly Javier Goya</em></a>, 48.558), <a href="https://collections.mfa.org/collections/315210/dpdprints-by-goya/objects" target="_blank">estampas</a> y <a href="https://collections.mfa.org/collections/315215/dpddrawings-by-goya;jsessionid=7527C5D8C33C9415AF5A65EC964E77C1/objects" target="_blank">dibujos</a>.</li>
382        <li><a href="https://www.metmuseum.org/art/collection/search?q=Goya&sortBy=Relevance&searchField=ArtistCulture" target="_blank"><strong>Metropolitan Museum</strong></a> (Nueva York): pinturas, estampas y dibujos.</li>
383        <li><a href="https://www.nortonsimon.org/art/in-focus/goya" target="_blank"><strong>Norton Simon Museum</strong></a> (Pasadena): historia de las colecciones de Goya y catálogo.</li>
384        <li><a href="https://www.frick.org/art/artists/goya" target="_blank"><strong>The Frick Collection</strong></a> (Nueva York): historia de las colecciones de Goya y catálogo.</li>
385        <li><a href="https://hispanicsociety.emuseum.com/search/Goya*" target="_blank"><strong>The Hispanic Society of America</strong></a> (Nueva York): colección de Goya, que incluye pinturas (<i>Portrait of the Duchess of Alba in black</i>, 1797, A102; <i>Portrait of Pedro Mocarte</i>, c.1805, A1890; <i>Portrait of Don Manuel Lapeña</i>, 1799, A99), estampas (<i>Los Caprichos</i>), </i>y dibujos (<i>Majas fighting, a Mano observing</i>, 1796-07, A3309; <i>Majas y majos conversing</i>, 1796-97, A3310; <i>Hay pulgas?</i>, 1796-97, A3316; <i>Buena gente somos los moralistas</i>, 1796-97, A3317; <i>Juan Fernández Navarrete</i>, 1798, LA2223; <i>Tiene prisa de escapar</i>, 1808-14, A3318; <i>Torture of a man</i>, c.1812-20, A3312; <i>Peasant carrying woman</i>, 1812-20, A3315; <i>Tuti li mundi</i>, c.1808-14, A3314; <i>Regocijo</i>, 1819-23, A3308; <i>Old man on a swing</i>, 1823-28, A3313; <i>Woman with 2 children</i>, 1823-28, A3311).</li>
386        <li><a href="https://www.nga.gov/artists/1353-francisco-goya" target="_blank"><strong>National Gallery of Art</strong></a> (Washington, D.C.): pinturas, estampas y dibujos.</li>
387        <li><a href="https://meadowsmuseumdallas.org/collections/pages/objects-1/portfolio/?records=9&query=mfs%20any%20%22Goya%22&sort=0" target="_blank"><strong>The Meadows Museum</strong></a> (Dallas): pinturas (incluidas copias y pastiches).</li>
388        <li><a href="https://www.artic.edu/artists/40615/francisco-jose-de-goya-y-lucientes" target="_blank"><strong>Art Institute of Chicago</strong></a>: pinturas, estampas y dibujos (incluidos pastiches).</li>
389        <li><a href="https://harvardartmuseums.org/collections/person/23169?person=23169" target="_blank"><strong>Harvard Art Museums</strong></a> (Cambridge, Mass.): pinturas, estampas y dibujos (incluidos pastiches).</li>
390        <li><strong>Museo de Arte de Ponce</strong> (Puerto Rico): <a href="https://museoarteponce.org/obras/retrato-de-martin-zapater-portrait-of-martin-zapater/" target="_blank"><em>Portrait of Martín Zapater</em></a>.</li><br>
391
392        REINO UNIDO<br>
393        <li><a href="https://www.nationalgallery.org.uk/artists/francisco-de-goya" target="_blank"><strong>National Gallery</strong></a> (Londres): <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-a-picnic" target="_blank"><em>A Picnic</em></a>, <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-a-scene-from-the-forcibly-bewitched" target="_blank"><em>A Scene from 'The Forcibly Bewitched'</em></a>, <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-don-andres-del-peral" target="_blank"><em>Don Andrés del Peral</em></a>, y <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-the-duke-of-wellington" target="_blank"><em>The Duke of Wellington</em></a>. También <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-dona-isabel-de-porcel" target="_blank"><em>Portrait of Isabel de Porcel</em></a>
393 (atribuido).</li>
394        <li><a href="https://thebowesmuseum.org.uk/discover-the-spanish-collection-held-by-the-bowes-museum/" target="_blank"><strong>The Bowes Museum</strong></a> (Durham): pinturas, incluido el retrato de Juan Meléndez Valdés y <i>Interior of a Prison</i>.</li>
395        <li><strong>English Heritage</strong>: Wellington Collection, Apsley House (Londres) (<a href="https://artuk.org/discover/artworks/equestrian-portrait-of-the-1st-duke-of-wellington-17691852-144183" target="_blank">Equestrian Portrait of the 1st Duke of Wellington</a>).</li>
396        <li><a href="https://artuk.org/discover/artworks/el-medico-the-doctor-210121" target="_blank"><strong>National Galleries of Scotland</strong></a> (Edimburgo): <em>The Doctor</em>.</li>
397        <li><strong>The Courtauld Gallery</strong> (Londres): el dibujo <a href="https://courtauld.ac.uk/highlights/cantar-y-bailar-singing-and-dancing/" target="_blank"><em>Singing and Dancing</em></a> y la pintura <a href="https://courtauld.ac.uk/highlights/portrait-of-francisco-de-saavedra/" target="_blank"><em>Portrait of Francisco de Saavedra</em></a>.</li>
398        <li><strong>Pollok House</strong> (Glasgow): serie de 6 pinturas pequeñas de escenas con niños, entre ellas <a href="https://artuk.org/discover/artworks/boys-playing-at-see-saw-84159" target="_blank"><em>Boys Playing at See-Saw</em></a> y <a href="https://artuk.org/discover/artworks/boys-playing-at-soldiers-84160" target="_blank"><em>Boys Playing at Soldiers</em></a>.</li>
399        <li><a href="https://artuk.org/discover/artworks/disparate-de-miedo-261946" target="_blank"><strong>The Higgins</strong></a> (Bedford): «Disparate de miedo» (<em>Disparates</em>).</li><br> 
400
401        IRLANDA<br>
402        <li><strong>National Gallery of Ireland</strong> (Dublín): <a href="https://www.nationalgallery.ie/art-and-artists/highlights-collection/dona-antonia-zarate-francisco-jose-de-goya-y-lucientes-1746" target="_blank"><em>Portrait of Doña Antonia de Zárate</em></a>.</li><br>
403
404        SUIZA<br>
405        <li><strong>Emil Bührle Collection</strong> (Zúrich): <a href="https://buehrle.ch/en/artworks/procession-in-valencia/" target="_blank"><em>Procession in Valencia</em></a>.</li>
406        <li>COLECCIONES PRIVADAS: Rothschild (incluida la versión auténtica de <em>Majas on the Balcony</em>).</li><br>
407
408        ALEMANIA<br>
409        <li><a href="https://recherche.smb.museum/?language=de&limit=15&sort=relevance&controls=none&conditions=AND%2BinvolvedParties%2B%22Francisco+Jos%C3%A9+de+Goya+y+Lucientes%22&collectionKey=NG*" target="_blank"><strong>Staatliche Museen zu Berlin</strong></a>: catálogo de obras, incluidas <em>La Cucaña</em>.</li>
410        <li><a href="https://www.sammlung.pinakothek.de/de/search?phrase=Goya#filters={}" target="_blank"><strong>Alte Pinakothek</strong></a> (Múnich): pinturas, estampas y dibujos (incluidos pastiches).</li><br>
411
412        ITALIA<br>
413        <li><strong>Uffizi</strong> (Florencia): <a href="https://www.virtualuffizi.com/es/retrato-de-la-condesa-de-chinc%C3%B3n.html" target="_blank"><em>Equestrian portrait of María Teresa de Vallabriga</em></a>.</li>
414        <li><strong>Fondazione Magnani Rocca</strong> (Parma): <a href="https://www.magnanirocca.it/goya-la-famiglia-dell-infante-don-luis/" target="_blank"><em>La familia del Infante Don Luis</em></a>.</li><br>
415
416      </p>
417      `,
418
419      PEOPLE_TITLE: "Equipo",
420      PEOPLE_BODY: `
421      <p><strong>Directora del proyecto:</strong> Paula Fayos Pérez, investigadora postdoctoral. Doctora en Historia del arte (Universidad de Cambridge, 2019). Miembro asociado de ARCHE (Université de Strasbourg) y de SU+MA (Universidad Complutense de Madrid).</p>
422
423      <p><strong>Comité científico:</strong> El contenido de la Goya Network se revisa y debate periódicamente con los miembros de un comité científico integrado por consultores externos de reconocido prestigio académico, que aportan orientación y asesoramiento sobre aspectos específicos del proyecto.</p>
424      <ul style="margin-top: 0.5rem; line-height: 1.6;">
425        <li><strong>Jean Michel Massing</strong>: profesor emérito de Historia del arte en la Universidad de Cambridge, King's College. Especialista en historia del grabado, iconografía y arte africano.</li> <br>
426        <li><strong>Aurélie Pétiot</strong>: profesora (lecturer) en la Université Paris Nanterre. Especialista en arte del siglo XIX, artes decorativas, estudios de género y prerrafaelismo.</li> <br>
427        <li><strong>Stephanie Loeb Stepanek</strong>: conservadora emérita de Estampas y Dibujos en el Museum of Fine Arts, Boston. Especialista en la obra gráfica de Goya y en la historia del coleccionismo de Goya en Estados Unidos.</li> <br>
428        <li><strong>Anita L. Verő</strong>: doctora en Inteligencia Artificial (Universidad de Cambridge, 2022), informática y fundadora de <a href="https://www.consonant-ai.com" target="_blank">ConsonantAI</a>. Apoyo en el desarrollo técnico del sitio web.</li>
429      </ul>
430
431      <p class="section-heading">Colaboradores</p>
432      <ul>
433        <li><strong>Manuela B. Mena-Marqués</strong>: doctora en Historia del arte (Universidad Complutense de Madrid, 1976). Conservadora del Gabinete de Dibujos y Estampas (1979-1981), subdirectora (1981-1996) y jefa del Área de Conservación de Pintura del siglo XVIII y Goya del Museo del Prado (1996-2019). Contribución: comentarios sobre el sitio web (junio de 2025).</li> <br>
434        <li><strong>Joëlle Arches</strong>, conservadora jefe y directora del <a href="https://www.museegoya.fr/" target="_blank">Musée Goya</a> y del Museo Jean Jaurès, y <strong>Cécile Berthoumieu</strong>, conservadora principal de patrimonio (Attachée principale de conservation du patrimoine). Contribución: comentarios sobre el sitio web; sugerencia de incorporar a Marcel Briguiboul a la red (septiembre de 2025).</li> <br>
435        <li><strong>Stephanie Loeb Stepanek</strong>: conservadora emérita de Estampas y Dibujos, Boston Museum of Fine Arts. Contribución: información sobre una edición temprana de <i>Disasters of War</i>, impresa por Laureano Potenciano (octubre de 2025).</li>
436      </ul>
437      `,
438
439      ABOUT_TITLE: "Acerca de la Goya Network / Red Goya",
440      ABOUT_BODY: `
441
442      <p><strong>Introducción:</strong> A lo largo de los siglos XVIII y XIX, las redes de artistas, escritores, coleccionistas e intelectuales fueron cruciales tanto para la producción artística como para la difusión y comprensión de figuras históricas. Cartografiar estas conexiones permite entender mejor las dinámicas de sociabilidad y la circulación de ideas. En los últimos años, el análisis de redes se ha convert
442ido en una herramienta importante en Humanidades Digitales para iluminar contextos artísticos históricos (véase la sección <a href="#" onclick="showSection('relevant-links'); return false;">Enlaces relevantes</a> para ejemplos de proyectos similares).</p> 
443
444      <p><em>The Goya Network</em> se apoya en estos métodos y ofrece una base de datos interactiva que cartografía las relaciones sociales y profesionales en torno al artista español Francisco de Goya, con especial atención a sus conexiones con artistas, críticos y coleccionistas en la España y la Francia del siglo XIX. A diferencia de los estudios tradicionales centrados en su obra, este proyecto pone el énfasis en el contexto relacional que configuró el legado de Goya, y ofrece una herramienta novedosa para el estudio de las redes sociales de artistas históricos. Como base de datos «viva», facilita la colaboración interdisciplinar y funciona tanto como herramienta de investigación como recurso público, proporcionando una visión del alcance de la influencia de Goya y de la recepción de su obra.</p>
445
446      <p><strong>Origen:</strong> Se trata de un proyecto digital basado en investigación, desarrollado por Paula Fayos-Pérez en el marco de su trabajo postdoctoral sobre las redes sociales y profesionales de Goya. Constituye una continuación de la investigación realizada durante su doctorado (Universidad de Cambridge, 2018) <a href="https://www.repository.cam.ac.uk/items/8d9751ce-aca1-4d35-9436-0820ecbc98d4" target="_blank">(repositorio)</a>, que posteriormente se transformó en un libro: <a href="https://www.ceeh.es/en/publicacion/goyas-caprichos-in-nineteenth-century-france-politics-of-the-grotesque/" target="_blank"><em>Goya’s Caprichos in Nineteenth-Century France. Politics of the Grotesque</em></a> (CEEH, 2024).</p>
447
448      <p><strong>Financiación:</strong> Este proyecto fue iniciado con el apoyo de una Beca Leonardo 2024 (<a href="https://www.redleonardo.es/beneficiario/paula-fayos-perez/" target="_blank">Beca Leonardo</a>) para Investigación Científica y Creación Cultural, financiada por la <a href="https://www.fbbva.es/" target="_blank">Fundación BBVA.</a> La Fundación BBVA no asume responsabilidad alguna por las opiniones, afirmaciones y contenidos de este proyecto, que son enteramente responsabilidad de sus autores.</p>
449
450      <p>Actualmente el proyecto cuenta con el apoyo de una beca de investigación de la <a href="https://www.gerda-henkel-stiftung.de/" target="_blank">Gerda Henkel Stiftung (Fundación Gerda Henkel).</a></p>
451
452      <p><strong>Guía de uso:</strong> Cada nodo (individuo) y cada arista/conexión (vínculo entre dos individuos) contiene datos. En los nodos, los usuarios pueden consultar información biográfica, así como producción artística/literaria, coleccionismo y enlaces a la obra de Goya. En las conexiones se muestra el tipo de relación, correspondencia, colaboraciones y otros datos. Las conexiones en color gris/plateado son conexiones directas (amistades, colegas, familiares, conocidos, etc.), mientras que las conexiones en color dorado son conexiones secundarias (por ejemplo, artistas y escritores que copiaron o escribieron sobre Goya pero no llegaron a conocerlo en persona). El tamaño de los nodos es proporcional al número de conexiones (aristas) que tienen.</p>
453
454      <p><strong>Naturaleza del proyecto:</strong> La Goya Network está concebida como una base de datos colaborativa y de contribución abierta, con el objetivo de convertirse en una herramienta útil para especialistas, estudiantes y el público general interesado en Goya. Se acogen contribuciones de historiadores del arte, historiadores, hispanistas, docentes y conservadores especializados en Goya, en la España del siglo XIX, en la Francia del siglo XIX o en cualquiera de los miembros de la red (por ejemplo, Federico Madrazo, Prosper Mérimée, Eugène Delacroix, etc.). Para enviar una contribución, véase la sección Contributions en el sitio web (más abajo).</p>
455
456      <p><strong>Metodología:</strong> <u>Investigación en Historia del arte</u>: basada en análisis sistemático de fuentes bibliográficas y de archivo (como correspondencia) de la Francia y España del siglo XIX. Los nodos y conexiones de la red fueron construidos mediante la cuidadosa comparación cruzada de publicaciones académicas (monografías, artículos revisados por pares, catálogos de exposiciones), sitios web institucionales y base
456s de datos de colecciones en línea de museos y fundaciones culturales, y recursos digitales verificados.<br>
457      <u>Desarrollo web</u>: realizado con HTML y JavaScript, con datos estructurados en JSON, y control de versiones y despliegue mediante GitHub; desarrollado con el apoyo de ChatGPT (OpenAI), DeepSeek (High-Flyer) y Claude (Anthropic). Configuración inicial del sitio web en colaboración con Anita Verő <a href="https://www.consonant-ai.com" target="_blank">(ConsonantAI)</a>.</p>
458
459      <p><strong>Derechos de autor y licencia:</strong> La Goya Network está protegida por acta notarial (Madrid, 2025), que documenta el proceso de investigación y desarrollo entre 2018 y 2025.  
460      Su código y estructura están licenciados bajo <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" style="color:#66ccff;">CC BY-NC 4.0</a>, lo que permite el uso académico no comercial con la debida atribución a la autora.</p>
461
462      <p><strong>Datos y cookies:</strong> Este sitio web utiliza cookies exclusivamente para Google Analytics, que registra de forma anónima el comportamiento de los visitantes. Utilizamos esta información únicamente para comprender cómo se usa el sitio y mejorar su contenido y navegación. Puedes desactivar las cookies de Google Analytics en todos los sitios web instalando el <a href="https://tools.google.com/dlpage/gaoptout" target="_blank" style="color:#66ccff;">complemento de inhabilitación para navegadores de Google Analytics</a>. Para más información sobre el uso de cookies por parte de Google, consulta la página <a href="https://developers.google.com/analytics/devguides/collection/analyticsjs/cookie-usage" target="_blank" style="color:#66ccff;">Cookies & Google Analytics</a>.</p>
463      `,
464
465    CONTACT_TITLE: "Contacto",
466    CONTACT_BODY: `
467    <p>Para colaboraciones, sugerencias o contribuciones:</p>
468
469    <p style="margin-top:0.5rem;">
470      <a href="mailto:[email protected]" style="font-weight:bold;">
471        [email protected]
472      </a>
473    </p>
474
475    <p style="margin-top:1.5rem;">
476      Véase también el formulario de contribuciones al final de la página.
477    </p>
478    `,
479
480    NEWS_TITLE: "Noticias",
481    NEWS_BODY: `
482    <ul style="padding-left:1.2rem; line-height:1.7;">
483      
484        <li>
485          <strong>Julio 2026.</strong> La Goya Network ha sido presentada en el <a href="https://www.eventbrite.co.uk/e/goya-network-tickets-1991696763635?aff=oddtdtcreator" target="_blank">Instituto Cervantes de Londres</a>, en un evento presidido por Manuela B. Mena Marqués.
486        </li>
487      <li>
488        <strong>Mayo de 2026.</strong> El proyecto de investigación que desarrolla la Goya Network ha recibido financiación de la <a href="https://www.gerda-henkel-stiftung.de/" target="_blank">Fundación Gerda Henkel (Alemania)</a>.
489      </li>
490      <li>
491        <strong>Marzo de 2026.</strong> La Goya Network sigue ampliándose con nuevos miembros y colaboraciones. Nueva colaboración: <a href="https://crimic-sorbonne.fr/" target="_blank">CRIMIC (Sorbonne Université)</a>.
492      </li>
493      <li>
494        <strong>Septiembre de 2025.</strong> Se estableció una nueva colaboración con <a href="https://artes-uk.org/" target="_blank">ARTES (Iberian & Latin American Visual Culture Group, Reino Unido)</a>.
495      </li>
496      <li>
497        <strong>Septiembre de 2024.</strong> La Red Goya recibe financiación de la <a href="https://www.redleonardo.es/noticias/paula-fayos-crea-goya-network-beca-leonardo/" target="_blank">Fundación BBVA (Beca Leonardo)</a>.
498      </li>
499    </ul>
500    `,
501
502    MENTIONS_TITLE: "Menciones",
503    MENTIONS_BODY: `
504
505    <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Artículos académicos</h3>
506      <ul style="padding-left:1.2rem; line-height:1.7;">
507        <li><strong><i>Leonardo</i> (MIT Press): próximamente</strong> – Paula Fayos Pérez & Anita Verő, “The Goya Network: Mapping Goya's Impact in 19th-Century France and Spain” (en inglés).</li>
508      </ul>
509
510      <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Conferencias y presentaciones</h3>
511      <ul style="padding-left:1.2rem; line-height:1.7;">
512        <li><strong>Instituto Cervantes London (2026)</strong> – 'Goya Network: Francisco de Goya's influence on 19th-century French art and literature' (en inglés) <a href="https://www.eventbrite.co.uk/e/goya-network-tickets-1991696763635?aff=
512oddtdtcreator" target="_blank">(ver evento)</a>.</li>
513        <li><strong>Prado Joven 2025 (Museo del Prado)</strong> – 'El impacto de Goya en el siglo XIX: de Gautier a Madrazo' (en español) <a href="https://www.museodelprado.es/recurso/programa-joven-2025/570e6e48-01fc-48b4-b8f6-270e3b3141f3" target="_blank">(programa)</a>. <a href="https://www.youtube.com/watch?v=jSGfYz08mP83" target="_blank">Ver vídeo</a>.</li>
514        <li><strong>Congreso Humanidades Digitales Hispánicas 2025 (Universidad de Valencia)</strong> – 'La Red Goya: Humanidades Digitales para mapear la recepción de Goya en el siglo XIX' (en español) <a href="https://hdh2025.uv.es/static/files/Programa_Congreso_1_8.pdf" target="_blank">(ver programa)</a>.</li>
515        <li><strong>Asociación Humanidades Digitales Hispánicas (2025)</strong> – 'Un café con Paula Fayos Pérez. La Red Goya: Humanidades Digitales para mapear la recepción de Goya en el siglo XIX' (en español) <a href="https://humanidadesdigitaleshispanicas.es/cafe-con-paula-fayos-perez-16-de-diciembre-de-2025/" target="_blank">(ver evento)</a>.</li>
516      </ul>
517
518      <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Presencia en línea</h3>
519      <ul style="padding-left:1.2rem; line-height:1.7;">
520        <li><strong>ARTES UK (Iberian & Latin American Visual Culture Group)</strong> – <a href="https://artes-uk.org/links-2/" target="_blank">la Red Goya incluida entre los enlaces</a> (en inglés).</li>
521        <li><strong>XIX (Société des études romantiques & dix-neuviémistes)</strong> – <a href="https://serd.hypotheses.org/19526" target="_blank">'The Goya Network (Réseau Goya)' </a>.</li>
522        <li><strong>Daumier.de</strong> – <a href="https://www.daumier.de/2025/07/30/das-goya-network/" target="_blank">Das Goya Network</a> (en alemán).</li>
523        <li><strong>Red Leonardo</strong> – <a href="https://www.redleonardo.es/noticias/paula-fayos-crea-goya-network-beca-leonardo/?fbclid=IwY2xjawMuhCNleHRuA2FlbQIxMQBicmlkETFmVHVybzVkRTNnNEZ3bGExAR7q3T7lseZC1CU_pp-vGJWXCjVU8C_gUFvqiVPRd7efUEbjLWnIKRb_WU9Ieg_aem_JIlJ6CO77pnEaRFtx1uKmQ" target="_blank">Paula Fayos crea la Goya Network [...]</a> (en español).</li>
524      </ul>
525
526      <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Entrevistas</h3>
527      <ul style="padding-left:1.2rem; line-height:1.7;">
528        <li><strong><i>Mas De Arte</i></strong> – <a href="https://masdearte.com/especiales/paula-fayos-perez/" target="_blank">Paula Fayos Pérez en Especiales/Fichados</a> (en español).</li>
529        <li><strong><i>El País</i></strong> – próximamente (en español).</li>
530      </ul>
531    `,
532
533    RELEVANT_LINKS_TITLE: "Enlaces relevantes",
534    RELEVANT_LINKS_BODY: `
535
536    <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Goya</h3>
537    <ul style="padding-left:1.2rem; line-height:1.6;">
538      <li><a href="https://www.museodelprado.es/coleccion/artista/francisco-de-goya-y-lucientes/39568a17-81b5-4d6f-84fa-12db60780812" target="_blank"><strong>Goya en el Prado</strong></a> – Un recurso digital del artista para aprender sobre sus pinturas, estampas y dibujos en el museo, así como documentos, referencias impresas y una biblioteca digital (en español).</li>
539      <li><a href="https://www.realacademiabellasartessanfernando.com/goya/la-academia-y-goya/" target="_blank"><strong>Real Academia de Bellas Artes de San Fernando: La Academia y Goya</strong></a> – La historia de Goya con la Real Academia de Bellas Artes de Madrid, así como una panorámica de las colecciones de Goya, particularmente sus series de estampas (en español).</li>
540    </ul>
541
542    <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Siglo XIX / Francia</h3>
543    <ul style="padding-left:1.2rem; line-height:1.6;">
544      <li><a href="https://www.musee-delacroix.fr/fr/informations-complementaires/liens/societe-des-amis-du-musee" target="_blank"><strong>Société des Amis du musée Delacroix</strong></a> – Dedicada al estudio y promoción de la obra y legado de Eugène Delacroix (francés/inglés).</li>
545      <li><a href="https://prosper-merimee.com/" target="_blank"><strong>Société Mérimée</strong></a> – Una organización y revista académica (<i>Cahiers Mérimée</i>) dedicada a la figura de Prosper Mérimée (en francés).</li>
546      <li><a href="https://www.societedesetudesmarcelinedesbordesvalmore.fr/" target="_blank"><strong>Société des études Marceline Desbordes-Valmore</strong></a> – Promueve el estudio de la poeta y novelista francesa del siglo XIX (en francés).</li>
547      <li><a href="https://www.theophilegautier.fr/" target="_blank"><strong>
547Société Théophile Gautier</strong></a> – Una asociación dedicada a la vida, obra y legado literario de Théophile Gautier (en francés).</li>
548      <li><a href="https://www.daumier.de/" target="_blank"><strong>Daumier.de</strong></a> – Dedicado al estudio y promoción de la obra y legado de Honoré Daumier (en alemán).</li>
549      <li><a href="https://serd.hypotheses.org/" target="_blank"><strong>XIX Société des études romantiques & dix-neuvièmistes</strong></a> – Una sociedad dedicada al estudio del romanticismo y el siglo XIX (en francés).</li>
550      <li><a href="https://19cwwfw.org/" target="_blank"><strong>Nineteenth-Century Women Writers in the Francophone World</strong></a> (de Bénédicte Monicat y Morgane Haesen) – Una base de datos de mujeres escritoras del siglo XIX en el mundo francófono (en inglés).</li>
551      <li><a href="https://19thc-artworldwide.org/" target="_blank"><strong>Nineteenth-Century Art Worldwide</strong></a> – Una revista dedicada al estudio del arte y la cultura visual del siglo XIX (en inglés).</li>
552    </ul>
553
554    <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Bases de Datos y Recursos Generales</h3>
555    <ul style="padding-left:1.2rem; line-height:1.6;">
556      <li><a href="https://historia-hispanica.rah.es/" target="_blank"><strong>Real Academia de la Historia (RAH): Historia Hispánica</strong></a> – Un portal digital creado por la RAH que geolocaliza en un mapa interactivo la historia global de España y de los territorios que formaron parte de la administración española (español/inglés).</li>
557      <li><a href="https://www.getty.edu/" target="_blank"><strong>J. Paul Getty Trust</strong></a> – Incluye el Museo Getty, Getty Research Institute y extensas bases de datos de historia del arte (en inglés).</li>
558      <li><a href="https://www.bnf.fr/" target="_blank"><strong>Bibliothèque nationale de France (BnF)</strong></a> – Biblioteca Nacional de Francia con colecciones digitalizadas incluyendo cartas, manuscritos e impresiones (francés/inglés/español).</li>
559      <li><a href="https://www.bne.es/" target="_blank"><strong>Biblioteca Nacional de España (BNE)</strong></a> – Biblioteca Nacional de España con extensos recursos digitales (en español).</li>
560    </ul>
561
562    <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Otros Proyectos de Humanidades Digitales</h3>
563    <ul style="padding-left:1.2rem; line-height:1.6;">
564      <li><a href="https://arkivet.thorvaldsensmuseum.dk/en" target="_blank"><strong>Thorvaldsen Letter Archive</strong></a> – Cartografía de la correspondencia y redes sociales del escultor neoclásico Bertel Thorvaldsen (inglés/danés).</li>
565      <li><a href="http://republicofletters.stanford.edu/casestudies/index.html" target="_blank"><strong>Mapping the Republic of Letters</strong></a> (Universidad de Stanford) – Una visualización interactiva de redes intelectuales en la Europa moderna temprana (en inglés).</li>
566      <li><a href="http://emlo.bodleian.ox.ac.uk/home" target="_blank"><strong>Early Modern Letters Online (EMLO)</strong></a> (Universidad de Oxford) – Un catálogo y análisis de la correspondencia del período moderno temprano (en inglés).</li>
567      <li><a href="https://mappingrome.com/" target="_blank"><strong>Mapping Rome</strong></a> – Visualización de redes sociales, económicas y culturales en la antigüedad romana (en inglés).</li>
568      <li><a href="https://vangoghworldwide.org/" target="_blank"><strong>Van Gogh Worldwide</strong></a> – Una plataforma colaborativa que conecta las obras de Van Gogh, sus cartas e impacto cultural en instituciones de todo el mund
568o (en inglés).</li>
569      <li><a href="https://womensbios.lib.virginia.edu/index.html" target="_blank"><strong>Collective Biographies of Women</strong></a> (Alison Booth, Universidad de Virginia) – Agregación de datos biográficos sobre mujeres en múltiples disciplinas y períodos (en inglés).</li>
570    </ul>
571    
572    `,
573
574    CONTACT_TITLE: "Contacto",
575    },
576  };
577
578  function t(k){ return (I18N[CURRENT_LANG] && I18N[CURRENT_LANG][k]) || k; }
579
580  function applyUIStrings(){
581    // Botones del menú superior
582    const map = [
583      ['.nav-buttons button[onclick*="network"]','BTN_NETWORK'],
584      ['.menu-content button[onclick*="about"]','BTN_ABOUT'],
585      ['.menu-content button[onclick*="people"]','BTN_PEOPLE'],
586      ['.menu-content button[onclick*="partners"]','BTN_PARTNERS'],
587      ['.menu-content button[onclick*="news"]','BTN_NEWS'],
588      ['.menu-content button[onclick*="mentions"]','BTN_MENTIONS'],
589      ['.menu-content button[onclick*="artworks"]','BTN_ARTWORKS'],
590      ['.menu-content button[onclick*="bibliography"]','BTN_BIB'],
591      ['.menu-content button[onclick*="relevant-links"]','BTN_RELEVANT_LINKS'],
592      ['.menu-content button[onclick*="cite"]','BTN_CITE'],
593      ['.menu-content button[onclick*="contact"]','BTN_CONTACT'],
594      ['#searchInput::placeholder','SEARCH_PH'],
595      ['.search-button','BTN_SEARCH'],
596    ];
597    map.forEach(([sel,key])=>{
598      if (sel.endsWith('::placeholder')) {
599        const id = sel.replace('::placeholder','');
600        const el = document.querySelector(id);
601        if (el) el.setAttribute('placeholder', t(key));
602      } else {
603        const el = document.querySelector(sel);
604        if (el) el.textContent = t(key);
605      }
606    });
607
608    // Filtros
609    const profAll = document.querySelector('#professionFilter option[value=""]');
610    if (profAll) profAll.textContent = t('FILTER_PROF_ALL');
611    const natAll = document.querySelector('#nationalityFilter option[value=""]');
612    if (natAll) natAll.textContent = t('FILTER_NAT_ALL');
613
614    // Traduce el texto visible de cada opción (sin tocar el value)
615    document.querySelectorAll('#professionFilter option').forEach(opt => {
616      if (!opt.value) return;
617      opt.textContent = t(opt.value).toLowerCase();
618    });
619
620    document.querySelectorAll('#nationalityFilter option').forEach(opt => {
621      if (!opt.value) return;
622      opt.textContent = t(opt.value);
623    });
624
625    // Título "Members …"
626    const membersHeading = document.querySelector('#membersSection .section-heading');
627    if (membersHeading) membersHeading.textContent = t('MEMBERS_TITLE');
628
629    // Mensaje de carga
630    const loading = document.getElementById('loadingMessage');
631    if (loading) loading.firstChild.textContent = t('LOADING')+' ';
632
633    // Guarda etiqueta para "Last update"
634    window.LAST_UPDATE_LABEL = t('LAST_UPDATE');
635
636    // Textos declarados con data-i18n
637    const HTML_I18N_KEYS = new Set([
638      'ARTWORKS_BODY',
639      'PEOPLE_BODY',
640      'ABOUT_BODY',
641      'CONTRIBUTIONS_TEXT',
642      'NEWS_BODY',
643      'MENTIONS_BODY',
644      'RELEVANT_LINKS_BODY',
645      'CONTACT_BODY',
646      // añade aquí futuras secciones largas con HTML, ej:
647      // 'ABOUT_BODY', 'PARTNERS_BODY'
648    ]);
649
650    document.querySelectorAll('[data-i18n]').forEach(el => {
651      const key = el.getAttribute('data-i18n');
652      if (!key) return;
653
654      const val = t(key);
655
656      // Si el string contiene HTML, hay que usar innerHTML
657      if (HTML_I18N_KEYS.has(key)) {
658        el.innerHTML = val;
659      } else {
660        el.textContent = val;
661      }
662    });
663  }
664
665  window.setLanguage = function(lang='en') {
666    CURRENT_LANG = (lang === 'es') ? 'es' : 'en';
667    localStorage.setItem('lang', CURRENT_LANG);
668    applyUIStrings();
669    if (typeof window.refreshNodeInfoLabels === 'function') window.refreshNodeInfoLabels();
670    location.reload();
671  };
672
673  function sortSelectOptionsAlphabetically(selectId) {
674    const select = document.getElementById(selectId);
675    if (!select) return;
676
677    const options = Array.from(select.options);
678
679    // Mantener la primera opción tipo "All professions"
680    const first = options.shift();
681
682    options.sort((a, b) =>
683      a.textContent.localeCompare(b.textContent, 'es', { sensitivity: 'base' })
684    );
685
686    select.innerHTML = '';
687    select.appendChild(first);
688    options.forEach(opt => select.appendChild(opt));
689  }
690
691  // Helper para traducir labels de panel
692  function labelI18N(label){ return t(label); }
693  // ====== /I18N ======
694
695  // --- Forzar re-render del panel lateral con el idioma actual ---
696  window.__lastSelection = null;
696 // guardaremos el último nodo/arista mostrados
697
698  window.refreshNodeInfoLabels = function () {
699    const info = document.getElementById('nodeInfo');
700    if (!info) return;
701
702    // Si teníamos un nodo seleccionado, re-emite el click para regenerar el HTML con t(...)
703    if (window.__lastSelection && window.__lastSelection.type === 'node' && window.__lastSelection.id) {
704      window.VIS_NETWORK.emit('click', { nodes: [window.__lastSelection.id], edges: [] });
705      return;
706    }
707
708    // Si teníamos una arista seleccionada, re-emite el click para regenerar
709    if (window.__lastSelection && window.__lastSelection.type === 'edge' && window.__lastSelection.id) {
710      window.VIS_NETWORK.emit('click', { nodes: [], edges: [window.__lastSelection.id] });
711      return;
712    }
713
714    // Si no había selección, vuelve al panel por defecto y re-aplica los textos
715    if (typeof window.showDefaultNodeInfo === 'function') window.showDefaultNodeInfo();
716    applyUIStrings(); // actualiza "Members (A–Z…)", placeholder, etc.
717  };
718
719let nodes, edges; // 👈 Hacemos estas variables globales
720
721function autoLinkNames(text, nodesMap) {
722  if (!text || typeof text !== "string") return text;
723
724  // Sustituye saltos de línea invisibles por espacio
725  text = text.replace(/\r?\n|\r/g, " ");
726
727  // Divide el texto en partes: enlaces HTML intactos y el resto
728  const splitParts = text.split(/(<a [^>]+>.*?<\/a>)/g);
729
730  const processed = splitParts.map(part => {
731    if (part.startsWith('<a ')) return part; // ya es un enlace, no tocar
732
733    Object.keys(nodesMap).forEach(name => {
734      const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
735      const regex = new RegExp(`(?<![\\w>])(${escapedName})(?![\\w<])`, "g");
736      part = part.replace(
737        regex,
738        `<a href="#" style="color:#66ccff" onclick="focusNode('${nodesMap[name].id}')">$1</a>`
739      );
740    });
741
742    return part;
743  });
744
745  return processed.join('');
746}
747
748function processMarkdownLinks(text) {
749  if (!text || typeof text !== "string") return text;
750  
751  // Preserve italics
752  text = text.replace(/<i>/g, '%%%ITALIC_OPEN%%%').replace(/<\/i>/g, '%%%ITALIC_CLOSE%%%');
753  
754  // Format 1: [text](url)
755  text = text.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, 
756    '<a href="$2" target="_blank" style="color:#66ccff;">$1</a>');
757  
758  // Format 2: (text)[url]
759  text = text.replace(/\(([^)]+)\)\[(https?:\/\/[^\]]+)\]/g, 
760    '(<a href="$2" target="_blank" style="color:#66ccff;">$1</a>)');
761    
762  // Format 3: text [url] (fallback)
763  text = text.replace(/(\b[^\s\[\]]+\b)\s*\[(https?:\/\/[^\]]+)\]/g, 
764    '<a href="$2" target="_blank" style="color:#66ccff;">$1</a>');
765  
766  // Restore italics
767  text = text.replace(/%%%ITALIC_OPEN%%%/g, '<i>').replace(/%%%ITALIC_CLOSE%%%/g, '</i>');
768  
769  return text;
770}
771
772function isImageUrl(u) {
773  if (!u) return false;
774  const s = String(u).trim();
775  return /\.(png|jpe?g|gif|webp|svg)(\?.*)?$/i.test(s);
776}
777
778// Add these two functions at the top of network.js
779window.search = function(nodeId) {
780  if (!nodeId || !window.VIS_NETWORK || !nodes.get(nodeId)) return;
781
782  if (typeof window.__GN_clearHighlights === 'function') {
783    window.__GN_clearHighlights();
784  }
785  
786  window.VIS_NETWORK.stopSimulation?.();
787
788  const pos = window.VIS_NETWORK.getPosition(nodeId);
789  if (!pos) return;
790
791  window.VIS_NETWORK.unselectAll();
792
793  // Use moveTo with moderate zoom (show neighboring context) - move immediately
794  window.VIS_NETWORK.moveTo({
795    position: pos,
796    scale: window.VIS_NETWORK.getScale() * 1.2
797  });
798
799  setTimeout(() => {
800    window.VIS_NETWORK.selectNodes([nodeId]);
801    window.VIS_NETWORK.emit('click', {
802      nodes: [nodeId],
803      edges: [],
804      pointer: { DOM: { x: 0, y: 0 }, canvas: { x: 0, y: 0 } }
805    });
806  }, 650);
807};
808
809// ===============================
810// Dropdown Menu Handlers
811// ===============================
812window.toggleMenu = function(event, menuId) {
813  event.stopPropagation();
814  const menu = document.getElementById(menuId);
815  if (menu) {
816    // Close all other menus first
817    document.querySelectorAll('.menu-content.show').forEach(m => {
818      if (m.id !== menuId) m.classList.remove('show');
819    });
820    menu.classList.toggle('show');
821  }
822};
823
824window.closeAllMenus = function() {
825  document.querySelectorAll('.menu-content.show').forEach(m => {
826    m.classList.remove('show');
827  });
828};
829
830// Close menus when clicking outside
831document.addEventListener('click', function(event) {
832  if (!event.target.closest('.menu-dropdown')) {
833    window.closeAllMenus();
834  }
835});
836
837// ===============================
838// Dropdown Menu Handler
839// ===============================
840window.toggleMenuDropdown = function() {
841  const menuContent = document.getElementById('menuContent');
842  if (menuContent) {
843    menuContent.classList.toggle('show');
844  }
845};
846
847window.closeMenuDropdown = function() {
848  const menuContent = document.getElementById('menuContent');
849  if (menuContent) {
850    menuContent.classList.remove('show');
851  }
852};
853
854// Close menu when clicking outside
855document.addEventListener('click', function(event) {
856  const menuDropdown = document.querySelector('.menu-dropdown');
857  if (menuDropdown && !menuDropdown.contains(event.target)) {
858    const menuContent = document.getElementById('menuContent');
859    if (menuContent) {
860      menuContent.classList.remove('show');
861    }
862  }
863});
864
865// ===============================
866// Filters: highlight + right panel
867// ===============================
868let __filterHighlightedIds = [];
869let __filterDefaultNodeInfoHTML = null;
870
871// Save current nodeInfo as "default" once
872function __ensureDefaultNodeInfoSnapshot() {
873  if (__filterDefaultNodeInfoHTML) return;
874  const nodeInfoEl = document.getElementById("nodeInfo");
875  if (nodeInfoEl) __filterDefaultNodeInfoHTML = nodeInfoEl.innerHTML;
876}
877
878function __escapeHtml(s) {
879  return String(s ?? "")
880    .replaceAll("&", "&amp;")
881    .replaceAll("<", "&lt;")
882    .replaceAll(">", "&gt;")
883    .replaceAll('"', "&quot;")
884    .replaceAll("'", "&#039;");
885}
886
887function __clearFilterHighlights() {
888  if (!window.nodes || !__filterHighlightedIds.length) return;
889
890  // Restore ONLY what this filter changed (border + borderWidth)
891  const updates = __filterHighlightedIds.map(id => ({
892    id,
893    color: { border: "#2B7CE9" },
894    borderWidth: 2
895  }));
896
897  nodes.update(updates);
898  __filterHighlightedIds = [];
899}
900
901window.renderFilterPanel = function ({ professionFilter, nationalityFilter, matchingNodeIds }) {
902  const nodeInfo = document.getElementById('nodeInfo');
903  if (!nodeInfo) return;
904
905  const total = nodes.get().length;
906
907  // Si no hay filtros activos, vuelve al panel por defecto
908  if (!professionFilter && !nationalityFilter) {
909    if (typeof window.showDefaultNodeInfo === 'function') window.showDefaultNodeInfo();
910    window.__lastSelection = null;
911    return;
912  }
913
914  if (count === 0) {
915    html += `<p style="color:#ccc;">No nodes match the selected filters.</p>`;
916  } else {
917    html += `<ul style="margin-top:0.4rem;">`;
918    items.forEach(it => {
919      html += `<li><a href="#" style="color:#66ccff" onclick="focusNode('${it.id}'); return false;">${it.name}</a></li>`;
920    });
921    html += `</ul>`;
922  }
923
924  html += `</div>`;
925  nodeInfo.innerHTML = html;
926
927  // Guardar estado
928  window.__lastSelection = { type: 'filter', professionFilter, nationalityFilter, ids: (matchingNodeIds || []).slice() };
929};
930
931// ==============================
932// FILTER HELPERS (PEGAR ENCIMA de window.filterGraph)
933// ==============================
934function __gnFold(s) {
935  return String(s || '')
936    .normalize('NFD').replace(/[\u0300-\u036f]/g, '')
937    .toLowerCase()
938    .trim();
939}
940
941function __gnTokenizeList(val) {
942  if (!val) return [];
943  if (Array.isArray(val)) return val.flatMap(__gnTokenizeList);
944
945  // Split por comas, punto y coma, slash, etc.
946  return String(val)
947    .split(/[,;\/|]/g)
948    .map(x => __gnFold(x))
949    .filter(Boolean);
950}
951
952function __gnVariants(term) {
953  const t = __gnFold(term);
954  const out = new Set([t]);
955
956  // singular/plural simples
957  if (t.endsWith('s')) out.add(t.slice(0, -1));
958  else out.add(t + 's');
959
960  if (t.endsWith('es')) out.add(t.slice(0, -2));
961  else out.add(t + 'es');
962
963  return out;
964}
965
966// Variantes ES/EN para nacionalidades (para igualar conteos ES vs EN)
967const __GN_NAT_SYNONYMS = {
968  spanish: ['spanish', 'espanol', 'español', 'espanola', 'española'],
969  french:  ['french', 'frances', 'francés', 'francesa', 'française', 'francais', 'français'],
970  british: ['british', 'ingles', 'inglés', 'inglesa', 'ingleses', 'britanico', 'británico', 'britanica', 'británica'],
971  german:  ['german', 'aleman', 'alemán', 'alemana', 'deutsch'],
972  italian: ['italian', 'italiano', 'italiana'],
973  irish:   ['irish', 'irlandes', 'irlandés', 'irlandesa'],
974  swiss:   ['swiss', 'suizo', 'suiza'],
975  cuban:   ['cuban', 'cubano', 'cubana'],
976  polish:  ['polish', 'polaco', 'polaca']
977};
978
979// Variantes ES/EN para profesiones (mínimo: arregla “art dealer”)
980const __GN_PROF_SYNONYMS = {
981  'art dealer': ['art dealer', 'art dealers', 'marchante de arte', 'marchantes de arte', 'marchante', 'marchantes']
982};
983
984// Clave de orden por apellido (versión “global” equivalente a la lógica de Members list)
985function __gnSurnameKeyForSort(name) {
986  if (!name) return '';
987  const exactName = String(name).trim();
988  const forceMap = window.__GN_SURNAME_FORCE_BY_FULLNAME ||
989    ((typeof SURNAME_FORCE_BY_FULLNAME !== 'undefined') ? SURNAME_FORCE_BY_FULLNAME : null);
990  const forced = forceMap ? (forceMap[name] || forceMap[exactName]) : null;
991  if (forced) return __gnFold(forced);
992
993  let base = String(name)
994    .replace(/\(.*?\)/g, '')
995    .split(',')[0]
996    .replace(/[.]/g, ' ')
997    .replace(/\s+/g, ' ')
998    .trim();
999
1000  const fold = __gnFold;
1001  const tokens = base.split(' ').filter(Boolean);
1002  if (!tokens.length) return '';
1003
1004  const tf = tokens.map(fold);
1005
1006  const titles = new Set([
1007    'duchess','duke','count','countess','marquis','marchioness',
1008    'queen','king','prince','princess','emperor','empress'
1009  ]);
1010  const roman = /^[IVXLCDM]+$/i;
1011
1012  // “Duchess of Abrantes” => Abrantes
1013  if (tf.some(t => titles.has(t)) && tf.includes('of') && tokens.length >= 2) {
1014    return fold(tokens[tokens.length - 1]);
1015  }
1016
1017  // “X y Y” => toma el anterior a 'y'
1018  const yIndex = tf.indexOf('y');
1019  if (yIndex > 0 && yIndex < tf.length - 1) {
1020    return fold(tokens[yIndex - 1]);
1021  }
1022
1023  // Si empieza por inicial “A. Dutuit” => usar último token
1024  // (ya quitamos puntos arriba, así que quedará “A Dutuit”)
1025  if (tokens.length >= 2 && tokens[0].length === 1) {
1026    return fold(tokens[tokens.length - 1]);
1027  }
1028
1029  // por defecto: último token que no sea romano
1030  for (let i = tokens.length - 1; i >= 0; i--) {
1031    const t = fold(tokens[i]);
1032    if (!roman.test(t)) return t;
1033  }
1034  return fold(tokens[tokens.length - 1]);
1035}
1036
1037function __gnMatchesProfession(nodeProf, selectedProf) {
1038  if (!selectedProf) return true;
1039
1040  const sel = __gnFold(selectedProf);
1041  const candidates = new Set();
1042
1043  // 1) valor tal cual + variantes
1044  for (const v of __gnVariants(sel)) candidates.add(v);
1045
1046  // 2) sinónimos explícitos
1047  if (__GN_PROF_SYNONYMS[sel]) {
1048    __GN_PROF_SYNONYMS[sel].forEach(s => __gnVariants(s).forEach(v => candidates.add(__gnFold(v))));
1049  }
1050
1051  // 3) si existe función t() (i18n), añade traducción como posible match
1052  try {
1053    if (typeof t === 'function') {
1054      const tr = __gnFold(t(selectedProf));
1055      if (tr) __gnVariants(tr).forEach(v => candidates.add(v));
1056      if (__GN_PROF_SYNONYMS[tr]) {
1057        __GN_PROF_SYNONYMS[tr].forEach(s => __gnVariants(s).forEach(v => candidates.add(__gnFold(v))));
1058      }
1059    }
1060  } catch (_) {}
1061
1062  const profTokens = __gnTokenizeList(nodeProf);
1063  return profTokens.some(tok => candidates.has(tok));
1064}
1065
1066function __gnMatchesNationality(nodeNat, selectedNat) {
1067  if (!selectedNat) return true;
1068
1069  const sel = __gnFold(selectedNat);
1070  const candidates = new Set([sel]);
1071
1072  // añade sinónimos ES/EN si existen
1073  if (__GN_NAT_SYNONYMS[sel]) {
1074    __GN_NAT_SYNONYMS[sel].forEach(s => candidates.add(__gnFold(s)));
1075  }
1076
1077  // traducción i18n si existiera
1078  try {
1079    if (typeof t === 'function') {
1080      const tr = __gnFold(t(selectedNat));
1081      if (tr) candidates.add(tr);
1082      if (__GN_NAT_SYNONYMS[tr]) {
1083        __GN_NAT_SYNONYMS[tr].forEach(s => candidates.add(__gnFold(s)));
1084      }
1085    }
1086  } catch (_) {}
1087
1088  const natTokens = __gnTokenizeList(nodeNat);
1089  // normalmente nationality es un único valor; pero lo tratamos igual
1090  
1091  // Check for exact token match first
1092  if (natTokens.some(tok => candidates.has(tok))) return true;
1093  
1094  // Also check for word-level matches within tokens (for cases like "French (with Irish ancestry)")
1095  const natFolded = __gnFold(nodeNat);
1096  for (const candidate of candidates) {
1097    // Use word boundary regex to find the candidate word within the nationality string
1098    const regex = new RegExp(`\\b${candidate}\\b`);
1099    if (regex.test(natFolded)) return true;
1100  }
1101  
1102  return false;
1103}
1104
1105function __gnRenderFilterPanel({ professionFilter, nationalityFilter, matchingNodeIds, totalCount }) {
1106  const nodeInfo = document.getElementById('nodeInfo');
1107  if (!nodeInfo) return;
1108
1109  // Si no hay filtros activos: vuelve al panel por defecto
1110  if (!professionFilter && !nationalityFilter) {
1111    if (typeof window.showDefaultNodeInfo === 'function') window.showDefaultNodeInfo();
1112    return;
1113  }
1114
1115  const parts = [];
1116  if (professionFilter) parts.push(`<strong>Profession:</strong> ${professionFilter}`);
1117  if (nationalityFilter) parts.push(`<strong>Nationality:</strong> ${nationalityFilter}`);
1118
1119  const items = (matchingNodeIds || [])
1120    .map(id => nodes.get(id))
1121    .filter(Boolean)
1122    // mostrar SIEMPRE el id (nombre completo)
1123    .map(n => ({ id: n.id, name: n.id }))
1124    // ordenar por apellido (pero sin mutilar el nombre mostrado)
1125    .sort((a, b) => {
1126      const ka = __gnSurnameKeyForSort(a.name);
1127      const kb = __gnSurnameKeyForSort(b.name);
1128      if (ka !== kb) return ka.localeCompare(kb, undefined, { sensitivity: 'base' });
1129      return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' });
1130    });
1131
1132  const count = items.length;
1133
1134  let html = `
1135    <div class="node-overview">
1136      <div>${parts.join(' &nbsp;|&nbsp; ')}</div>
1137      <div>${count} nodes out of ${totalCount}</div>
1138    </div>
1139    <div class="section-heading">Results (A–Z)</div>
1140    <div style="line-height:1.6;">
1141  `;
1142
1143  if (!count) {
1144    html += `<p style="color:#ccc;">No nodes match the selected filters.</p>`;
1145  } else {
1146    html += `<ul style="margin-top:0.4rem;">`;
1147    items.forEach(it => {
1148      html += `<li><a href="#" style="color:#66ccff" onclick="focusNode('${it.id}'); return false;">${it.name}</a></li>`;
1149    });
1150    html += `</ul>`;
1151  }
1152
1153  html += `</div>`;
1154  nodeInfo.innerHTML = html;
1155}
1156
1157// ==============================
1158// REEMPLAZAR COMPLETO: window.filterGraph
1159// ==============================
1160function splitAndNormalizeList(v) {
1161  if (!v) return [];
1162  const raw = Array.isArray(v) ? v : String(v).split(/[;,]/); // soporta coma y punto y coma
1163  return raw
1164    .map(s => String(s).trim().toLowerCase())
1165    .filter(Boolean);
1166}
1167
1168// Convierte valores en ES (p.ej. "marchante de arte") a la clave canónica ("art dealer"),
1169// usando los <option value="..."> del selector + los textos I18N.
1170function buildCanonicalMap(selectId, lang) {
1171  const map = Object.create(null);
1172  const sel = document.getElementById(selectId);
1173  if (!sel) return map;
1174
1175  const dict = (I18N && I18N[lang]) ? I18N[lang] : {};
1176
1177  [...sel.options].forEach(opt => {
1178    if (!opt.value) return; // salta "All ..."
1179    const canonical = opt.value.trim().toLowerCase(); // p.ej. "art dealer"
1180    map[canonical] = canonical;
1181
1182    // texto visible del option (si está traducido por applyUIStrings)
1183    if (opt.textContent) map[opt.textContent.trim().toLowerCase()] = canonical;
1184
1185    // traducción desde I18N (por si el dato viene en ES pero el option aún no)
1186    if (dict[opt.value]) map[String(dict[opt.value]).trim().toLowerCase()] = canonical;
1187  });
1188
1189  return map;
1190}
1191window.filterGraph = function() {
1192  const professionFilter = document.getElementById('professionFilter')?.value || '';
1193  const nationalityFilter = document.getElementById('nationalityFilter')?.value || '';
1194
1195  // 1) limpiar highlights previos (usa tu clearHighlights si existe; si no, usa el “expuesto”)
1196  if (typeof clearHighlights === 'function') clearHighlights();
1197  else if (typeof window.__GN_clearHighlights === 'function') window.__GN_clearHighlights();
1198
1199  // 2) sin filtros: salir + restaurar panel
1200  if (!professionFilter && !nationalityFilter) {
1201    __gnRenderFilterPanel({ professionFilter:'', nationalityFilter:'', matchingNodeIds:[], totalCount: (window.__GN_DATA?.nodes?.length || nodes.get().length) });
1202    return;
1203  }
1204
1205  // 3) total “real” desde JSON (evita 207 vs 202)
1206  const baseNodes = (window.__GN_DATA?.nodes && Array.isArray(window.__GN_DATA.nodes))
1207    ? window.__GN_DATA.nodes
1208    : nodes.get();
1209
1210  const totalCount = baseNodes.length;
1211
1212  // 4) matching robusto (case-insensitive + listas + ES/EN variantes)
1213  const matchingIds = baseNodes
1214    .filter(n => __gnMatchesProfession(n.profession, professionFilter) && __gnMatchesNationality(n.nationality, nationalityFilter))
1215    .map(n => n.id);
1216
1217  // 5) si no hay resultados, renderiza panel (sin alert)
1218  if (!matchingIds.length) {
1219    __gnRenderFilterPanel({ professionFilter, nationalityFilter, matchingNodeIds: [], totalCount });
1220    return;
1221  }
1222
1223  // 6) resaltar en rojo (borde rojo + ancho)
1224  matchingIds.forEach(id => {
1225    const node = nodes.get(id);
1226    if (!node) return;
1227    nodes.update({
1228      id,
1229      color: { ...(node.color || {}), border: 'red' },
1230      borderWidth: 4
1231    });
1232  });
1233
1234  // 7) foco en el primer match
1235  const firstId = matchingIds[0];
1236  try {
1237    const scale = window.VIS_NETWORK.getScale();
1238    const pos = window.VIS_NETWORK.getPosition(firstId);
1239    window.VIS_NETWORK.moveTo({ position: pos, scale, animation: { duration: 500 } });
1240  } catch (_) {}
1241
1242  // 8) registra lastHighlightedNodes para tu sistema de “clearHighlights”
1243  if (typeof window.__GN_setLastHighlightedNodes === 'function') {
1244    window.__GN_setLastHighlightedNodes(matchingIds);
1245  } else if (typeof lastHighlightedNodes !== 'undefined') {
1246    lastHighlightedNodes = matchingIds;
1247  }
1248
1249  // 9) pinta panel derecho
1250  __gnRenderFilterPanel({ professionFilter, nationalityFilter, matchingNodeIds: matchingIds, totalCount });
1251};
1252
1253// ===== HASH NAVIGATION SETUP =====
1254let __hashProcessed = false;
1255
1256// ===== GLOBAL HELPER FUNCTIONS FOR HASH NAVIGATION =====
1257function slugifyName(name) {
1258  return String(name)
1259    .normalize('NFD')
1260    .replace(/[\u0300-\u036f]/g, '') // Remove accents
1261    .replace(/ /g, '_')               // Replace spaces with underscores
1262    .toLowerCase();                   // Make lowercase
1263}
1264
1265function unslugifyName(slug) {
1266  return String(slug).replace(/_/g, ' ');
1267}
1268
1269function normalizeForComparison(str) {
1270  if (!str || typeof str !== 'string') {
1271    return '';
1272  }
1273  return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
1274}
1275
1276function makeNodeHash(nodeId) {
1277  return '#node/' + slugifyName(nodeId);
1278}
1279
1280function makeEdgeHash(edgeId) {
1281  if (typeof edges === 'undefined' || !edges) return '';
1282  const edge = edges.get(edgeId);
1283  if (!edge) return '';
1284
1285  if (typeof nodes === 'undefined' || !nodes) return '';
1286  const fromNode = nodes.get(edge.from);
1287  const toNode = nodes.get(edge.to);
1288  if (!fromNode || !toNode) return '';
1289
1290  const a = fromNode.id;
1291  const b = toNode.id;
1292
1293  // Ordenar para que el hash sea consistente
1294  const ordered = [a, b].sort((x, y) =>
1295    x.localeCompare(y, undefined, { sensitivity: 'base' })
1296  );
1297
1298  return '#edge/' + slugifyName(ordered[0]) + '--' + slugifyName(ordered[1]);
1299}
1300
1301// ===== END GLOBAL HELPERS =====
1302
1303// Simple hash navigation - no promises to avoid Chrome extension errors
1304function handleInitialHash(retryCount = 0) {
1305  const MAX_RETRIES = 15;
1306  const RETRY_INTERVAL = 200;  // Fast retry (200ms instead of 500ms)
1307  const rawHash = window.location.hash.substring(1);
1308  
1309  if (__hashProcessed || !rawHash) {
1310    return;
1311  }
1312  
1313  const networkOk = !!window.VIS_NETWORK;
1314  const nodesOk = !!nodes;
1315  const edgesOk = !!edges;
1316  const nodesCount = nodes ? nodes.get().length : 0;
1317  const edgesCount = edges ? edges.get().length : 0;
1318  const ready = networkOk && nodesOk && edgesOk && nodesCount > 0 && edgesCount > 0;
1319  
1320  if (!ready && retryCount < MAX_RETRIES) {
1321    console.log(`🔄 Hash navigation retry ${retryCount + 1}/${MAX_RETRIES} - networkOk: ${networkOk}, nodesOk: ${nodesOk} (${nodesCount}), edgesOk: ${edgesOk} (${edgesCount})`);
1322    setTimeout(() => handleInitialHash(retryCount + 1), RETRY_INTERVAL);
1323    return;
1324  }
1325  if (!ready) {
1326    console.error("❌ Hash navigation FAILED - network not ready after retries");
1327    __hashProcessed = true;
1328    return;
1329  }
1330
1331  __hashProcessed = true;
1332  
1333  // Old format: #Node_Name
1334  if (!rawHash.includes('/')) {
1335    const decodedHash = decodeURIComponent(rawHash).replace(/_/g, ' ');
1336    let node = nodes.get().find(n => n.id === decodedHash || n.label === decodedHash);
1337    if (node) {
1338      setTimeout(() => {
1339        const pos = window.VIS_NETWORK.getPosition(node.id);
1340        window.VIS_NETWORK.moveTo({position: pos, scale: window.VIS_NETWORK.getScale(), animation: {duration: 500}});
1341        window.VIS_NETWORK.selectNodes([node.id]);
1342        window.VIS_NETWORK.emit('click', {nodes: [node.id], edges: [], pointer: {DOM: {x: 0, y: 0}, canvas: {x: 0, y: 0}}});
1343      }, 50);
1344    }
1345    return;
1346  }
1347
1348  const slashIndex = rawHash.indexOf('/');
1349  const type = rawHash.substring(0, slashIndex);
1350  const value = rawHash.substring(slashIndex + 1);
1351
1352  if (type === 'node') {
1353    const decodedId = unslugifyName(value);
1354    const decodedNorm = normalizeForComparison(decodedId);
1355    let node = nodes.get().find(n => normalizeForComparison(n.id) === decodedNorm);
1356    
1357    if (node) {
1358      setTimeout(() => {
1359        const pos = window.VIS_NETWORK.getPosition(node.id);
1360        window.VIS_NETWORK.moveTo({position: pos, scale: window.VIS_NETWORK.getScale(), animation: {duration: 500}});
1361        window.VIS_NETWORK.selectNodes([node.id]);
1362        window.VIS_NETWORK.emit('click', {nodes: [node.id], edges: [], pointer: {DOM: {x: 0, y: 0}, canvas: {x: 0, y: 0}}});
1363      }, 50);
1364    }
1365    return;
1366  }
1367
1368  if (type === 'edge') {
1369    const parts = value.split('--');
1370    if (parts.length !== 2) return;
1371
1372    const left = unslugifyName(parts[0]);
1373    const right = unslugifyName(parts[1]);
1374    const leftNorm = normalizeForComparison(left);
1375    const rightNorm = normalizeForComparison(right);
1376
1377    let matchingEdge = edges.get().find(edge => {
1378      if (edge._isClusterEdge || edge.hidden) return false;
1379      const fromNode = nodes.get(edge.from);
1380      const toNode = nodes.get(edge.to);
1381      if (!fromNode || !toNode) return false;
1382      const pairNorm = [normalizeForComparison(fromNode.id), normalizeForComparison(toNode.id)].sort();
1383      return pairNorm[0] === leftNorm && pairNorm[1] === rightNorm;
1384    });
1385
1386    if (matchingEdge) {
1387      setTimeout(() => {
1388        const fromPos = window.VIS_NETWORK.getPosition(matchingEdge.from);
1389        const toPos = window.VIS_NETWORK.getPosition(matchingEdge.to);
1390        if (fromPos && toPos) {
1391          const centerX = (fromPos.x + toPos.x) / 2;
1392          const centerY = (fromPos.y + toPos.y) / 2;
1393          window.VIS_NETWORK.moveTo({position: {x: centerX, y: centerY}, scale: window.VIS_NETWORK.getScale(), animation: {duration: 500}});
1394        }
1395        window.VIS_NETWORK.selectEdges([matchingEdge.id]);
1396        window.VIS_NETWORK.emit('click', {nodes: [], edges: [matchingEdge.id], pointer: {DOM: {x: 0, y: 0}, canvas: {x: 0, y: 0}}});
1397      }, 50);
1398    }
1399  }
1400}
1401
1402document.addEventListener('DOMContentLoaded', async function () {
1403  applyUIStrings();
1404
1405  if (CURRENT_LANG === 'es') {
1406        sortSelectOptionsAlphabetically('professionFilter');
1407      }
1408
1409  const sel = document.getElementById('langSwitcher');
1410  if (sel) sel.value = CURRENT_LANG;
1411
1412  // ——— Default snapshot del panel lateral (nodeInfo)
1413  let __defaultNodeInfoHTML = null;
1414  // Add this right after: document.addEventListener('DOMContentLoaded', async function () {
1415  try {
1416        // ADD THESE LINES:
1417        const preloadImages = (nodes) => {
1418            const promises = nodes
1419                .filter(node => node.image)
1420                .map(node => {
1421                    return new Promise((resolve) => {
1422                        const img = new Image();
1423                        img.src = node.image;
1424                        img.onload = resolve;
1425                        img.onerror = resolve;
1426                    });
1427                });
1428            return Promise.all(promises);
1429        };
1430
1431        // 🔥 DECLARAR nodePositions ANTES de usarlo
1432        let nodePositions = JSON.parse(localStorage.getItem('nodePositions') || '{}');
1433    
1434        // Load the network data
1435        // ✅ NUEVO CÓDIGO (carga ambos archivos)
1436        const [networkResponse, positionsResponse] = await Promise.all([
1437          fetch(getDataFileForLang(CURRENT_LANG)),
1438          fetch('positions_config.json')
1439        ]);
1440        
1441        if (!networkResponse.ok) throw new Error('Error cargando datos de red');
1442        const data = await networkResponse.json();
1443        window.__GN_DATA = data;
1444        
1445        // Cargar posiciones (si existe)
1446        if (positionsResponse.ok) {
1447          const positionsData = await positionsResponse.json();
1448          nodePositions = positionsData;
1449          console.log('✅ Posiciones cargadas desde positions_config.json');
1450        } else {
1451          console.warn('⚠️ positions_config.json no encontrado, usando posiciones por defecto');
1452        }
1453        
1454        // Start image preloading
1455        // const imagePreload = preloadImages(data.nodes);
1456
1457        // Existing setup code
1458        const nodeInfo = document.getElementById('nodeInfo');
1459        nodeInfo.style.maxHeight = '810px';
1460        nodeInfo.style.overflowY = 'auto';
1461
1462        const edgeCount = {};
1463        data.edges.forEach(edge => {
1464          edgeCount[edge.from] = (edgeCount[edge.from] || 0) + 1;
1465          edgeCount[edge.to] = (edgeCount[edge.to] || 0) + 1;
1466        });
1467
1468        const nodesMap = {};
1469        const labelToId = {};
1470        // Cluster color palette
1471        const CLUSTER_COLORS = [
1472          '#e57373', // red
1473          '#64b5f6', // blue
1474          '#81c784', // green
1475          '#ffd54f', // yellow
1476          '#ba68c8', // purple
1477          '#4db6ac', // teal
1478          '#ffb74d', // orange
1479          '#a1887f', // brown
1480          '#90a4ae', // gray-blue
1481          '#f06292', // pink
1482        ];
1483
1484        const RADIAL_CLUSTERS = {
1485          "MADRAZO_FAMILY": {
1486            members: [
1487              "Federico de Madrazo",
1488              "José de Madrazo",
1489              "Pedro de Madrazo",
1490              "Luis de Madrazo",
1491              "Raimundo de Madrazo",
1492              "Cecilia de Madrazo",
1493              "Román Garreta",
1494              "Mariano Fortuny y Madrazo",
1495              "Mariano Fortuny y Marsal",
1496              "Luisa Garreta",
1497              "Juan de Madrazo"
1498            ],
1499            radius: 200,
1500            padding: 150,
1501            centerYOffset: -150,
1502            startAngle: -Math.PI / 2,
1503            sharedBoundaryNodes: {
1504              "José de Madrazo": Math.PI * 1.25  // north-west, next to Federico
1505            },
1506            title: "Madrazo family",
1507            titleEs: "Familia Madrazo",
1508            origin: "A prominent Spanish artistic dynasty deeply embedded in the institutional structure of nineteenth-century art production and curation. Beginning with José de Madrazo, the family maintained a sustained presence as court painters and museum administrators —mainly at the royal collections and future Prado Museum—, closely connected to the Spanish monarchy across the reigns of Carlos IV, Fernando VII, and Isabel II. Their activity extended beyond Spain through strong professional and social ties in France and Italy, where they participated in collecting networks and transnational art markets. The family also played a significant role in the reception and circulation of Francisco de Goya's work: José de Madrazo was his court colleague, while later generations—particularly Federico de Madrazo—actively collected his works and facilitated their acquisition and sale in France in collaboration with figures such as Baron Taylor. The network includes both biological and marital relations, incorporating artists such as Mariano Fortuny y Marsal.",
1509            originEs: "Una destacada dinastía artística española profundamente integrada en la estructura institucional de la producción y gestión del arte en el siglo XIX. Desde José de Madrazo, la familia mantuvo una presencia continuada como pintores de corte y responsables de instituciones museísticas —sobre todo las colecciones reales y el Museo del Prado—, estrechamente vinculados a la monarquía española durante los reinados de Carlos IV, Fernando VII e Isabel II. Su actividad se extendió más allá de España a través de fuertes vínculos profesionales y sociales en Francia e Italia, donde participaron en redes de coleccionismo y en el mercado artístico transnacional. La familia desempeñó también un papel relevante en la recepción y circulación de la obra de Francisco de Goya: José de Madrazo fue su colega en la corte, mientras que generaciones posteriores —en particular Federico de Madrazo— coleccionaron su obra y facilitaron su compra y venta en Francia en colaboración con figuras como el Baron Taylor. El grupo incluye tanto relaciones biológicas como matrimoniales, incorporando artistas como Mariano Fortuny y Marsal.",
1510            bibliography: [
1511              "Amaya Alzaga, <em>Raimundo de Madrazo y Garreta (1841-1920)</em>, PhD thesis, UNED, 2012",
1512              "Amaya Alzaga, 'Federico de Madrazo en París (1837-1839): su mirada sobre la pintura francesa de su tiempo y viceversa', in <em>Federico de Madrazo y Carlos Luis de Ribera. Pintores Del Romanticismo Español</em>, 2018, pp.8-28.",
1513              "José Luis Díez, <em>Federico de Madrazo: epistolario, vol. 1</em>, 1994.",
1514              "Ainhoa Gilarranz Ibáñez, 'No Solo Es Arte: Las Redes de Federico de Madrazo Durante Su Pensionado En París y Roma (1837-1842)'', <em>Bulletin Hispanique</em>, no. 122-1, June 2020, pp. 277-92.",
1515              "Carlos González López, <em>Federico de Madrazo y Küntz</em>, 1981.",
1516              "Carlos González López, 'Federico de Madrazo en el París de 1833', <em>Archivo Español de Arte</em>, 1994, vol. 67, no. 267, pp.211-24.",
1517              "Carlos González Navarro, <em>José de Madrazo (1781-1859), Dibujante</em>, PhD thesis, Madrid, Universidad Complutense de Madrid, 2023.",
1518              "Monte Martí, <em>El mundo de los Madrazo: colección de la Comunidad de Madrid</em>, 2007.",
1519              "Pedro J. Martínez Plaza, <em>Epistolario del Archivo de Madrazo del Museo del Prado. Cartas de Mariano Fortuny, Cecilia, Ricardo, Raimundo e Isabel de Madrazo</em>, 2017.",
1520              "Raquel Sánchez, 'José de Madrazo y los deberes del padre perfecto', in <em>Hijos del Siglo</em>, 2024."
1521            ],
1522            websites: []
1523            // Format: websites: ["[Label Name](https://url.com)", "[Another](https://url2.com)"]
1524          },
1525
1526          "HUGO_CENACLE": {
1527            members: [
1528              "Honoré de Balzac",
1529              "Louis Boulanger",
1530              "Eugène Delacroix",
1531              "Achille Devéria",
1532              "Alexandre Dumas père",
1533              "Antoine Fontaney",
1534              "Théophile Gautier",
1535              "Prosper Mérimée",
1536              "Alfred de Musset",
1537              "Charles Nodier",
1538              "Charles-Augustin Sainte-Beuve"
1539            ],
1540            center: "Victor Hugo",
1541            radius: 200,
1542            padding: 90,
1543            startAngle: -Math.PI / 2,
1544            title: "Hugo cenacle",
1545            titleEs: "Clúster Hugo",
1546            origin: "A literary and artistic circle formed around Victor Hugo in the late 1820s, often associated with the emergence of French Romanticism. Composed primarily of young writers, poets, and playwrights, alongside painters and draftsmen, the group developed in opposition to established academic and classical norms, advocating new artistic and literary forms aligned with contemporary political liberalism and the cultural ideal of modernity. Many members were also active collectors and critics. Within their broader Romantic interests, they showed an early and sustained engagement with Francisco de Goya—particularly his <i>Caprichos</i>—becoming among the first French intellectuals to systematically interpret and circulate his imagery, well before its reception in Spain, and contributing to his later critical canonization.",
1547            originEs: "Un círculo literario y artístico formado en torno a Victor Hugo a finales de la década de 1820, asociado a la emergencia del Romanticismo francés. Integrado principalmente por jóvenes escritores, poetas y dramaturgos, junto a pintores y dibujantes, el grupo se configuró en oposición a las normas académicas y clásicas establecidas, defendiendo nuevas formas artísticas y literarias vinculadas al liberalismo político contemporáneo y a un ideal cultural de modernidad. Muchos de sus miembros fueron también coleccionistas y críticos. Dentro de sus intereses románticos más amplios, mostraron una temprana y sostenida atención a Francisco de Goya —en particular los <i>Caprichos</i>—, convirtiéndose en algunos de los primeros intelectuales franceses en interpretar y difundir sistemáticamente su imaginería, mucho antes de su recepción en España, contribuyendo a su posterior canonización crítica.",
1548            bibliography: [
1549              "Claude Schopp (ed.), <em>Une amitié capitale: Correspondance Victor Hugo - Alexandre Dumas</em>, 2015.",
1550              "Claude Schopp (ed.), <em>Frères d'armes de la révolution romantique. Lettres d'Alexandre Dumas au Baron Taylor et à Adrien Dauzats</em>, Cahiers Alexandre Dumas, 1993, no. 20 (reedited in 2023).",
1551              "Robert Avrett, 'Ochoa's Translation of Hugo's Hernani: A Study in Racial Psychologies' <em>The Modern Language Journal</em>, Vol. 38 no. 7, Nov 1954, pp.362-65.",
1552              "Ilse Hempel Lipschutz, <em>Spanish Painting and the French Romantics</em>, Harvard University Press, 1972.",
1553              "Mª Rosario Ozaeta, 'Eugenio de Ochoa, Traductor de Hugo', in <em>Neoclásicos y románticos ante la traducción</em>, edited by Francisco Lafarga et al, Universidad de Murcia, 2002, pp.419-36.",
1554              "Roberto Dengler Gassin, 'Algunas consideraciones a propósito de Hernani, drama de Victor Hugo (1830), versión castellana de Eugenio de Ochoa (1836)', in ed. Donaire & Lafarga, <em>Traducción y adaptación cultural: España-Francia</em>, 1991, pp.337-345.",
1555              "Anne Martin-Fugier, <i>Les Romantiques: 1820-1848</i>, 1998.",
1556              "René Bray, <i>Chronologie du romantisme (1804-1830)</i>, t.33, coll. Bibliothèque de la Revue des cours et conférences, VII, 1932.",
1557              "Charles Marc Des Granges, <i>Le Romantisme et la critique : la presse littéraire sous la restauration, 1815-1830</i>, 1907.",
1558              "Edmond Biré, <i>Victor Hugo avant 1830</i>, 1902."
1559            ],
1560            websites: [
1561              "[Atenas. Diario de a bordo](https://atenas-diariodeabordo.blogspot.com/search/label/Romanticismo%20y%20Rom%C3%A1nticos%20%E2%80%93III%20a%E2%80%93%20V%C3%8DCTOR%20HUGO.%20La%20vida): Romanticismo y Románticos -III a- Víctor Hugo. La vida.",
1562            ]
1563            // Format: websites: ["[Label Name](https://url.com)", "[Another](https://url2.com)"]
1564          },
1565
1566          "ILUSTRADOS_CLUSTER": {
1567            members: [
1568              "XI Count of Altamira",
1569              "Francisco Cabarrús",
1570              "Juan Agustín Ceán Bermúdez",
1571              "Leandro Fernández de Moratín",
1572              "Joaquín María Ferrer",
1573              "Count of Floridablanca",
1574              "Francisco de Goya",
1575              "Bernardo de Iriarte",
1576              "Juan Antonio Llorente",
1577              "Sebastián Martínez y Pérez",
1578              "Gaspar Melchor de Jovellanos",
1579              "Juan Meléndez Valdés",
1580              "Martín Zapater",
1581              "Juan Fernández de Rojas",
1582              "Manuel García de la Prada"
1583            ],
1584            radius: 166,
1585            padding: 92,
1586            centerYOffset: 180,
1587            startAngle: -Math.PI / 2,
1588            sharedBoundaryNodes: {
1589              "Francisco de Goya": Math.PI / 2,     // south of cluster
1590              "Martín Zapater": Math.PI / 2.2     // also south, close to Goya
1591            },
1592            title: "Ilustrados cluster",
1593            titleEs: "Clúster Ilustrados",
1594            origin: "A heterogeneous intellectual and political network associated with the Spanish Enlightenment (<i>Ilustración</i>), united by reformist ideals and a shared commitment to cultural, administrative, and social modernization. Comprising statesmen, writers, clerics, bankers, and artists, the group contributed to debates on progress and institutional reform in late eighteenth- and early nineteenth-century Spain, often operating within overlapping courtly, academic, and economic spheres. Many members maintained close personal and professional relationships with Francisco de Goya, who portrayed several of them in portraiture, reflecting both patterns of patronage and a degree of intellectual affinity.",
1595            originEs: "Un entramado intelectual y político heterogéneo vinculado a la Ilustración española, unido por ideales reformistas y por un compromiso compartido con la modernización cultural, administrativa y social. Integrado por estadistas, escritores, clérigos, banqueros y artistas, el grupo participó en los debates sobre el progreso y la reforma institucional en la España de finales del siglo XVIII y comienzos del XIX, operando frecuentemente en espacios cortesanos, académicos y económicos interconectados. Muchos de sus miembros mantuvieron relaciones estrechas personales y profesionales con Francisco de Goya, quien retrató a varios de ellos, reflejando tanto dinámicas de mecenazgo como un grado de afinidad intelectual.",
1596            bibliography: [
1597              "Mercedes Águeda Villar & Xavier de Salas (eds.), <i>Francisco de Goya: cartas a Martín Zapater</i>, 1982.",
1598              "Ángel Canellas López, <i>Diplomatario. Francisco de Goya</i>, 1981.",
1599              "Jacqueline Hara, <i>Francisco de Goya (1746-1828). Letters of Love and Friendship in Translation</i>, 1997.",
1600              "Enrique Lafuente Ferrari, 'Las cartas de Goya a Zapater y los epistolarios españoles', <i>Homenaje a la memoria de don Antonio Rodríguez Moñino, 1910-1970</i>, 1975, pp. 285-328.",
1601              "Luis Martín-Estudillo, <i>Goya o el misterio de la lectura</i>, 2023.",
1602              "Manuela B. Mena Marqués, Gudrun Maurer (coord.), Virginia Albarrán Martín, <i>Goya y la corte ilustrada</i>, 2017.",
1603              "Manuela B. Mena Marqués, 'Jovellanos pintado por Goya', <i>Boletín jovellanista</i>, no.17, 2017, pp.135-47.",
1604              "José Luis Morales y Marín, <i>Francisco Bayeu. Vida y obra</i>, 1995.",
1605              "Natacha Seseña, <i>Goya y las mujeres</i>, 2004.",
1606              "Janis Tomlinson, <em>Goya in the Twilight of Enlightenment</em>, 1992."
1607            ],
1608            websites: [
1609              "[Cartas de Goya a Martín Zapater](https://www.museodelprado.es/aprende/enciclopedia/voz/cartas-de-goya-a-martin-zapater/2ab3aedb-07a9-4031-b6e0-64d9806ac8b5), Mercedes Águeda Villar, Museo del Prado."
1610            ],
1611            conferences: [
1612              "Manuela B. Mena Marqués, [Goya y sus amigos](https://www.museodelprado.es/actualidad/multimedia/goya-y-sus-amigos-por-manuela-mena/2bcc3d56-1056-43cb-80d2-ccb552b54541), Cátedra 2013."
1613            ]
1614          },
1615
1616          "GOYA_FAMILY": {
1617            members: [
1618              "Francisco de Goya",
1619              "Javier Goya",
1620              "Mariano Goya",
1621              "Gumersinda Goicoechea",
1622              "Josefa Bayeu",
1623              "Francisco Bayeu"
1624            ],
1625            radius: 85,
1626            padding: 65,
1627            startAngle: -Math.PI / 2,
1628            sharedBoundaryNodes: {
1629              "Francisco de Goya": -Math.PI / 2
1630            },
1631            title: "Goya family",
1632            titleEs: "Familia Goya",
1633            origin: "A family network of Francisco de Goya, encompassing his biological relatives and close in-laws. Originating in Aragón and later established in Madrid, the group reflects the painter’s early social and professional environment as well as the domestic ties that connected him to the Bayeu family, particularly through marriage alliances and artistic apprenticeship networks.",
1634            originEs: "Un entramado familiar en torno a Francisco de Goya, que incluye tanto sus parientes biológicos como sus allegados políticos. De origen aragonés y posteriormente establecido en Madrid, el grupo refleja el entorno social y profesional temprano del pintor, así como los vínculos domésticos que lo conectaron con la fam
1634ilia Bayeu, especialmente a través de alianzas matrimoniales y redes de formación artística.",
1635            bibliography: [],
1636            websites: []
1637          },
1638
1639          "OSUNA_CORE": {
1640            center: "XII Countess-Duchess of Benavente and Duchess of Osuna",
1641            members: [
1642              "IX Duke of Osuna",
1643              "X Duke of Osuna",
1644              "VIII Duchess of Abrantes",
1645              "X Marchioness of Santa Cruz"
1646            ],
1647            radius: 135,
1648            startAngle: -Math.PI / 2,
1649            title: "Osuna core",
1650            titleEs: "Núcleo Osuna",
1651            origin: "A leading Spanish aristocratic circle of the late eighteenth and early nineteenth centuries, centered on the House of Osuna and its allied networks. One of the most important patrons and collectors of Francisco de Goya, the group supported a wide range of his production, from decorative commissions to more experimental and critical imagery, including witchcraft scenes and the Caprichos series. Their cultural profile combined Enlightenment-inflected interests with strong engagement in artistic patronage, and included salonnières who facilitated intellectual and social exchange within elite Madrid circles.",
1652            originEs: "Un influyente círculo de la aristocracia española de finales del siglo XVIII y comienzos del XIX, centrado en la casa de Osuna y sus redes aliadas. Uno de los principales mecenas y coleccionistas de Francisco de Goya, el grupo impulsó un amplio espectro de su producción, desde encargos decorativos hasta imágenes más experimentales y críticas, incluyendo escenas de brujería y la serie de los Caprichos. Su perfil cultural combinaba intereses de raíz ilustrada con un fuerte compromiso con el mecenazgo artístico, e incluía salonnières que facilitaron el intercambio intelectual y social en los círculos de élite madrileños.",
1653            bibliography: [
1654              "Manuela B. Mena Marqués, 'Dos adquisiciones de Goya en el Museo del Prado: <i>Vuelo de brujas</i> (1999) y <i>La marquesa de Santa Cruz</i> (1986), in <i>Los tesoros ocultos del Museo del Prado</i>, 2017, pp.277-305.",
1655              "Manuela B. Mena Marqués, Gudrun Maurer (coord.), Virginia Albarrán Martín, <i>Goya y la corte ilustrada</i>, 2017."
1656              ],
1657            websites: [],
1658            conferences: [
1659              "Manuela Mena, [Vuelo de brujas y La Marquesa de Santa Cruz. Francisco de Goya](https://fundacionbarrie.org/conferencia-manuela-mena), 2016."
1660            ]
1661          },
1662
1663          "MONTIJO_CORE": {
1664            center: "Eugenio Eulalio Palafox, VII Count of Montijo",
1665            members: [
1666              "María Francisca de Sales Portocarrero, VI Countess of Montijo",
1667              "Eugenio Eulalio Palafox, VII Count of Montijo",
1668              "Cipriano Portocarrero, VIII Count of Montijo",
1669              "María Manuela Kirkpatrick",
1670              "Eugenia de Montijo",
1671              "María Gabriela de Palafox, Marchioness of Lazán",
1672              "María Tomasa Palafox, Marchioness of Villafranca"
1673            ],
1674            radius: 140,
1675            padding: 100,
1676            startAngle: -Math.PI / 2,
1677            title: "Montijo core",
1678            titleEs: "Núcleo Montijo",
1679            origin: "A dynastic and social network of Spanish nobility centered on the Montijo and allied Palafox–Villafranca families, operating across Spain and France in the late eighteenth and nineteenth centuries. United through marriage alliances, courtly mobility, and shared cultural interests, they played a role in the patronage and circulation of Francisco de Goya’s work. Several members functioned as salonnières and cultural intermediaries in transnational elite circles, particularly in Paris, where intellectual and literary exchanges included figures such as Prosper Mérimée. The group’s later consolidation of influence at the French imperial court further extended its cultural and political reach.",
1680            originEs: "Un entramado dinástico y social de la nobleza española centrado en las casas de Montijo y las familias aliadas Palafox-Villafranca, activo entre finales del siglo XVIII y el XIX entre España y Francia. Unidos por alianzas matrimoniales, movilidad cortesana e intereses culturales compartidos, participaron en el patronazgo y circulación de la obra de Francisco de Goya. Varios de sus miembros actuaron como salonnières e intermediarios culturales en círculos de élite transnacionales, especialmente en París, donde mantuvieron contactos con figuras literarias e intelectuales como Prosper Mérimée. La posterior proyección de la familia en la corte imperial francesa amplió su influencia cultural y política.",
1681            bibliography: [],
1682            websites: []
1683          },
1684
1685          "SPANISH_COURT_PAINTERS": {
1686            members: [
1687              "Francisco de Goya",
1688              "Francisco Bayeu",
1689              "Mariano Salvador Maella",
1690              "Giambattista Tiepolo",
1691              "Giandomenico Tiepolo",
1692              "Vicente López",
1693              "Anton Raphael Mengs",
1694              "Agustín Esteve",
1695              "Federico de Madrazo",
1696              "José de Madrazo"
1697            ],
1698            radius: 160, // increased from 132 for wider circle
1699            padding: 92,
1700            startAngle: -Math.PI / 2,
1701            title: "Spanish court painters",
1702            titleEs: "Pintores de la corte española",
1703            origin: "A heterogeneous group of painters who served the Spanish royal court across the late eighteenth and early nineteenth centuries. Despite differing stylistic orientations and artistic backgrounds, they were connected through their roles as court painters, resulting in professional proximity, occasional collaboration, and shifting degrees of personal and artistic affinity. Within this network, family ties and generational succession also played a role in shaping interactions, while stylistic contrasts—from late Baroque tradition to Neoclassicism and Romanticism—coexisted within the same institutional framework.",
1704            originEs: "Un grupo heterogéneo de pintores al servicio de la corte española a finales del siglo XVIII y comienzos del XIX. A pesar de sus diferentes estilos, orígenes y posiciones estéticas, estuvieron vinculados por su condición de pintores de cámara, lo que generó proximidad profesional, colaboración ocasional y distintos grados de afinidad personal y artística. Dentro de esta red, los lazos familiares y la sucesión generacional también influyeron en las relaciones, mientras coexistían orientaciones estilísticas contrastadas —desde la tradición tardobarroca hasta el neoclasicismo y el romanticismo— dentro del mismo marco institucional.",
1705            bibliography: [
1706              "Ester Alba Pagán, 'Imagen y poder. La pintura valenciana en la época de Fernando VII (1814-1833)de la alegoría a la pintura conmemorativa', <i>Arte, poder y sociedad en la España de los siglos XV a XX</i>, 2008, pp.337-54.",
1707              "Virginia Albarrán Martín, 'Pintura de tema religioso de Agustín Esteve', in Alejandro Cañestro Donoso, ed. <em>Scripta artium in honorem prof. José Manuel Cruz Valdovinos</em>, 1, Madrid, pp. 297-318.",
1708              "Virginia Albarrán Martín, 'De Goyas que son Esteves', in <em>Goya: Collecting the Grotesque</em>, upcoming.",
1709              "Manuela B. Mena Marqués, Gudrun Maurer (coord.), Virginia Albarrán Martín, <i>Goya y la corte ilustrada</i>, 2017.",
1710              "Jesusa Vega, 'Fernando VII: resistencia y deseo', <i>Journal of Spanish Cultural Studies</i>, 2013, vol.14, no.4, pp.348-99."
1711            ],
1712            websites: [
1713              "[Colección de Carlos III: José Manuel de la Mano, Museo del Prado](https://www.museodelprado.es/aprende/enciclopedia/voz/coleccion-de-carlos-iii/4d51e705-b17d-4836-8745-54569c9e7b30).",
1714              "[Descubrir el Arte: El poderoso legado de Carlos III](https://www.descubrirelarte.es/2016/12/30/el-poderoso-legado-de-carlos-iii.html).",
1715              "[Colección de Carlos IV: Luis Miguel Enciso Recio, Museo del Prado](https://www.museodelprado.es/aprende/enciclopedia/voz/coleccion-de-carlos-iv/f1094cbd-7bfb-4b99-97fa-04769b0a3954).",
1716              "[Más de Arte: Carlos IV, mecenas y coleccionista](https://masdearte.com/carlos-iv-mecenas-y-coleccionista/).",
1717              "[Carlos IV, por José de Madrazo](https://www.galeriadelascoleccionesreales.es/obra-de-arte/carlos-iv/726318d0-2f71-4c14-9394-681e52e2a480), Galería de las Colecciones Reales.",
1718              "[Colección de Fernando VII: Ángel Aterido, Museo del Prado](https://www.museodelprado.es/aprende/enciclopedia/voz/coleccion-de-fernando-vii/1fd09a07-e3e0-4bca-945f-1506b871fc2b)."
1719            ],
1720            conferences: [
1721              "[<i>El arte en tiempo de Carlos III, IV Jornadas de Arte</i>](https://dialnet.unirioja.es/servlet/libro?codigo=509232), 1989.",
1722              "[<i>Carlos IV y las artes. El rey coleccionista</i>](https://www.patrimonionacional.es/sites/default/files/carlos_iv_y_las_artes.pdf), Universidad Complutense / Patrimonio Nacional, 2019."
1723            ]
1724          },
1725
1726          "PRINT_SPECIALISTS": {
1727            members: [
1728              "Eugène Piot",
1729              "Philippe Burty",
1730              "Paul Lefort",
1731              "Charles Blanc",
1732              "Paul Mantz",
1733              "Jules Jacquemart",
1734              "Alphonse Hirsch"
1735            ],
1736            radius: 100,
1737            padding: 80,
1738            startAngle: -Math.PI / 2,
1739            title: "Print specialists",
1740            titleEs: "Especialistas en estampa",
1741            origin: "A group of French printmakers, editors, critics, and collectors who played a decisive role in the nineteenth-century reception of Francisco de Goya. Active from the 1830s onwards, they were among the earliest scholarly interpreters of his graphic work, combining connoisseurship, collecting practices, and art criticism. Their writings and collections contributed to the first systematic studies and cataloguing efforts devoted to Goya's oeuvre.",
1742            originEs: "Un grupo de grabadores, editores, críticos y coleccionistas franceses que desempeñaron un papel decisivo en la recepción de Francisco de Goya en el siglo XIX. Activos desde la década de 1830, fueron algunos de los primeros intérpretes eruditos de su obra gráfica, combinando el conocimiento experto, el coleccionismo y la crítica de arte. Sus escritos y colecciones contribuyeron a los primeros intentos sistemáticos de estudio y catalogación de la obra de Goya.",
1743            bibliography: [],
1744            websites: []
1745          },
1746
1747          "MADRAZO_CARDERERA_GROUP": {
1748            members: [
1749              "Federico de Madrazo",
1750              "Pedro de Madrazo",
1751              "Valentín Carderera",
1752              "Eugenio Ochoa",
1753              "Santiago Masarnau",
1754              "Vicente Masarnau",
1755              "Carlos Luis de Ribera"
1756            ],
1757            radius: 135,
1758            padding: 84,
1759            centerYOffset: -280,
1760            startAngle: -Math.PI / 2,
1761            sharedBoundaryNodes: {
1762              "Federico de Madrazo": Math.PI / 2,
1763              "Pedro de Madrazo": Math.PI / 2
1764            },
1765            title: "Madrazo-Carderera group",
1766            titleEs: "Grupo Madrazo-Carderera",
1767            origin: "A group of Spanish artists, writers, and intellectuals of the same generation, linked by long-standing friendships formed in youth and sustained through shared artistic and cultural interests. Including members of the Madrazo family, they maintained close connections with Parisian cultural circles, where several of them lived and engaged with French Romanticism. They also played an important role in the dissemination and critical reception of Francisco de Goya's work in both Spain and France.",
1768            originEs: "Un grupo de artistas, escritores e intelectuales españoles de la misma generación, unidos por amistades de larga duración formadas en la juventud y mantenidas a través de intereses artísticos y culturales compartidos. Incluye miembros de la familia Madrazo y mantiene vínculos estrechos con los círculos culturales parisinos, donde varios de ellos residieron y entraron en contacto con el Romanticismo francés. También desempeñaron un papel importante en la difusión y recepción crítica de la obra de Francisco de Goya tanto en España como en Francia.",
1769            bibliography: [
1770              "Amaya Alzaga, 'Federico de Madrazo en París (1837-1839): su mirada sobre la pintura francesa de su tiempo y viceversa', in <em>Federico de Madrazo y Carlos Luis de Ribera. Pintores Del Romanticismo Español</em>, 2018, pp.8-28.",
1771              "Robert Avrett, 'Ochoa's Translation of Hugo's Hernani: A Study in Racial Psychologies' <em>The Modern Language Journal</em>, Vol. 38 no. 7, Nov 1954, pp.362-65.",
1772              "Paula Fayos Pérez, 'Valentín Carderera and the Dissemination of Goya's Graphic Work in France', <em>The Burlington Magazine</em>, vol. 162, no. 1413, 2020, pp. 1048-55.",
1773              "José María Lanzarote Guiral, <em>Diarios de viaje de Valentín Carderera por Europa (1841-1861). París, Londres, Bélgica y Alemania</em>, 2016.",
1774              "Eugenio de Ochoa, <em>Paris, Londres y Madrid, Paris</em>, Dramard Baudry, 1861.",
1775              "Mª Rosario Ozaeta, 'Eugenio de Ochoa, Traductor de Hugo', in <em>Neoclásicos y románticos ante la traducción</em>, edited by Francisco Lafarga et al, Universidad de Murcia, 2002, pp.419-36.",
1776              "Roberto Dengler Gassin, 'Algunas consideraciones a propósito de Hernani, drama de Victor Hugo (1830), versión castellana de Eugenio de Ochoa (1836)', in ed. Donaire & Lafarga, <em>Traducción y adaptación cultural: España-Francia</em>, 1991, pp.337-345."
1777            ],
1778            websites: []
1779          },
1780
1781          "BOURBON_CORE": {
1782            members: [
1783              "Carlos III",
1784              "Carlos IV",
1785              "María Luisa de Parma",
1786              "Fernando VII",
1787              "Infanta Luisa Fernanda de Borbón",
1788              "Luis de Borbón",
1789              "María Teresa de Vallabriga",
1790              "XV Countess of Chinchón",
1791              "Isabel II",
1792              "María Cristina de Borbón-Dos Sicilias"
1793            ],
1794            radius: 150,
1795            padding: 120,
1796            startAngle: -Math.PI / 2,
1797            sharedBoundaryNodes: {
1798              "XV Countess of Chinchón": 0
1799            },
1800            title: "Bourbon cluster",
1801            titleEs: "Clúster Borbón",
1802            origin: "A core group of Spanish Bourbon royalty spanning the late eighteenth and nineteenth centuries. Centered on the royal family of Carlos III and his descendants, this network includes successive monarchs and consorts who shaped court politics, patronage systems, and the commissioning and circulation of Francisco de Goya's work within royal and dynastic contexts.",
1803            originEs: "Un núcleo de la realeza borbónica española que abarca finales del siglo XVIII y el siglo XIX. Centrado en la familia de Carlos III y sus descendientes, este grupo incluye a monarcas y consortes sucesivos que configuraron la política de corte, los sistemas de patronazgo y el encargo y circulación de la obra de Francisco de Goya en contextos dinásticos y cortesanos.",
1804            bibliography: [
1805              "Manuela B. Mena Marqués, <i>Teresa de Vallabriga, infanta de España: Francisco de Goya y la familia Borbón-Vallabriga</i>, Magdalena Lasala (coord.), 2018.",
1806              "Manuela B. Mena Marqués, 'El Retrato de Don Luis María de Borbón y Vallabriga niño, estudiando geografía', <i>Luis María de Borbón. Francisco de Goya</i>, 2007, pp.22-42."
1807            ],
1808            websites: []
1809          },
1810
1811         "VILLAFRANCA_CLUSTER": {
1812            members: [
1813              "Francisco Álvarez de Toledo, XII Marquis of Villafranca",
1814              "María Antonia Gonzaga, Marchioness of Villafranca (widow)",
1815              "María Tomasa Palafox, Marchioness of Villafranca",
1816              "José Álvarez de Toledo, Duke of Alba",
1817              "María Teresa de Silva, XIII Duchess of Alba"
1818            ],
1819            radius: 78,
1820            padding: 84,
1821            startAngle: -Math.PI / 2,
1822            title: "Villafranca-Alba cluster",
1823            titleEs: "Clúster Villafranca-Alba",
1824            origin: "A group of Spanish aristocrats connected through the House of Villafranca and the House of Alba. Linked by family ties, marriage alliances, and noble inheritance, they formed part of one of the most influential aristocratic networks in Spain during the late eighteenth and early nineteenth centuries, and were significant patrons and collectors of Francisco de Goya.",
1825            originEs: "Un grupo de aristócratas españoles vinculados a las casas de Villafranca y Alba. Unidos por lazos familiares, alianzas matrimoniales y herencias nobiliarias, formaron parte de una de las redes aristocráticas más influyentes de España a finales del siglo XVIII y comienzos del XIX, y fueron importantes mecen
1825as y coleccionistas de Francisco de Goya.",
1826            bibliography: [
1827              "Manuela B. Mena Marqués & Gudrun Maurer, <i>La duquesa de Alba, musa de Goya: el mito y la historia</i>, 2006."
1828            ],
1829            websites: []
1830          },
1831
1832          "TAYLOR_CLUSTER": {
1833            members: [
1834              "Baron Taylor",
1835              "Adrien Dauzats",
1836              "Pharamond Blanchard"
1837            ],
1838            radius: 78,
1839            padding: 84,
1840            startAngle: -Math.PI / 2,
1841            title: "Taylor cluster",
1842            titleEs: "Clúster Taylor",
1843            origin: "A group of artists and cultural intermediaries closely associated with the French discovery of Spain during the nineteenth century. Through their travels, artistic collaborations, and collecting activities, they helped shape French perceptions of Spanish culture and played a significant role in the acquisition of Spanish artworks for Louis Philippe I's Galerie espagnole, maintaining connections with figures such as Javier Goya and the Madrazo family.",
1844            originEs: "Un grupo de artistas, viajeros e intermediarios culturales que contribuyeron al creciente interés francés por España durante el siglo XIX. Estrechamente implicados en la formación de la Galerie espagnole de Louis Philippe I, promovieron el arte y la cultura españoles a través de los viajes, el coleccionismo y el intercambio artístico, al tiempo que mantuvieron importantes vínculos con Javier Goya y la familia Madrazo.",
1845            bibliography: [
1846              "Claude Schopp (ed.), <em>Frères d'armes de la révolution romantique. Lettres d'Alexandre Dumas au Baron Taylor et à Adrien Dauzats</em>, Cahiers Alexandre Dumas, 1993, no. 20 (reedited in 2023).",
1847              "Paul Guinard, <em>Dauzats et Blanchard: Peintres de l'Espagne Romantique</em>, 1967.",
1848              "Ilse Hempel Lipschutz, <em>Spanish Painting and the French Romantics</em>, Harvard University Press, 1972.",
1849              "Alisa Luxenberg, <em>The Galerie Espagnole and the Museo Nacional, 1835-1853: Saving Spanish Art, or the Politics of Patrimony</em>, 2008.",
1850              "Gary Tinterow et al. <em>Manet/Velázquez: The French Taste for Spanish Painting</em>, New York, Metropolitan Museum of Art, 2003."
1851            ],
1852            websites: []
1853          },
1854
1855          "DAVID_CLUSTER": {
1856            members: [
1857              "Jacques-Louis David",
1858              "Jean-Auguste-Dominique Ingres",
1859              "Antoine-Jean Gros",
1860              "Paul Delaroche"
1861            ],
1862            radius: 78,
1863            padding: 84,
1864            startAngle: -Math.PI / 2,
1865            title: "David cluster",
1866            titleEs: "Clúster David",
1867            origin: "A group of painters linked to the artistic legacy of Jacques-Louis David, whose influence extended from Neoclassicism to the broader academic tradition of the nineteenth century. Through their historical paintings, classical ideals, and teaching networks, they played a central role in shaping European academic art.",
1868            originEs: "Un grupo de pintores vinculados al legado artístico de Jacques-Louis David, cuya influencia se extendió desde el Neoclasicismo hasta la tradición académica más amplia del siglo XIX. A través de sus pinturas de historia, ideales clásicos y redes de enseñanza, desempeñaron un papel central en la configuración del arte académico europeo.",
1869            bibliography: [],
1870            websites: []
1871          },
1872
1873          "IMPRESSIONIST_CLUSTER": {
1874            members: [
1875              "Félix Bracquemond",
1876              "Edgar Degas",
1877              "Henri Fantin-Latour",
1878              "Édouard Manet",
1879              "Berthe Morisot",
1880              "Auguste Renoir"
1881            ],
1882            radius: 78,
1883            padding: 84,
1884            startAngle: -Math.PI / 2,
1885            title: "Impressionist cluster",
1886            titleEs: "Clúster Impresionista",
1887            origin: "French artistic movement originating in the 1860s, dedicated to capturing the ephemeral light of a specific instant and its effect on colors and forms. Each artist expressed their own subjective vision, resulting in diverse and highly individual styles rather than a unified aesthetic. The movement was formally introduced through the first independent exhibition held in Paris in 1874.",
1888            originEs: "Movimiento artístico francés que se originó en la década de 1860, dedicado a capturar la luz efímera de un instante específico y su efecto en los colores y las formas. Cada artista expresó su propia visión subjetiva, resultando en estilos diversos e individuales más que en una estética unificada. El movimiento fue presentado formalmente a través de la primera exposición independiente celebrada en París en 1874.",
1889            bibliography: [
1890              "Françoise Cachin, Charles S. Moffett & Juliet Wilson-Bareau, <em>Manet: 1832-1883</em>, Paris, Réunion des Musées Nationaux, 1983.",
1891              "Dorothee Hansen, <em>Manet and Astruc. Friendship and Inspiration</em>, CEEH, 2021.",
1892              "Sophie Monneret, <em>L'Impressionnisme et son époque</em>, vol.2, t.1, Paris, Robert Laffont, 1987.",
1893              "Adolphe Tabarant, <em>Manet et ses œuvres</em>, Paris, Gallimard, 1947 (1st ed. 1897).",
1894              "Gary Tinterow et al. <em>Manet/Velázquez: The French Taste for Spanish Painting</em>, New York, Metropolitan Museum of Art, 2003.",
1895              "Juliet Wilson-Bareau, <em>Manet: The Execution of Maximilian: Painting, Politics and Censorship</em>, Princeton (New Jersey), Princeton University Press, 1992.",
1896              "Stephan Wolohojian and Ashley Dunn, <em>Manet/Degas</em>, The Metropolitan Museum of Art, 2023."
1897            ],
1898            websites: [
1899              "[Networks of Impressionism](https://www.navigating.art/articles-from-navigatingart/networks-of-impressionism): An upcoming collaborative digital endeavor from the Museum Barberini (Postdam, Germany), the Wildenstein Plattner Institute and Navigating.art."
1900            ]
1901          },
1902
1903          "SPANISH_ROMANTICS_CLUSTER": {
1904            members: [
1905              "José de Espronceda",
1906              "Mariano José Larra",
1907              "José Zorrilla",
1908              "Jenaro Pérez Villaamil",
1909              "Ramón de Mesonero Romanos",
1910              "Carlos Luis de Ribera",
1911              "Eugenio Lucas Velázquez"
1912            ],
1913            radius: 100,
1914            padding: 90,
1915            startAngle: -Math.PI / 2,
1916            title: "Spanish Romantics",
1917            titleEs: "Románticos españoles",
1918            origin: "A heterogeneous group of Spanish writers, painters, and critics of the same generation, united by shared aesthetic and political sensibilities representing key figures in Spain's Romantic movement of the 19th century. Encompassing both literary figures and visual artists, they were committed to expressive, emotional forms and liberal ideals. Many maintained connections to Parisian Romantic circles and contributed to Spain's cultural modernization through art, theater, literature, and social commentary.",
1919            originEs: "Un grupo heterogéneo de escritores, pintores y críticos españoles de la misma generación, unidos por una sensibilidad estética y política común, figuras clave del movimiento romántico español del siglo XIX. Integrando tanto figuras literarias como artistas visuales, se comprometieron con formas expresivas y emocionales, así como con ideales liberales. Muchos mantuvieron conexiones con los círculos románticos parisinos y contribuyeron a la modernización cultural de España a través del arte, el teatro, la literatura y la crítica social.",
1920            bibliography: [
1921              "Donald E. Schurlknight, <i>Spanish Romanticism in Context: Of Subversion, Contradiction and Politics: Espronceda, Larra, Rivas, Zorrilla</i>, 1998.",
1922              "Begoña Torres González, 'La Hermandad Romántica: Larra y las artes plásticas', in <i>Larra: Fígaro de vuelta, 1809-2009</i>, 2009, pp.183-203."
1923            ],
1924            websites: []
1925          },
1926
1927          "BORDEAUX_PARIS_CIRCLE": {
1928            members: [
1929              "Leandro Fernández de Moratín",
1930              "Joaquín María Ferrer",
1931              "Juan Antonio Melón",
1932              "Antonio de Brugada",
1933              "Pierre Lacour",
1934              "Pierre Lacour fils",
1935              "Rosario Weiss Zorrilla",
1936              "Leocadia Zorrilla y Galarza",
1937              "Cyprien Gaulon",
1938              "Jacques Galos",
1939              "Carle Vernet"
1940            ],
1941            radius: 160,
1942            padding: 110,
1943            centerYOffset: 260,
1944            startAngle: -Math.PI / 2,
1945            sharedBoundaryNodes: {
1946              "Leandro Fernández de Moratín": Math.PI / 2,
1947              "Rosario Weiss Zorrilla": Math.PI / 2
1948            },
1949            title: "Bordeaux-Paris Goya circle",
1950            titleEs: "Círculo Goya Burdeos-París",
1951            origin: "A transnational network of Spanish exiles, artists, and intellectuals centered on Francisco de Goya's final years in Bordeaux (1824-1828) and their connections to Paris. United by political liberalism, artistic commitment, and shared displacement from Spain, this group encompassed writers, painters, teachers, and family members connected through kinship, intellectual affinity, and the circumstance of exile.",
1952            originEs: "Un entramado transnacional de exiliados españoles, artistas e intelectuales centrado en los últimos años de Francisco de Goya en Burdeos (1824-1828) y sus conexiones con París. Unidos por liberalismo político, compromiso artístico y desplazamiento compartido desde España, este grupo abarcaba escritores, pintores, maestros y miembros de la familia conectados a través del parentesco, la afinidad intelectual y la circunstancia del exilio.",
1953            websites: []
1954          },
1955
1956          "GOYA_HISTORIANS_CRITICS": {
1957            members: [
1958              "Charles Yriarte",
1959              "Eugène Piot",
1960              "Paul Lefort",
1961              "Valentín Carderera",
1962              "Théophile Gautier",
1963              "Théophile Thoré",
1964              "Gustave Brunet",
1965              "Laurent Matheron"
1966            ],
1967            radius: 150,
1968            padding: 100,
1969            centerYOffset: -140,
1970            startAngle: -Math.PI / 2,
1971            sharedBoundaryNodes: {
1972              "Charles Yriarte": Math.PI / 2,
1973              "Valentín Carderera": Math.PI / 2
1974            },
1975            title: "Goya historians & critics",
1976            titleEs: "Historiadores y críticos de Goya",
1977            origin: "A diverse group of nineteenth-century art historians, critics, writers, and collectors who played a decisive role in the critical interpretation, scholarship, and dissemination of Francisco de Goya's work. Active primarily between the 1830s and 1890s, they combined connoisseurship with historical documentation, contributing to the first systematic cataloguing and critical analyses of his oeuvre. Their writings, collections, and curatorial practices helped establish Goya as a central figure in European art history, bridging Spanish and French intellectual contexts.",
1978            originEs: "Un grupo diverso de historiadores del arte, críticos, escritores y coleccionistas del siglo XIX que desempeñaron un papel decisivo en la interpretación crítica, la erudición y la difusión de la obra de Francisco de Goya. Activos principalmente entre la década de 1830 y 1890, combinaron el conocimiento experto con la documentación histórica, contribuyendo a los primeros análisis sistemáticos y críticos de su obra. Sus escritos, colecciones y prácticas curatoriales ayudaron a establecer a Goya como figura central en la historia del arte europeo, vinculando contextos intelectuales españoles y franceses.",
1979            bibliography: [
1980              "Nigel Glendinning, <i>Goya and his Critics</i>
1980, 1977 (new edition, ed. Vega, 2017).",
1981              "Paula Fayos Pérez, <i>Goya's Caprichos in Nineteenth-Century France. Politics of the Grotesque</i>, 2024."
1982            ],
1983            websites: []
1984          }
1985        };
1986
1987        // Map clusterId to color
1988        const clusterColorMap = {};
1989        let clusterColorIdx = 0;
1990        Object.keys(RADIAL_CLUSTERS).forEach(cid => {
1991          clusterColorMap[cid] = CLUSTER_COLORS[clusterColorIdx % CLUSTER_COLORS.length];
1992          clusterColorIdx++;
1993        });
1994
1995        clusterColorMap["GOYA_FAMILY"] = "#64b5f6";      // azul
1996        clusterColorMap["SPANISH_COURT_PAINTERS"] = "#ffb74d";   // naranja
1997
1998        // Reverse map: nodeId -> [clusterId1, clusterId2, ...]
1999        const nodeClusterMap = {};
2000        Object.entries(RADIAL_CLUSTERS).forEach(([cid, cfg]) => {
2001          if (cfg.center) {
2002            if (!nodeClusterMap[cfg.center]) nodeClusterMap[cfg.center] = [];
2003            nodeClusterMap[cfg.center].push(cid);
2004          }
2005          (cfg.members || []).forEach(id => {
2006            if (!nodeClusterMap[id]) nodeClusterMap[id] = [];
2007            nodeClusterMap[id].push(cid);
2008          });
2009        });
2010
2011        let clusterEdgeColoringEnabled = true;
2012
2013        const getClusterIdForNode = nodeId => nodeClusterMap[nodeId] || [];
2014        const getClusterEdgeStatus = (fromId, toId) => {
2015          const fromClusters = getClusterIdForNode(fromId);
2016          const toClusters = getClusterIdForNode(toId);
2017          if (!fromClusters.length || !toClusters.length) return 'unclassified';
2018          return fromClusters.some(c => toClusters.includes(c)) ? 'intra-cluster' : 'inter-cluster';
2019        };
2020
2021        const hexToRgba = (hex, alpha) => {
2022          if (!hex) return `rgba(100,100,100,${alpha})`;
2023          const shorthand = /^#([a-f\d])([a-f\d])([a-f\d])$/i;
2024          const fullHex = hex.replace(shorthand, (m, r, g, b) => `${r}${r}${g}${g}${b}${b}`);
2025          const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(fullHex);
2026          if (!match) return `rgba(100,100,100,${alpha})`;
2027          return `rgba(${parseInt(match[1], 16)}, ${parseInt(match[2], 16)}, ${parseInt(match[3], 16)}, ${alpha})`;
2028        };
2029
2030        const getEdgeStyle = (edge) => {
2031          const level = edge.connection_level || 'direct';
2032          const edgeStatus = getClusterEdgeStatus(edge.from, edge.to);
2033          
2034          // Calculate width based on strength (1-5 scale)
2035          // If no strength specified, use default based on level
2036          let strengthWidth = 1;
2037          if (edge.strength) {
2038            // Map strength 1-5 to width 1.5-4.5 (more dramatic variation)
2039            strengthWidth = 1.5 + (edge.strength - 1) * 0.75;
2040          }
2041          
2042          if (clusterEdgeColoringEnabled && edgeStatus === 'intra-cluster') {
2043            const fromClusters = getClusterIdForNode(edge.from);
2044            const toClusters = getClusterIdForNode(edge.to);
2045            const commonCluster = fromClusters.find(c => toClusters.includes(c));
2046            const clusterColor = clusterColorMap[commonCluster] || '#646464';
2047            const baseWidth = level === 'secondary' ? 1.4 : 1.8;
2048            return {
2049              color: hexToRgba(clusterColor, level === 'secondary' ? 0.45 : 0.8),
2050              width: edge.strength ? strengthWidth : baseWidth
2051            };
2052          }
2053          const baseWidth = level === 'secondary' ? 1.2 : 1.5;
2054          return {
2055            color: level === 'secondary' ? 'rgba(150,150,150,0.15)' : 'rgba(200,200,200,0.25)',
2056            width: edge.strength ? strengthWidth : baseWidth
2057          };
2058        };
2059
2060        window.toggleClusterEdgeColoring = function () {
2061          clusterEdgeColoringEnabled = !clusterEdgeColoringEnabled;
2062          const button = document.getElementById('clusterToggleOption');
2063          if (button) {
2064            button.textContent = clusterEdgeColoringEnabled ? t('BTN_CLUSTER_TOGGLE_ON') : t('BTN_CLUSTER_TOGGLE_OFF');
2065          }
2066          edges.update(edges.get().map(edge => {
2067            const style = getEdgeStyle(edge);
2068            return {
2069              id: edge.id,
2070              color: { color: style.color },
2071              width: style.width
2072            };
2073          }));
2074        };
2075
2076        let selectedClusterId = null;
2077
2078        const getClusterDisplayName = clusterId => {
2079          const cfg = RADIAL_CLUSTERS[clusterId];
2080          if (!cfg) return clusterId || '';
2081          return (CURRENT_LANG === 'es' ? cfg.titleEs : cfg.title) || clusterId;
2082        };
2083
2084        const updateClusterInfoBadge = () => {
2085          const badge = document.getElementById('clusterInfoBadge');
2086          if (!badge) return;
2087          badge.textContent = '';
2088        };
2089
2090        window.clearClusterSelection = function () {
2091          selectedClusterId = null;
2092          const option = document.getElementById('clusterSelect');
2093          if (option) option.value = '';
2094          nodes.update(nodes.get().map(node => ({
2095            id: node.id,
2096            opacity: 1,
2097            borderWidth: 2
2098          })));
2099          edges.update(edges.get().map(edge => {
2100            const style = getEdgeStyle(edge);
2101            return {
2102              id: edge.id,
2103              color: { color: style.color },
2104              width: style.width
2105            };
2106          }));
2107          updateClusterInfoBadge();
2108          if (typeof window.showDefaultNodeInfo === 'function') {
2109            window.showDefaultNodeInfo();
2110          }
2111        };
2112
2113        window.selectCluster = function (clusterId) {
2114          if (!clusterId) {
2115            window.clearClusterSelection();
2116            return;
2117          }
2118
2119          const cfg = RADIAL_CLUSTERS[clusterId];
2120          if (!cfg) {
2121            window.clearClusterSelection();
2122            return;
2123          }
2124
2125          selectedClusterId = clusterId;
2126          const members = new Set(Object.keys(nodeClusterMap).filter(id => nodeClusterMap[id].includes(clusterId)));
2127          const clusterColor = clusterColorMap[clusterId] || '#ffffff';
2128          const highlightColor = hexToRgba(clusterColor, 0.85);
2129
2130          // Actually select the nodes in vis.js so they can be dragged together
2131          network.selectNodes(Array.from(members));
2132
2133          nodes.update(nodes.get().map(node => ({
2134            id: node.id,
2135            opacity: members.has(node.id) ? 1 : 0.25,
2136            borderWidth: members.has(node.id) ? 4 : 1
2137          })));
2138
2139          edges.update(edges.get().map(edge => {
2140            const edgeStatus = getClusterEdgeStatus(edge.from, edge.to);
2141            if (edgeStatus === 'intra-cluster' && nodeClusterMap[edge.from].includes(clusterId) && nodeClusterMap[edge.to].includes(clusterId)) {
2142              return {
2143                id: edge.id,
2144                color: { color: highlightColor },
2145                width: 2.4
2146              };
2147            }
2148            return {
2149              id: edge.id,
2150              color: { color: 'rgba(200,200,200,0.08)' },
2151              width: 1
2152            };
2153          }));
2154          updateClusterInfoBadge();
2155
2156          // Focus on cluster without glitching
2157          const memberArray = Array.from(members);
2158          if (memberArray.length > 0 && window.VIS_NETWORK) {
2159            try {
2160              const pos = window.VIS_NETWORK.getPositions(memberArray);
2161              // Calculate cluster center and bounds
2162              let cx = 0, cy = 0, minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
2163              memberArray.forEach(id => {
2164                cx += pos[id].x;
2165                cy += pos[id].y;
2166                minX = Math.min(minX, pos[id].x);
2167                maxX = Math.max(maxX, pos[id].x);
2168                minY = Math.min(minY, pos[id].y);
2169                maxY = Math.max(maxY, pos[id].y);
2170              });
2171              cx /= memberArray.length;
2172              cy /= memberArray.length;
2173
2174              // Calculate appropriate scale based on cluster size
2175              const width = maxX - minX || 100;
2176              const height = maxY - minY || 100;
2177              const maxDim = Math.max(width, height);
2178              const scale = Math.min(1.5, 800 / (maxDim + 200)); // Fit with padding, max 1.5x zoom
2179
2180              // Move immediately without animation to avoid glitching
2181              window.VIS_NETWORK.moveTo({
2182                position: { x: cx, y: cy },
2183                scale: scale
2184              });
2185            } catch (e) {
2186              console.warn('Error focusing on cluster:', e);
2187            }
2188          }
2189
2190          // Render cluster info in the right panel
2191          const nodeInfoEl = document.getElementById('nodeInfo');
2192          if (nodeInfoEl) {
2193            const clusterTitle = getClusterDisplayName(clusterId);
2194            const sortedMembers = Array.from(members).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
2195            const isEs = CURRENT_LANG === 'es';
2196
2197            let html = `<div class="cluster-info-panel">`;
2198            html += `<h3 style="margin:0 0 1rem 0; font-size:1.1rem; color:#fff; border-bottom:1px solid #444; padding-bottom:0.5rem;">${clusterTitle}</h3>`;
2199
2200            // Members section
2201            html += `<div class="section-heading" style="margin-bottom:0.4rem;">${isEs ? 'Miembros' : 'Members'}</div>`;
2202            html += `<ul style="margin:0 0 1rem 0; padding-left:1.2rem; line-height:1.8;">`;
2203            sortedMembers.forEach(id => {
2204              const safeId = id.replace(/'/g, "\\'");
2205              html += `<li><a href="#" style="color:#66ccff;" onclick="focusNode('${safeId}'); return false;">${id}</a></li>`;
2206            });
2207            html += `</ul>`;
2208
2209            // Origin section
2210            const originText = isEs ? (cfg.originEs || cfg.origin) : cfg.origin;
2211            if (originText) {
2212              const linkedOriginText = autoLinkNames(originText, nodesMap);
2213              html += `<div class="section-heading" style="margin-bottom:0.4rem;">${isEs ? 'Contexto' : 'Context'}</div>`;
2214              html += `<p style="margin:0 0 1rem 0; color:#ccc; line-height:1.5;">${linkedOriginText}</p>`;
2215            }
2216
2217            // Bibliography section
2218            if (cfg.bibliography && cfg.bibliography.length > 0) {
2219              html += `<div class="section-heading" style="margin-bottom:0.4rem;">${isEs ? 'Bibliografía' : 'Bibliography'}</div>`;
2220              html += `<ul style="margin:0 0 1rem 0; padding-left:1.2rem; line-height:1.8; color:#ccc;">`;
2221              cfg.bibliography.forEach(entry => {
2222                html += `<li>${entry}</li>`;
2223              });
2224              html += `</ul>`;
2225            }
2226
2227            // Websites section
2228            if (cfg.websites && cfg.websites.length > 0) {
2229              html += `<div class="section-heading" style="margin-bottom:0.4rem;">${isEs ? 'Sitios web' : 'Websites'}</div>`;
2230              html += `<ul style="margin:0 0 1rem 0; padding-left:1.2rem; line-height:1.8;">`;
2231              cfg.websites.forEach(site => {
2232                let label, url, description = '';
2233                if (typeof site === 'string') {
2234                  // Parse markdown link format: [label](url) optionally followed by description
2235                  const match = site.match(/\[(.*?)\]\((.*?)\)(.*)/);
2236                  if (match) {
2237                    label = match[1];
2238                    url = match[2];
2239                    description = match[3].trim();
2240                    // Remove leading colon if present
2241                    if (description.startsWith(':')) {
2242                      description = description.substring(1).trim();
2243                    }
2244                  } else {
2245                    label = site;
2246                    url = site;
2247                  }
2248                } else {
2249                  // Object format: {label, url, description}
2250                  label = site.label;
2251                  url = site.url;
2252                  description = site.description || '';
2253                }
2254                html += `<li><a href="${url}" target="_blank" rel="noopener noreferrer" style="color:#66ccff;">${label}</a>`;
2255                if (description) {
2256                  html += `<br><span style="color:#999; font-size:0.9rem; margin-left:1rem; display:block; margin-top:0.2rem;">${description}</span>`;
2257                }
2258                html += `</li>`;
2259              });
2260              html += `</ul>`;
2261            }
2262
2263            // Conferences section
2264            if (cfg.conferences && cfg.conferences.length > 0) {
2265              html += `<div class="section-heading" style="margin-bottom:0.4rem;">${isEs ? 'Conferencias' : 'Conferences'}</div>`;
2266              html += `<ul style="margin:0 0 1rem 0; padding-left:1.2rem; line-height:1.8; color:#ccc;">`;
2267              cfg.conferences.forEach(entry => {
2268                // Parse markdown links [label](url) - only show label without brackets, hide URL
2269                let linkedEntry = entry
2270                  .replace(/\[(.*?)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer" style="color:#66ccff;">$1</a>');
2271                html += `<li>${linkedEntry}</li>`;
2272              });
2273              html += `</ul>`;
2274            }
2275
2276            html += `</div>`;
2277            nodeInfoEl.innerHTML = html;
2278          }
2279        };
2280
2281        const clusterSelect = document.getElementById('clusterSelect');
2282        if (clusterSelect) {
2283          const sortedClusterEntries = Object.entries(RADIAL_CLUSTERS)
2284            .sort((a, b) => {
2285              const nameA = getClusterDisplayName(a[0]);
2286              const nameB = getClusterDisplayName(b[0]);
2287              return nameA.localeCompare(nameB, undefined, { sensitivity: 'base' });
2288            });
2289
2290          clusterSelect.innerHTML = `<option value="">${t('SELECT_CLUSTER_OPTION')}</option>` +
2291            sortedClusterEntries.map(([cid]) => {
2292              const name = getClusterDisplayName(cid);
2293              return `<option value="${cid}">${name}</option>`;
2294            }).join('');
2295
2296          clusterSelect.addEventListener('change', (e) => {
2297            window.selectCluster(e.target.value);
2298          });
2299        }
2300
2301        const initClusterControls = () => {
2302          const button = document.getElementById('clusterToggleButton');
2303          if (button) {
2304            button.textContent = clusterEdgeColoringEnabled ? t('BTN_CLUSTER_TOGGLE_ON') : t('BTN_CLUSTER_TOGGLE_OFF');
2305          }
2306          updateClusterInfoBadge();
2307        };
2308
2309        initClusterControls();
2310
2311        // Ajustes de nodos con cluster color
2312        // ✅ NUEVO CÓDIGO (aplica posiciones)
2313        nodes = new vis.DataSet(data.nodes.map(node => {
2314          const degree = edgeCount[node.id] || 1;
2315          const clusterIds = nodeClusterMap[node.id];
2316          const clusterColor = clusterIds && clusterIds.length > 0 ? clusterColorMap[clusterIds[0]] : undefined;
2317          
2318          // 🔥 APLICAR POSICIONES DESDE positions_config.json
2319          const pos = nodePositions[node.id] || { x: undefined, y: undefined };
2320          
2321          const config = {
2322            ...node,
2323            x: pos.x,  // <-- AÑADIR ESTO
2324            y: pos.y,  // <-- AÑADIR ESTO
2325            size: Math.min(13 + degree * 0.72, 38),
2326            mass: 1 + degree * 0.025,
2327            font: {
2328              size: Math.min(11 + degree * 0.42, 18),
2329              color: '#ffffff',
2330              strokeWidth: 3,
2331              strokeColor: '#111111',
2332              face: 'EB Garamond, serif',
2333              align: 'center',
2334              bold: true,
2335              vadjust: -10
2336            },
2337            color: clusterColor ? { background: clusterColor, border: '#2B7CE9' } : { border: '#2B7CE9' },
2338            borderWidth: 2,
2339            shape: node.image ? 'circularImage' : 'dot',
2340            labelHighlightBold: false,
2341            margin: -10
2342          };
2343          config._imageUrl = node.image;
2344          nodesMap[node.id] = config;
2345          return config;
2346        }));
2347
2348    // Edges más transparentes (general)
2349    edges = new vis.DataSet(data.edges.map(edge => {
2350      const level = edge.connection_level || "direct";
2351      const edgeStatus = getClusterEdgeStatus(edge.from, edge.to);
2352
2353      // Muestra etiqueta solo si es "direct" o "secondary".
2354      // Si termina en "?" (p. ej. "direct?") NO la pintamos y la ponemos como tooltip.
2355      const label =
2356        edge.label === 'direct' || edge.label === 'secondary'
2357          ? edge.label
2358          : undefined;
2359
2360      const title =
2361        edge.label && /\?$/.test(edge.label)
2362          ? edge.label  // aparecerá al pasar el ratón
2363          : edge.title;
2364
2365      const style = getEdgeStyle(edge);
2366      
2367      // Calculate spring length based on strength (1-5 scale)
2368      // Respect strength for BOTH intra and inter-cluster edges
2369      let edgeLength = 210; // default
2370      if (edge.strength) {
2371        const strength = Math.max(1, Math.min(5, edge.strength)); // clamp 1-5
2372        
2373        if (edgeStatus === 'inter-cluster') {
2374          // Inter-cluster edges: use longer base length but STILL apply strength
2375          // Level-1 strength = tighter pull even across clusters (important for key relationships)
2376          edgeLength = 120 + (strength - 1) * 50; // strength 1-5 = 120-320px range (base 200 for inter-cluster)
2377        } else {
2378          // Intra-cluster edges: apply strength normally to pull related nodes together
2379          // Make strength-1 edges extremely tight (20px) for very close relationships
2380          edgeLength = 20 + (strength - 1) * 70; // strength 1-5 = 20-300px range
2381        }
2382      }
2383
2384      const processedEdge = {
2385        ...edge,
2386        label,
2387        title,
2388        color: { color: style.color },
2389        width: style.width,
2390        physics: true,           // ensure physics is enabled
2391        length: edgeLength        // vis.js uses 'length' property for edge spring length
2392      };
2393
2394      return processedEdge;
2395    }));
2396    window.edges = edges;
2397
2398    // ===================== MINI-FAMILIAS (anchors invisibles) =====================
2399    // NOTA: esto NO toca tus edges reales. Añade nodos/edges "fantasma" para compactar grupos.
2400
2401    window.__clusterOf = {}; // nodeId -> clusterId
2402
2403    Object.entries(RADIAL_CLUSTERS).forEach(([clusterId, cfg]) => {
2404      if (cfg.center && nodes.get(cfg.center)) {
2405        if (!window.__clusterOf[cfg.center]) window.__clusterOf[cfg.center] = [];
2406        window.__clusterOf[cfg.center].push(clusterId);
2407      }
2408
2409      (cfg.members || []).forEach(id => {
2410        if (!nodes.get(id)) return;
2411        if (!window.__clusterOf[id]) window.__clusterOf[id] = [];
2412        window.__clusterOf[id].push(clusterId);
2413      });
2414    });
2415
2416    /*
2417    (function addClusterAnchors(nodes, edges, clusters) {
2418      const anchorNodes = [];
2419      const anchorEdges = [];
2420
2421      Object.entries(clusters).forEach(([clusterId, cfg]) => {
2422        const members = (cfg?.members || []).filter(id => nodes.get(id));
2423        const center = cfg?.center && nodes.get(cfg.center) ? cfg.center : null;
2424
2425        if (center) {
2426          window.__clusterOf[center] = clusterId;
2427        }
2428        members.forEach(id => { window.__clusterOf[id] = clusterId; });
2429        if (members.length < 2) return;
2430
2431        const anchorId = `ANCHOR__${clusterId}`;
2432
2433        // Nodo-ancla invisible (no aparece en pantalla)
2434        anchorNodes.push({
2435          id: anchorId,
2436          label: "",
2437          shape: "dot",
2438          size: 1,
2439          mass: 0.01,
2440          clusterAnchor: true,
2441          physics: true,
2442          color: { border: "rgba(0,0,0,0)", background: "rgba(0,0,0,0)" },
2443          hidden: true,
2444          selectable: false
2445        });
2446
2447        const L = typeof cfg.radius === "number" ? cfg.radius : 60;
2448
2449        // Aristas invisibles cortas hacia cada miembro
2450        members.forEach(memberId => {
2451          const pulls = cfg?.pulls ?? 3; // nº de “muelles” paralelos (más = más compacto)
2452          for (let k = 0; k < pulls; k++) {
2453            anchorEdges.push({
2454              id: `AEDGE__${clusterId}__${memberId}__${k}`,
2455              from: anchorId,
2456              to: memberId,
2457              physics: true,
2458              smooth: false,
2459              _isClusterEdge: true,
2460              length: L,
2461              width: 0.1,
2462              selectionWidth: 0,
2463              hoverWidth: 0,
2464              color: { color: "rgba(0,0,0,0)" }
2465            });
2466          }
2467        });
2468      });
2469
2470      if (anchorNodes.length) nodes.add(anchorNodes);
2471      if (anchorEdges.length) edges.add(anchorEdges);
2472    })(nodes, edges, CLUSTERS);
2473    */
2474    // =================== /MINI-FAMILIAS (anchors invisibles) ===================
2475
2476
2477    const locale = (CURRENT_LANG === 'es') ? 'es-ES' : 'en-GB';
2478
2479    const lastUpdate = data.last_update;
2480
2481    const formattedUpdate = lastUpdate
2482      ? new Date(lastUpdate + 'T00:00:00').toLocaleDateString(
2483          locale,
2484          { day: 'numeric', month: 'long', year: 'numeric' }
2485        )
2486      : t('Unknown');
2487
2488    document.getElementById("networkStats").innerHTML =
2489      `${t('Nodes')}: ${data.nodes.length} | ${t('Connections')}: ${data.edges.length}<br>
2490      <span style="font-size: 0.8rem; color: #999;">${window.LAST_UPDATE_LABEL}: ${formattedUpdate}</span>`;
2491
2492
2493    // ===== NEW NODES
2494    function buildNewNodesList(data) {
2495      const container = document.getElementById('newNodesList');
2496      if (!container) return;
2497
2498      const CUTOFF_STR = '2025-04-01';
2499
2500      const items = (data.nodes || [])
2501        .filter(n => typeof n.added === 'string' && n.added >= CUTOFF_STR)
2502        .map(n => ({
2503          id: n.id,
2504          label: (CURRENT_LANG === 'es' && n['display name es']) ? n['display name es'] : (n.label || n.id),
2505          date: n.added
2506        }))
2507        .sort((a, b) => new Date(b.date) - new Date(a.date));
2508
2509      if (!items.length) {
2510        container.innerHTML = `<em>${CURRENT_LANG === 'es'
2511          ? 'No hay nodos añadidos desde abril de 2025.'
2512          : 'No nodes added since April 2025.'}</em>`;
2513        return;
2514      }
2515
2516      container.innerHTML = '';
2517
2518      items.forEach(item => {
2519        const wrapper = document.createElement('div');
2520        wrapper.className = 'new-node-item';
2521
2522        const a = document.createElement('a');
2523        a.href = '#';
2524        a.style.color = '#66ccff';
2525        a.textContent = item.label;
2526
2527        const dateSpan = document.createElement('span');
2528        dateSpan.className = 'new-node-date';
2529
2530        const locale = (CURRENT_LANG === 'es') ? 'es-ES' : 'en-GB';
2531        const dateStr = new Date(item.date + 'T00:00:00')
2532          .toLocaleDateString(locale, { day: '2-digit', month: 'short', year: 'numeric' });
2533
2534        dateSpan.textContent = ` — ${dateStr}`;
2535
2536        a.onclick = (e) => {
2537          e.preventDefault();
2538          if (typeof focusNode === 'function') focusNode(item.id);
2539        };
2540
2541        wrapper.appendChild(a);
2542        wrapper.appendChild(dateSpan);
2543        container.appendChild(wrapper);
2544      });
2545    }
2546
2547    buildNewNodesList(data);
2548
2549    const newNodesToggle = document.getElementById('newNodesToggle');
2550    const newNodesDropdown = document.getElementById('newNodesDropdown');
2551
2552    if (newNodesToggle && newNodesDropdown) {
2553      newNodesToggle.addEventListener('click', function (e) {
2554        e.stopPropagation();
2555        const isOpen = newNodesDropdown.style.display === 'block';
2556        newNodesDropdown.style.display = isOpen ? 'none' : 'block';
2557        newNodesToggle.classList.toggle('open', !isOpen);
2558      });
2559
2560      document.addEventListener('click', function (e) {
2561        if (!document.getElementById('newNodesSection')?.contains(e.target)) {
2562          newNodesDropdown.style.display = 'none';
2563          newNodesToggle.classList.remove('open');
2564        }
2565      });
2566    }
2567
2568    // ---- MEMBERS LIST: manual overrides (edit these if needed) ----
2569    // 1) Para nombres completos donde quieras forzar el apellido clave (usa la grafía exacta)
2570
2571    // Declarar una sola vez (arriba, antes de surnameKey)
2572    const SURNAME_FORCE_BY_FULLNAME = Object.create(null);
2573    const GIVEN_FORCE_TOKENS = new Set();
2574    const SURNAME_FORCE_TOKENS = new Set();
2575
2576    // Rellenar el diccionario/sets (puedes repetir este bloque en otros sitios sin problema)
2577    Object.assign(SURNAME_FORCE_BY_FULLNAME, {
2578      'Weiss Zorrilla': 'weiss',
2579      'Prince Antoine, Duke of Montpensier': 'montpensier',
2580      'Juan Agustín Ceán Bermúdez': 'cean',
2581      'II Duke of San Carlos': 'san carlos',
2582      'X Marchioness of Santa Cruz': 'santa cruz',
2583      'María Antonia Gonzaga, Marchioness of Villafranca (widow)': 'villafranca',
2584      'Berthe Morisot': 'morisot',
2585      'Antoine-Jean Gros': 'gros',
2586      'Francisco Álvarez de Toledo, XII Marquis of Villafranca': 'villafranca',
2587      'José Álvarez de Toledo, Duke of Alba': 'alba',
2588      'María Teresa de Silva, XIII Duchess of Alba': 'alba',
2589      'María Cristina de Borbón-Dos Sicilias': 'borbon',
2590      'Rosario Weiss Zorrilla': 'weiss',
2591      'Isabel II': 'isabel',
2592      'Carlos III': 'carlos',
2593      'Carlos IV': 'carlos',
2594      'Fernando VII': 'fernando',
2595      'Louis Philippe I': 'louis',
2596      'María Tomasa Palafox, Marchioness of Villafranca': 'villafranca',
2597      'María Gabriela de Palafox, Marchioness of Lazán': 'lazan',
2598      'Eugenio Eulalio Palafox, VII Count of Montijo': 'montijo',
2599      'Cipriano Portocarrero, VIII Count of Montijo': 'montijo',
2600      'María Francisca de Sales Portocarrero, VI Countess of Montijo': 'montijo',
2601      'Alexandre Dumas père': 'dumas',
2602      'Francisco Martínez de la Rosa': 'martinez',
2603      'Juan Meléndez Valdés': 'melendez',
2604      'Jenaro Pérez Villaamil': 'perez',
2605      'Antonio Remón Zarco del Valle y Huet': 'remon',
2606      'María de las Mercedes Santa Cruz y Montalvo, Countess of Merlin': 'merlin',
2607      'Ramón de Mesonero Romanos': 'mesonero',
2608      'Gregorio Cruzada Villaamil': 'cruzada',
2609      'Juan Fernández de Rojas': 'fernandez',
2610      'Manuel García de la Prada': 'garcia',
2611      'Dominique Vivant Denon': 'vivant',
2612      'Pierre Lacour fils': 'lacour',
2613      'Madame Ewelina Hanska': 'hanska'
2614    });
2615
2616    window.__GN_SURNAME_FORCE_BY_FULLNAME = SURNAME_FORCE_BY_FULLNAME;
2617
2618    /* ---- MEMBERS LIST: alphabetical index of nodes (A–Z by surname) ---- */
2619    function surnameKey(name) {
2620      if (!name) return '';
2621
2622      // Normaliza a "base": quita paréntesis y corta por coma (para eliminar títulos)
2623      let base = String(name)
2624        .replace(/\(.*?\)/g, '')   // (widow), (husband of), etc.
2625        .split(',')[0]             // ", Duke of Alba" fuera
2626        .replace(/[.]/g, ' ')
2627        .replace(/\s+/g, ' ')
2628        .trim();
2629
2630      // Helper: quitar acentos y pasar a minúsculas
2631      const fold = s => s.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
2632
2633      // 0) Override por nombre completo (y por base limpia)
2634      const exactName = String(name).trim();
2635      const override = SURNAME_FORCE_BY_FULLNAME[name] || SURNAME_FORCE_BY_FULLNAME[exactName] || SURNAME_FORCE_BY_FULLNAME[base];
2636      if (override) {
2637        return fold(override);
2638      }
2639
2640      const tokens = base.split(' ').filter(Boolean);
2641      if (!tokens.length) return '';
2642
2643      const tf = tokens.map(fold);
2644
2645      const particles = new Set([
2646        'de',"d'",'del','de-la','de-las','de-los','la','las','los','le','les',
2647        'du','des','d','da','di','do','dos','van','von','der','den','zu','zur',
2648        'y','e','the','of'
2649      ]);
2650
2651      const suffixes = new Set(['jr','junior','fils','hijo']);
2652      const titles = new Set([
2653        'duchess','duke','count','countess','marquis','marchioness',
2654        'queen','king','prince','princess','emperor','empress'
2655      ]);
2656      const roman = /^[IVXLCDM]+$/i;
2657
2658      // 1) Si hay un título con "of …" (Duchess of Abrantes), coge el último token
2659      if (tf.some(t => titles.has(t)) && tf.includes('of') && tokens.length >= 2) {
2660        return fold(tokens[tokens.length - 1]);
2661      }
2662
2663      // 2) Caso "X y Y": toma el anterior a 'y' (apellido español tradicional)
2664      const yIndex = tf.indexOf('y');
2665      if (yIndex > 0 && yIndex < tf.length - 1) {
2666        return fold(tokens[yIndex - 1]);
2667      }
2668
2669      // Helper para validar tokens como posibles apellidos
2670      const normalizeToken = (raw) => {
2671        const t = fold(raw).replace(/['’]/g,'');
2672        if (!t) return '';
2673        if (particles.has(t)) return '';
2674        if (suffixes.has(t)) return '';
2675        if (roman.test(t)) return '';
2676        return t;
2677      };
2678
2679      // 3) Fuerza de apellido por token (opcional)
2680      if (SURNAME_FORCE_TOKENS.has(tf[tf.length - 1])) {
2681        return tf[tf.length - 1];
2682      }
2683
2684      // 4) Dos tokens: por defecto usa el ÚLTIMO (Nombre Apellido),
2685      //    salvo que el primero esté marcado como nombre (forzado) o compuesto de nombres.
2686      if (tokens.length === 2) {
2687        const first = tf[0];
2688        const second = normalizeToken(tokens[1]) || tf[1];
2689
2690        // "Antoine-Jean" u otros compuestos: si todas las partes están forzadas como nombre → usa el segundo
2691        const isHyphenGiven = first.includes('-') &&
2692          first.split('-').every(p => GIVEN_FORCE_TOKENS.has(p));
2693
2694        const firstForcedGiven = GIVEN_FORCE_TOKENS.has(first) || isHyphenGiven;
2695
2696        // Si el segundo está forzado como apellido, úsalo
2697        if (SURNAME_FORCE_TOKENS.has(second)) return second;
2698
2699        // En casi todos los casos de 2 tokens queremos el ÚLTIMO (Apellido).
2700        // EXCEPCIÓN: si quieres que un caso 2-apellidos españoles cuente por el primero,
2701        //            añade el nombre completo arriba en SURNAME_FORCE_BY_FULLNAME.
2702        if (firstForcedGiven) {
2703          return second; // Nombre compuesto forzado → usa Apellido
2704        }
2705        return second;    // default: último token como apellido
2706      }
2707
2708      // 5) General: recorre desde el final buscando un token válido
2709      for (let i = tokens.length - 1; i >= 0; i--) {
2710        const n = normalizeToken(tokens[i]);
2711        if (n) return n;
2712      }
2713
2714      // 6) Fallback: último token "tal cual"
2715      return tf[tf.length - 1];
2716    }
2717
2718    function buildMembersList(data = window.__GN_DATA) {
2719      const container = document.getElementById('membersList');
2720      if (!container) return;
2721
2722      const items = (data?.nodes || [])
2723        .map(n => ({
2724          id: n.id,
2725          display: (CURRENT_LANG === 'es' && n['display name es']) ? n['display name es'] : n.id
2726        }))
2727        .filter(x => x.id);
2728
2729      const collator = new Intl.Collator(undefined, { sensitivity: 'base' });
2730      items.sort((a,b) => collator.compare(surnameKey(a.id), surnameKey(b.id)));
2731
2732      const esc = s => String(s).replace(/\\/g,'\\\\').replace(/'/g,"\\'");
2733
2734      let html = '';
2735      let currentLetter = '';
2736      items.forEach(({id, display}) => {
2737        const key = surnameKey(id);
2738        const letter = (key.charAt(0) || '#').toUpperCase();
2739        if (letter !== currentLetter) {
2740          if (currentLetter) html += '</ul>';
2741          html += `<div class="section-heading" style="margin-top:0.8rem;">${letter}</div><ul style="list-style:none; padding-left:0.5rem; margin:0;">`;
2742          currentLetter = letter;
2743        }
2744        html += `<li style="margin:0.1rem 0;"><a href="#" onclick="focusNode('${esc(id)}'); return false;" style="color:#66ccff; text-decoration:none;">${display}</a></li>`;
2745      });
2746      if (items.length) html += '</ul>';
2747
2748      container.innerHTML = html;
2749    }
2750
2751    window.buildMembersList = buildMembersList;
2752    buildMembersList(data);
2753
2754    let lastHighlightedNode = null;
2755    let lastHighlightedNodes = [];
2756    let lastNonHighlightedNodes = [];
2757    let activeEdgeIds = new Set();   // Edges activos (los conectados al nodo resaltado)
2758
2759    // ——— limpia SOLO el "rojo" de nodos; no toca edges ni estados globales ———
2760    function clearNodeHighlightsOnly() {
2761      const nodeUpdates = [];
2762
2763      if (lastHighlightedNode) {
2764        nodeUpdates.push({
2765          id: lastHighlightedNode,
2766          color: { border: '#2B7CE9' },
2767          borderWidth: 2,
2768          opacity: 1
2769        });
2770      }
2771
2772      if (lastHighlightedNodes && lastHighlightedNodes.length > 0) {
2773        lastHighlightedNodes.forEach(id => {
2774          nodeUpdates.push({
2775            id,
2776            color: { border: '#2B7CE9' },
2777            borderWidth: 2,
2778            opacity: 1
2779          });
2780        });
2781      }
2782
2783      if (nodeUpdates.length > 0) nodes.update(nodeUpdates);
2784
2785      // reset SOLO de nodos
2786      lastHighlightedNode = null;
2787      lastHighlightedNodes = [];
2788    }
2789
2790    // Exponer a window para que filterGraph() (global) pueda limpiar y registrar highlights
2791      window.__GN_clearHighlights = clearHighlights;
2792      window.__GN_setLastHighlightedNodes = function (ids) {
2793        lastHighlightedNodes = Array.isArray(ids) ? ids : [];
2794      };
2795
2796    function clearHighlights() {
2797      // Batch update nodes
2798      const nodeUpdates = [];
2799
2800      if (lastHighlightedNode) {
2801        nodeUpdates.push({ 
2802          id: lastHighlightedNode, 
2803          color: { border: '#2B7CE9' }, 
2804          borderWidth: 2,
2805          opacity: 1 // Reset opacity
2806        });
2807      }
2808      
2809      if (lastHighlightedNodes.length > 0) {
2810        lastHighlightedNodes.forEach(id => {
2811          nodeUpdates.push({ 
2812            id, 
2813            color: { border: '#2B7CE9' }, 
2814            borderWidth: 2,
2815            opacity: 1 // Reset opacity
2816          });
2817        });
2818      }
2819      
2820      // Reset opacity for non-highlighted nodes
2821      if (lastNonHighlightedNodes.length > 0) {
2822        lastNonHighlightedNodes.forEach(id => {
2823          nodeUpdates.push({ 
2824            id, 
2825            opacity: 1 // Reset to fully opaque
2826          });
2827        });
2828      }
2829      
2830      if (nodeUpdates.length > 0) nodes.update(nodeUpdates);
2831    
2832      // Batch update edges
2833      const edgeUpdates = edges.get().map(edge => {
2834        const style = getEdgeStyle(edge);
2835        return {
2836          id: edge.id,
2837          color: { color: style.color },
2838          width: style.width
2839        };
2840      });
2841      edges.update(edgeUpdates);
2842      
2843      // 🔁 Reset de estados
2844      lastHighlightedNode = null;
2845      lastHighlightedNodes = [];
2846      lastNonHighlightedNodes = [];
2847      activeEdgeIds = new Set();
2848    }
2849    
2850    const container = document.getElementById('network');
2851    if (!container) {
2852      console.error("❌ No se encontró el contenedor #network.");
2853      return;
2854    }
2855    
2856    // Check if we have a specific node/edge hash URL - for logging purposes only
2857    console.log(`Hash URL: ${window.location.hash}`);
2858    
2859    const network = new vis.Network(container, { nodes, edges }, {
2860      nodes: { 
2861        borderWidth: 2,
2862        shapeProperties: {
2863          useBorderWithImage: true
2864        }
2865      },
2866      edges: {
2867        color: 'rgba(200,200,200,0.2)',
2868        width: 1,
2869        selectionWidth: 5, // aumenta el área clicable al seleccionar
2870        hoverWidth: 3,     // facilita el clic al pasar el ratón
2871        smooth: { type: 'continuous' } // ← más rápido y visualmente igual en tu caso (antes: dynamic)
2872      },
2873      // Process edge strength to adjust width
2874      // (actual processing happens in edges.map() after loading JSON)
2875
2876      physics: {
2877        enabled: true, 
2878        solver: 'repulsion',
2879        repulsion: {
2880          nodeDistance: 600,         // increased from 500 for stronger node separation and collision prevention
2881          centralGravity: 0.018,     // ↓ Slightly less gravity
2882          springLength: 210,         // ↑ Longer springs for more separation
2883          springConstant: 0.012,     // ↓ Softer springs
2884          damping: 0.9               // ↑ Fast settling (increased from 0.52)
2885        },
2886        stabilization: {
2887          iterations: 200,           // Limit iterations for faster settling
2888          fit: true
2889        },
2890        maxVelocity: 50             // Limit velocity to prevent wild oscillations
2891      },
2892
2893        // 🔥 AÑADE ESTA NUEVA OPCIÓN (ANTI-OVERLAP INTEGRADO):
2894        layout: {
2895          improvedLayout: false,
2896          randomSeed: 1912,
2897          hierarchical: {
2898            enabled: false
2899          }
2900        },
2901
2902        // 🔥 Y ESTA OPCIÓN CRUCIAL:
2903        interaction: {
2904          dragNodes: true,
2905          hideEdgesOnDrag: false,
2906          hideNodesOnDrag: false,
2907          zoomView: true,          // ✅ permite hacer zoom
2908          dragView: true,          // ✅ permite mover la vista (pan)
2909          navigationButtons: false  // Disable built-in buttons, we'll create custom ones
2910        },
2911        
2912        // 🔥 Y ESTA MÁS:
2913        configure: {
2914          enabled: false,
2915          filter: 'physics'
2916        }
2917      });
2918    // Add canvas click handler to deselect cluster when clicking background
2919    network.on('click', function(params) {
2920      if (params.nodes.length === 0 && params.edges.length === 0 && selectedClusterId) {
2921        window.clearClusterSelection();
2922      }
2923    });
2924
2925    window.VIS_NETWORK = network;
2926
2927    // Move navigation buttons inside network container so they appear within the network area
2928    const panButtons = document.getElementById('customPanButtons');
2929    const zoomButtons = document.getElementById('customZoomButtons');
2930    if (panButtons && panButtons.parentElement.id !== 'network') {
2931      container.appendChild(panButtons);
2932    }
2933    if (zoomButtons && zoomButtons.parentElement.id !== 'network') {
2934      container.appendChild(zoomButtons);
2935    }
2936
2937    // ===== ADMIN MODE =====
2938    const urlParams = new URLSearchParams(window.location.search);
2939    const isAdminMode = urlParams.get('admin') === 'true';
2940    console.log('Admin Mode:', isAdminMode ? 'ENABLED' : 'DISABLED');
2941
2942    // Show/hide admin controls
2943    if (isAdminMode) {
2944      const adminPanel = document.createElement('div');
2945      adminPanel.style.cssText = `
2946        position: fixed; top: 10px; right: 10px; background: rgba(255, 0, 0, 0.9);
2947        color: white; padding: 10px 15px; border-radius: 5px; font-size: 12px; z-index: 9999;
2948        font-family: monospace;
2949      `;
2950      adminPanel.innerHTML = '🔓 ADMIN MODE ACTIVE<br><button id="downloadLayoutBtn" style="margin-top: 5px; padding: 5px 10px; cursor: pointer;">Download Layout</button>';
2951      document.body.appendChild(adminPanel);
2952
2953      document.getElementById('downloadLayoutBtn').addEventListener('click', function() {
2954        const positions = network.getPositions(nodes.getIds());
2955        const dataStr = JSON.stringify(positions, null, 2);
2956        const dataBlob = new Blob([dataStr], { type: 'application/json' });
2957        const url = URL.createObjectURL(dataBlob);
2958        const link = document.createElement('a');
2959        link.href = url;
2960        link.download = 'positions_config.json';
2961        link.click();
2962        console.log('Downloaded positions_config.json');
2963      });
2964    } else {
2965      // Disable node dragging but allow view panning for regular users
2966      network.setOptions({ interaction: { dragNodes: false, dragView: true } });
2967    }
2968
2969    // ===== CLUSTER POSITION PERSISTENCE =====
2970    let dragTimeout;
2971
2972    // Only save positions when dragging ends IN ADMIN MODE
2973    if (isAdminMode) {
2974      network.on('dragEnd', function(params) {
2975        clearTimeout(dragTimeout);
2976        dragTimeout = setTimeout(() => {
2977          const allNodeIds = nodes.getIds();
2978          const positions = network.getPositions(allNodeIds);
2979          allNodeIds.forEach(nodeId => {
2980            if (positions[nodeId]) {
2981              nodePositions[nodeId] = { x: positions[nodeId].x, y: positions[nodeId].y };
2982            }
2983          });
2984          localStorage.setItem('nodePositions', JSON.stringify(nodePositions));
2985          console.log('Saved ALL', allNodeIds.length, 'node positions');
2986        }, 500);
2987      });
2988    }
2989
2990    // Apply saved node positions on load - DEFINED AS GLOBAL
2991    window.applySavedNodePositions = function() {
2992      if (Object.keys(nodePositions).length > 0) {
2993        const savedNodeIds = Object.keys(nodePositions);
2994        const positions = network.getPositions(savedNodeIds);
2995        savedNodeIds.forEach(nodeId => {
2996          if (nodePositions[nodeId]) {
2997            network.moveNode(nodeId, nodePositions[nodeId].x, nodePositions[nodeId].y);
2998          }
2999        });
3000        console.log('Restored ALL positions for', savedNodeIds.length, 'nodes');
3001      }
3002    };
3003
3004
3005    // Add reset function
3006    window.resetNodePositions = function() {
3007      localStorage.removeItem('nodePositions');
3008      nodePositions = {};
3009      console.log('Reset all node positions');
3010      location.reload();
3011    };
3012
3013    const HALO_PRIORITY_BY_NODE = {
3014      "Francisco de Goya": 30,
3015      "Adrien Dauzats": 28,
3016      "Valentín Carderera": 25,
3017      "Federico de Madrazo": 28,
3018      "Eugène Delacroix": 30,
3019      "Charles Baudelaire": 22,
3020      "Nadar": 22,
3021      "Manuel Godoy": 22,
3022      "Mariano José Larra": 18,
3023      "Mariano Salvador Maella": 18,
3024      "Eugenia de Montijo": 20,
3025      "Richard Ford": 18,
3026      "Edgar Degas": 20,
3027      "Théophile Gautier": 25,
3028      "Achille Devéria": 18,
3029      "Eugène Piot": 20,
3030      "Eugenio Ochoa": 20,
3031      "Carlos Luis de Ribera": 20
3032    };
3033
3034    function getNodeHalo(node) {
3035      const size = node?.size || 25;
3036      const fontSize = node?.font?.size || 16;
3037      const id = node?.id || node?.label || '';
3038      const priorityExtra = HALO_PRIORITY_BY_NODE[id] || 0;
3039
3040      // halo visual = tamaño del nodo + peso de etiqueta + colchón extra
3041      return size + (fontSize * 1.8) + 18 + priorityExtra;
3042    }
3043
3044    function enforceGlobalNodeHalo(network, nodes, passes = 20) {
3045      const ids = nodes.getIds().filter(id => !String(id).startsWith('ANCHOR__'));
3046
3047      for (let p = 0; p < passes; p++) {
3048        const pos = network.getPositions(ids);
3049        let movedAny = false;
3050
3051        for (let i = 0; i < ids.length; i++) {
3052          for (let j = i + 1; j < ids.length; j++) {
3053            const a = ids[i];
3054            const b = ids[j];
3055
3056            const pA = pos[a];
3057            const pB = pos[b];
3058            if (!pA || !pB) continue;
3059
3060            const nodeA = nodes.get(a);
3061            const nodeB = nodes.get(b);
3062            if (!nodeA || !nodeB) continue;
3063
3064            const haloA = getNodeHalo(nodeA);
3065            const haloB = getNodeHalo(nodeB);
3066            const minD = haloA + haloB;
3067
3068            let dx = pB.x - pA.x;
3069            let dy = pB.y - pA.y;
3070            let d = Math.hypot(dx, dy);
3071
3072            // evita división por cero
3073            if (d < 0.001) {
3074              dx = 1;
3075              dy = 0;
3076              d = 1;
3077            }
3078
3079            if (d < minD) {
3080              const overlap = minD - d;
3081              const ux = dx / d;
3082              const uy = dy / d;
3083
3084              // mover cada nodo la mitad
3085              const shift = overlap / 2;
3086
3087              network.moveNode(a, pA.x - ux * shift, pA.y - uy * shift);
3088              network.moveNode(b, pB.x + ux * shift, pB.y + uy * shift);
3089
3090              movedAny = true;
3091            }
3092          }
3093        }
3094
3095        // si ya no ha movido nada, salir antes
3096        if (!movedAny) break;
3097      }
3098    }
3099
3100    // 🔥 SOLUCIÓN NUCLEAR ANTI-OVERLAP (MULTI-PASS, RADIO REAL)
3101    function nudgeOverlaps(network, nodes, clusterOf = null, passes = 5) {
3102      for (let p = 0; p < passes; p++) {
3103        const ids = nodes.getIds().filter(id => !String(id).startsWith('ANCHOR__'));
3104        const pos = network.getPositions(ids);
3105
3106        for (let i = 0; i < ids.length; i++) {
3107          for (let j = i + 1; j < ids.length; j++) {
3108            const a = ids[i], b = ids[j];
3109            // Permitir separación incluso dentro del mismo cluster
3110              const pA = pos[a], pB = pos[b];
3111              if (!pA || !pB) continue;
3112
3113            const nodeA = nodes.get(a);
3114            const nodeB = nodes.get(b);
3115
3116            const baseA = (nodeA?.size || 25) + ((nodeA?.font?.size || 16) * 1.8);
3117            const baseB = (nodeB?.size || 25) + ((nodeB?.font?.size || 16) * 1.8);
3118
3119            const extraA = HALO_PRIORITY_BY_NODE[nodeA?.id || ''] || 0;
3120            const extraB = HALO_PRIORITY_BY_NODE[nodeB?.id || ''] || 0;
3121
3122            const minD = baseA + baseB + 24 + extraA + extraB;
3123
3124            const dx = pB.x - pA.x;
3125            const dy = pB.y - pA.y;
3126            const d = Math.hypot(dx, dy) || 1;
3127
3128            if (d < minD) {
3129              const push = (minD - d) / 2;
3130              const ux = dx / d, uy = dy / d;
3131
3132              network.moveNode(a, pA.x - ux * push, pA.y - uy * push);
3133              network.moveNode(b, pB.x + ux * push, pB.y + uy * push);
3134            }
3135          }
3136        }
3137      }
3138    }
3139
3140    function placeFamilyAroundCenter(network, nodes, centerId, memberIds, radius = 140, startAngle = -Math.PI / 2) {
3141      const centerPos = network.getPositions([centerId])[centerId];
3142      if (!centerPos) return;
3143
3144      const validMembers = memberIds.filter(id => nodes.get(id) && id !== centerId);
3145      if (!validMembers.length) return;
3146
3147      const totalWeight = validMembers.reduce((acc, id) => {
3148        const n = nodes.get(id);
3149        return acc + (n?.size || 20);
3150      }, 0);
3151
3152      let currentAngle = startAngle;
3153
3154      validMembers.forEach(id => {
3155        const n = nodes.get(id);
3156        const weight = (n?.size || 20) / totalWeight;
3157        const angleSpan = weight * 2 * Math.PI;
3158        const angle = currentAngle + angleSpan / 2;
3159
3160        const x = centerPos.x + Math.cos(angle) * radius;
3161        const y = centerPos.y + Math.sin(angle) * radius;
3162
3163        network.moveNode(id, x, y);
3164        currentAngle += angleSpan;
3165      });
3166    }
3167
3168    function arrangeInCircle(network, nodes, memberIds, radius = 140, startAngle = -Math.PI / 2, sharedBoundaryNodes = {}) {
3169      const validMembers = memberIds.filter(id => nodes.get(id));
3170      if (validMembers.length < 2) return;
3171
3172      const pos = network.getPositions(validMembers);
3173      let cx = 0, cy = 0;
3174      validMembers.forEach(id => {
3175        cx += pos[id].x;
3176        cy += pos[id].y;
3177      });
3178      cx /= validMembers.length;
3179      cy /= validMembers.length;
3180
3181      const total = validMembers.length;
3182      const slotAngles = Array.from({ length: total }, (_, i) => startAngle + (i * 2 * Math.PI / total));
3183
3184      const normalizeAngle = a => {
3185        let x = a % (2 * Math.PI);
3186        if (x < 0) x += 2 * Math.PI;
3187        return x;
3188      };
3189
3190      const usedSlots = new Set();
3191      const slotByNode = new Map();
3192
3193      // 1) reservar slots para nodos compartidos, colocándolos en el borde deseado
3194      Object.entries(sharedBoundaryNodes).forEach(([nodeId, desiredAngle]) => {
3195        if (!validMembers.includes(nodeId)) return;
3196
3197        const target = normalizeAngle(desiredAngle);
3198        let bestIdx = -1;
3199        let bestDiff = Infinity;
3200
3201        slotAngles.forEach((angle, idx) => {
3202          if (usedSlots.has(idx)) return;
3203
3204          const diffRaw = Math.abs(normalizeAngle(angle) - target);
3205          const diff = Math.min(diffRaw, 2 * Math.PI - diffRaw);
3206
3207          if (diff < bestDiff) {
3208            bestDiff = diff;
3209            bestIdx = idx;
3210          }
3211        });
3212
3213        if (bestIdx !== -1) {
3214          usedSlots.add(bestIdx);
3215          slotByNode.set(nodeId, slotAngles[bestIdx]);
3216        }
3217      });
3218
3219      // 2) asignar el resto de nodos a slots libres
3220      const remainingNodes = validMembers.filter(id => !slotByNode.has(id));
3221      const freeSlots = slotAngles.filter((_, idx) => !usedSlots.has(idx));
3222
3223      remainingNodes.forEach((id, i) => {
3224        slotByNode.set(id, freeSlots[i]);
3225      });
3226
3227      // 3) mover nodos
3228      validMembers.forEach(id => {
3229        const angle = slotByNode.get(id);
3230        const x = cx + Math.cos(angle) * radius;
3231        const y = cy + Math.sin(angle) * radius;
3232        network.moveNode(id, x, y);
3233      });
3234    }
3235
3236    function arrangeAroundSharedNode(
3237      network,
3238      nodes,
3239      memberIds,
3240      sharedNodeId,
3241      radius = 140,
3242      sharedAngle = -Math.PI / 2,
3243      startSweepAngle = -Math.PI / 2,
3244      sweepSize = Math.PI * 1.55
3245    ) {
3246      const validMembers = memberIds.filter(id => nodes.get(id));
3247      if (validMembers.length < 2) return;
3248      if (!validMembers.includes(sharedNodeId)) return;
3249
3250      const sharedPos = network.getPositions([sharedNodeId])[sharedNodeId];
3251      if (!sharedPos) return;
3252
3253      // Centro del círculo deducido desde la posición fija del nodo compartido
3254      const cx = sharedPos.x - Math.cos(sharedAngle) * radius;
3255      const cy = sharedPos.y - Math.sin(sharedAngle) * radius;
3256
3257      // El shared node NO cuenta como slot normal
3258      const otherMembers = validMembers.filter(id => id !== sharedNodeId);
3259      const count = otherMembers.length;
3260      if (!count) return;
3261
3262      // Coloca el nodo compartido exactamente donde debe ir
3263      network.moveNode(
3264        sharedNodeId,
3265        cx + Math.cos(sharedAngle) * radius,
3266        cy + Math.sin(sharedAngle) * radius
3267      );
3268
3269      // Reparte el resto en un arco amplio, dejando libre la zona del shared node
3270      const step = count === 1 ? 0 : sweepSize / (count - 1);
3271
3272      otherMembers.forEach((id, i) => {
3273        const angle = startSweepAngle + step * i;
3274        const x = cx + Math.cos(angle) * radius;
3275        const y = cy + Math.sin(angle) * radius;
3276        network.moveNode(id, x, y);
3277      });
3278    }
3279
3280        function placeVillafrancaAlbaClusters(network) {
3281          const dukeId = "José Álvarez de Toledo, Duke of Alba";
3282          const duchessId = "María Teresa de Silva, XIII Duchess of Alba";
3283          const marquisId = "Francisco Álvarez de Toledo, XII Marquis of Villafranca";
3284          const widowId = "María Antonia Gonzaga, Marchioness of Villafranca (widow)";
3285          const mariaTomasaId = "María Tomasa Palafox, Marchioness of Villafranca";
3286
3287          const ids = [dukeId, duchessId, marquisId, widowId, mariaTomasaId];
3288          if (ids.some(id => !nodes.get(id))) return;
3289
3290          const sharedPos = network.getPositions([mariaTomasaId])[mariaTomasaId];
3291          if (!sharedPos) return;
3292
3293          const radius = 82;
3294          const sharedAngle = 0; // María Tomasa mira hacia Montijo, borde derecho
3295          const cx = sharedPos.x - Math.cos(sharedAngle) * radius;
3296          const cy = sharedPos.y - Math.sin(sharedAngle) * radius;
3297
3298          const ordered = [
3299            mariaTomasaId,  // 0º derecha
3300            widowId,        // 72º arriba-dcha
3301            dukeId,         // 144º arriba-izda
3302            duchessId,      // 216º abajo-izda
3303            marquisId       // 288º abajo-dcha
3304          ];
3305
3306          ordered.forEach((id, i) => {
3307            const angle = i * (2 * Math.PI / ordered.length);
3308            network.moveNode(
3309              id,
3310              cx + Math.cos(angle) * radius,
3311              cy + Math.sin(angle) * radius
3312            );
3313          });
3314        }
3315
3316      function placeFedericoSatelliteClusters(network) {
3317        const goyaId = "Francisco de Goya";
3318        const federicoId = "Federico de Madrazo";
3319
3320        if (!nodes.get(goyaId) || !nodes.get(federicoId)) return;
3321
3322        const goyaPos = network.getPositions([goyaId])[goyaId];
3323        const federicoPos = network.getPositions([federicoId])[federicoId];
3324        if (!goyaPos || !federicoPos) return;
3325
3326        // =========================================================
3327        // COURT PAINTERS
3328        // Entre Goya y Federico, pero algo más pegado a Goya.
3329        // =========================================================
3330        const courtRadius = 120; // increased from 90 for wider circle without overlaps
3331        const courtCenter = {
3332          x: goyaPos.x + (federicoPos.x - goyaPos.x) * 0.52, // before: 0.40
3333          y: goyaPos.y + (federicoPos.y - goyaPos.y) * 0.38 // before: 0.30
3334        };
3335
3336        const courtAngles = {
3337          "Francisco Bayeu":         Math.PI * 1.26,
3338          "Mariano Salvador Maella": Math.PI * 1.00,
3339          "Giambattista Tiepolo":    Math.PI * 0.78,
3340          "Giandomenico Tiepolo":    Math.PI * 0.58,
3341          "Vicente López":           Math.PI * 0.30,
3342          "Anton Raphael Mengs":     Math.PI * 0.10,
3343          "Agustín Esteve":          Math.PI * 1.66
3344        };
3345
3346        [
3347          "Francisco Bayeu",
3348          "Mariano Salvador Maella",
3349          "Giambattista Tiepolo",
3350          "Giandomenico Tiepolo",
3351          "Vicente López",
3352          "Anton Raphael Mengs",
3353          "Agustín Esteve"
3354        ].forEach(id => {
3355          if (!nodes.get(id)) return;
3356          const angle = courtAngles[id];
3357          network.moveNode(
3358            id,
3359            courtCenter.x + Math.cos(angle) * courtRadius,
3360            courtCenter.y + Math.sin(angle) * courtRadius
3361          );
3362        });
3363
3364        // =========================================================
3365        // MADRAZO-CARDERERA GROUP
3366        // Arriba-derecha de Federico, no encima del círculo familiar.
3367        // =========================================================
3368        const mcRadius = 160;
3369        const mcCenter = {
3370          x: federicoPos.x + 300,
3371          y: federicoPos.y - 150
3372        };
3373
3374        const mcMembers = [
3375          "Valentín Carderera",
3376          "Eugenio Ochoa",
3377          "Santiago Masarnau",
3378          "Vicente Masarnau",
3379          "Carlos Luis de Ribera"
3380        ];
3381
3382        const mcStep = (2 * Math.PI) / mcMembers.length;
3383        mcMembers.forEach((id, i) => {
3384          if (!nodes.get(id)) return;
3385          const angle = -Math.PI / 2 + i * mcStep;
3386          network.moveNode(
3387            id,
3388            mcCenter.x + Math.cos(angle) * mcRadius,
3389            mcCenter.y + Math.sin(angle) * mcRadius
3390          );
3391        });
3392
3393        // =========================================================
3394        // PRINT SPECIALISTS
3395        // Igual que antes.
3396        // =========================================================
3397        const piotId = "Eugène Piot";
3398        if (nodes.get(piotId)) {
3399          const piotPos = network.getPositions([piotId])[piotId];
3400          if (piotPos) {
3401            const psMembers = [
3402              "Philippe Burty",
3403              "Paul Lefort",
3404              "Charles Blanc",
3405              "Paul Mantz"
3406            ].filter(id => nodes.get(id));
3407
3408            const psRadius = 88;
3409            const psCenter = {
3410              x: piotPos.x + 34,
3411              y: piotPos.y + 72
3412            };
3413
3414            const psAngles = [
3415              Math.PI * 1.14,
3416              Math.PI * 0.76,
3417              Math.PI * 0.38,
3418              Math.PI * 0.04
3419            ];
3420
3421            psMembers.forEach((id, i) => {
3422              const angle = psAngles[i];
3423              network.moveNode(
3424                id,
3425                psCenter.x + Math.cos(angle) * psRadius,
3426                psCenter.y + Math.sin(angle) * psRadius
3427              );
3428            });
3429          }
3430        }
3431      }
3432
3433      function placeMadrazoFamilyCluster(network) {
3434        // Properly arrange Madrazo family in a circle
3435        const cfg = RADIAL_CLUSTERS["MADRAZO_FAMILY"];
3436        if (!cfg || !cfg.members) return;
3437        
3438        arrangeInCircle(
3439          network,
3440          nodes,
3441          cfg.members,
3442          cfg.radius || 260,
3443          cfg.startAngle ?? (-Math.PI / 2),
3444          cfg.sharedBoundaryNodes || {}
3445        );
3446      }
3447    
3448        function placeIlustradosCluster(network) {
3449          const cfg = RADIAL_CLUSTERS["ILUSTRADOS_CLUSTER"];
3450          if (!cfg || !cfg.members) return;
3451
3452          // Arrange in circle first
3453          arrangeInCircle(
3454            network,
3455            nodes,
3456            cfg.members,
3457            cfg.radius || 150,
3458            cfg.startAngle ?? (-Math.PI / 2),
3459            cfg.sharedBoundaryNodes || {}
3460          );
3461
3462          // Apply vertical offset if specified
3463          if (cfg.centerYOffset) {
3464            const memberIds = cfg.members.filter(id => nodes.get(id));
3465            const pos = network.getPositions(memberIds);
3466            memberIds.forEach(id => {
3467              const p = pos[id];
3468              network.moveNode(id, p.x, p.y + cfg.centerYOffset);
3469            });
3470          }
3471        }
3472
3473        function placeGoyaFamilyCluster(network) {
3474          const cfg = RADIAL_CLUSTERS["GOYA_FAMILY"];
3475          if (!cfg || !cfg.members) return;
3476
3477          // Arrange in circle first
3478          arrangeInCircle(
3479            network,
3480            nodes,
3481            cfg.members,
3482            cfg.radius || 85,
3483            cfg.startAngle ?? (-Math.PI / 2),
3484            cfg.sharedBoundaryNodes || {}
3485          );
3486
3487          // Apply vertical offset if specified
3488          if (cfg.centerYOffset) {
3489            const memberIds = cfg.members.filter(id => nodes.get(id));
3490            const pos = network.getPositions(memberIds);
3491            memberIds.forEach(id => {
3492              const p = pos[id];
3493              network.moveNode(id, p.x, p.y + cfg.centerYOffset);
3494            });
3495          }
3496        }
3497
3498        function placeGodoyPositioning(network) {
3499          // Position Godoy between his two wives: Chinchón (1st wife) and Tudó (2nd wife)
3500          const godoyId = "Manuel Godoy";
3501          const chinchonId = "XV Countess of Chinchón";
3502          const tudoId = "Josefa Tudó";
3503
3504          const ids = [godoyId, chinchonId, tudoId].filter(id => nodes.get(id));
3505          if (ids.length < 3 || !nodes.get(godoyId) || !nodes.get(chinchonId) || !nodes.get(tudoId)) return;
3506
3507          const pos = network.getPositions(ids);
3508          const chinchonPos = pos[chinchonId];
3509          const tudoPos = pos[tudoId];
3510
3511          if (!chinchonPos || !tudoPos) return;
3512
3513          // Place Godoy between them (average position, slightly offset)
3514          const godoyX = (chinchonPos.x + tudoPos.x) / 2 + 15;
3515          const godoyY = (chinchonPos.y + tudoPos.y) / 2 - 20;
3516          network.moveNode(godoyId, godoyX, godoyY);
3517        }
3518
3519        function placeMonacoGroup(network) {
3520          // Position Louis-Philippe next to Montpensier, and Montpensier next to Infanta María Luisa
3521          const louisId = "Louis Philippe I";
3522          const montpensierId = "Prince Antoine, Duke of Montpensier";
3523          const infantaId = "Infanta Luisa Fernanda de Borbón";
3524
3525          const ids = [louisId, montpensierId, infantaId].filter(id => nodes.get(id));
3526          if (ids.length < 3 || !nodes.get(infantaId)) return;
3527
3528          const pos = network.getPositions(ids);
3529          const infantaPos = pos[infantaId];
3530
3531          if (!infantaPos) return;
3532
3533          // Position Montpensier to the left of Infanta (below)
3534          network.moveNode(montpensierId, infantaPos.x - 70, infantaPos.y + 40);
3535
3536          // Position Louis-Philippe to the left of Montpensier
3537          const montpensierNewPos = network.getPositions([montpensierId])[montpensierId];
3538          if (montpensierNewPos) {
3539            network.moveNode(louisId, montpensierNewPos.x - 70, montpensierNewPos.y - 35);
3540          }
3541        }
3542    
3543        function placeMontpensierBridge(network) {
3544          const infantaId = "Infanta Luisa Fernanda de Borbón";
3545          const louisId = "Louis Philippe I";
3546          const montpensierId = "Prince Antoine, Duke of Montpensier";
3547
3548          if (!nodes.get(infantaId) || !nodes.get(louisId) || !nodes.get(montpensierId)) return;
3549
3550          const infantaPos = network.getPositions([infantaId])[infantaId];
3551          if (!infantaPos) return;
3552
3553          network.moveNode(louisId, infantaPos.x + 88, infantaPos.y - 34);
3554          network.moveNode(montpensierId, infantaPos.x + 92, infantaPos.y + 40);
3555        }
3556      
3557        function placeEstevesPair(network) {
3558          const agustinId = "Agustín Esteve";
3559          const rafaelId = "Rafael Esteve";
3560
3561          if (!nodes.get(agustinId) || !nodes.get(rafaelId)) return;
3562
3563          const agustinPos = network.getPositions([agustinId])[agustinId];
3564          if (!agustinPos) return;
3565
3566          network.moveNode(rafaelId, agustinPos.x - 54, agustinPos.y + 36);
3567        }
3568
3569        function placeCloseMasterStudentPairs(network) {
3570          // Keep key master-student pairs close together despite cluster boundaries
3571          const masterStudentPairs = [
3572            ["Ernest Meissonier", "Eduardo Zamacois", 90],
3573            // Add more as needed
3574          ];
3575
3576          masterStudentPairs.forEach(([masterId, studentId, distance]) => {
3577            if (!nodes.get(masterId) || !nodes.get(studentId)) return;
3578
3579            const masterPos = network.getPositions([masterId])[masterId];
3580            if (!masterPos) return;
3581
3582            // Position student to the right of master
3583            network.moveNode(studentId, masterPos.x + distance, masterPos.y);
3584          });
3585        }
3586
3587        function placeCloseRelativesToGoya(network) {
3588          // Rosario and Leocadia are level 1-2 with Goya, keep them very close
3589          const goyaId = "Francisco de Goya";
3590          if (!nodes.get(goyaId)) return;
3591
3592          const goyaPos = network.getPositions([goyaId])[goyaId];
3593          if (!goyaPos) return;
3594
3595          // Position Rosario and Leocadia above and to the right (accessible from Goya's perspective)
3596          if (nodes.get("Rosario Weiss Zorrilla")) {
3597            network.moveNode("Rosario Weiss Zorrilla", goyaPos.x - 100, goyaPos.y - 150);
3598          }
3599
3600          if (nodes.get("Leocadia Zorrilla y Galarza")) {
3601            network.moveNode("Leocadia Zorrilla y Galarza", goyaPos.x - 100, goyaPos.y - 100);
3602          }
3603        }
3604
3605        function placeGeorgeSandConnections(network) {
3606          // George Sand's level-1 connections should be close to her despite cluster boundaries
3607          const georgeSandId = "George Sand";
3608          if (!nodes.get(georgeSandId)) return;
3609
3610          const georgeSandPos = network.getPositions([georgeSandId])[georgeSandId];
3611          if (!georgeSandPos) return;
3612
3613          // Level-1 connections to keep close
3614          const connections = [
3615            { id: "Eugène Delacroix", distance: 110, angle: -Math.PI / 4 },      // NW
3616            { id: "Frédéric Chopin", distance: 95, angle: -Math.PI / 3 },       // upper left
3617            { id: "Alfred de Musset", distance: 100, angle: -Math.PI * 0.6 },   // left
3618            { id: "Prosper Mérimée", distance: 110, angle: -Math.PI * 0.7 }     // lower left
3619          ];
3620
3621          connections.forEach(conn => {
3622            if (!nodes.get(conn.id)) return;
3623            const x = georgeSandPos.x + Math.cos(conn.angle) * conn.distance;
3624            const y = georgeSandPos.y + Math.sin(conn.angle) * conn.distance;
3625            network.moveNode(conn.id, x, y);
3626          });
3627        }
3628
3629        function placeBourbonCluster(network) {
3630          const members = [
3631            "Carlos III",
3632            "Carlos IV",
3633            "María Luisa de Parma",
3634            "Fernando VII",
3635            "Infanta Luisa Fernanda de Borbón",
3636            "Luis de Borbón",
3637            "María Teresa de Vallabriga",
3638            "Isabel II",
3639            "María Cristina de Borbón-Dos Sicilias"
3640          ].filter(id => nodes.get(id));
3641
3642          const chinchonId = "XV Countess of Chinchón";
3643          if (!nodes.get(chinchonId) || !members.length) return;
3644
3645          const godoyId = "Manuel Godoy";
3646          const radius = 150;
3647
3648          // Calculate circle center from OTHER members (not Chinchón)
3649          const memberPos = network.getPositions(members);
3650          let cx = 0, cy = 0;
3651          members.forEach(id => {
3652            cx += memberPos[id].x;
3653            cy += memberPos[id].y;
3654          });
3655          cx /= members.length;
3656          cy /= members.length;
3657
3658          // Get Godoy's angle from this center
3659          let godoyAngle = 0;
3660          if (nodes.get(godoyId)) {
3661            const godoyPos = network.getPositions([godoyId])[godoyId];
3662            godoyAngle = Math.atan2(godoyPos.y - cy, godoyPos.x - cx);
3663          }
3664
3665          // Place Chinchón OUTSIDE the circle on the Godoy-facing side
3666          const chinchonRadius = radius + 80; // extend beyond circle toward Godoy
3667          network.moveNode(
3668            chinchonId,
3669            cx + Math.cos(godoyAngle) * chinchonRadius,
3670            cy + Math.sin(godoyAngle) * chinchonRadius
3671          );
3672
3673          // Arrange other members evenly around the core circle
3674          const angleStep = (2 * Math.PI) / members.length;
3675          let currentAngle = -Math.PI / 2; // start at top
3676
3677          members.forEach(id => {
3678            network.moveNode(
3679              id,
3680              cx + Math.cos(currentAngle) * radius,
3681              cy + Math.sin(currentAngle) * radius
3682            );
3683            currentAngle += angleStep;
3684          });
3685        }
3686
3687    function getClusterNodeIds(cfg, nodes) {
3688      return [...new Set(
3689        [
3690          ...(cfg.center && nodes.get(cfg.center) ? [cfg.center] : []),
3691          ...((cfg.members || []).filter(id => nodes.get(id)))
3692        ]
3693      )];
3694    }
3695
3696    function getClusterCenterPos(network, nodes, cfg) {
3697      const ids = getClusterNodeIds(cfg, nodes);
3698      if (!ids.length) return null;
3699
3700      if (cfg.center && nodes.get(cfg.center)) {
3701        return network.getPositions([cfg.center])[cfg.center];
3702      }
3703
3704      const pos = network.getPositions(ids);
3705      let cx = 0, cy = 0;
3706      ids.forEach(id => {
3707        cx += pos[id].x;
3708        cy += pos[id].y;
3709      });
3710
3711      return {
3712        x: cx / ids.length,
3713        y: cy / ids.length
3714      };
3715    }
3716
3717    function pushOutsidersFromClusters(network, nodes, clusters, defaultPadding = 70) {
3718      const allIds = nodes.getIds().filter(id => !String(id).startsWith('ANCHOR__'));
3719      const pos = network.getPositions(allIds);
3720
3721      Object.entries(clusters).forEach(([clusterId, cfg]) => {
3722        const centerPos = getClusterCenterPos(network, nodes, cfg);
3723        if (!centerPos) return;
3724
3725        const clusterIds = new Set(getClusterNodeIds(cfg, nodes));
3726        const baseRadius = cfg.radius || 150;
3727        const padding = cfg.padding ?? defaultPadding;
3728        const halo = baseRadius + padding;
3729
3730        allIds.forEach(id => {
3731          if (clusterIds.has(id)) return;
3732
3733          // no expulsar un nodo compartido que pertenece a este clúster
3734          const belongsHere = Array.isArray(nodeClusterMap[id]) && nodeClusterMap[id].includes(clusterId);
3735          if (belongsHere) return;
3736
3737          const p = pos[id];
3738          if (!p) return;
3739
3740          const outsider = nodes.get(id);
3741          const outsiderHalo = Math.max(18, (getNodeHalo(outsider) || 18) * 0.35);
3742
3743          const dx = p.x - centerPos.x;
3744          const dy = p.y - centerPos.y;
3745          const d = Math.hypot(dx, dy) || 1;
3746
3747          const minD = halo + outsiderHalo;
3748
3749          if (d < minD) {
3750            const push = minD - d;
3751            const ux = dx / d;
3752            const uy = dy / d;
3753            network.moveNode(id, p.x + ux * push, p.y + uy * push);
3754          }
3755        });
3756      });
3757    }
3758
3759      function separateClusters(network, nodes, clusters, passes = 16, baseGap = 110, sharedGap = 36) {
3760      const entries = Object.entries(clusters);
3761
3762      for (let pass = 0; pass < passes; pass++) {
3763        let movedAny = false;
3764
3765        for (let i = 0; i < entries.length; i++) {
3766          for (let j = i + 1; j < entries.length; j++) {
3767            const [clusterAId, cfgA] = entries[i];
3768            const [clusterBId, cfgB] = entries[j];
3769
3770            const idsA = getClusterNodeIds(cfgA, nodes);
3771            const idsB = getClusterNodeIds(cfgB, nodes);
3772            if (!idsA.length || !idsB.length) continue;
3773
3774            const shared = idsA.filter(id => idsB.includes(id));
3775
3776            const centerA = getClusterCenterPos(network, nodes, cfgA);
3777            const centerB = getClusterCenterPos(network, nodes, cfgB);
3778            if (!centerA || !centerB) continue;
3779
3780            let dx = centerB.x - centerA.x;
3781            let dy = centerB.y - centerA.y;
3782            let d = Math.hypot(dx, dy);
3783
3784            if (d < 0.001) {
3785              dx = 1;
3786              dy = 0;
3787              d = 1;
3788            }
3789
3790            const radiusA = (cfgA.radius || 150) + (cfgA.padding || 0);
3791            const radiusB = (cfgB.radius || 150) + (cfgB.padding || 0);
3792            const minD = radiusA + radiusB + (shared.length ? sharedGap : baseGap);
3793
3794            if (d < minD) {
3795              const overlap = minD - d;
3796              const ux = dx / d;
3797              const uy = dy / d;
3798
3799              const movableA = idsA.filter(id => !shared.includes(id));
3800              const movableB = idsB.filter(id => !shared.includes(id));
3801
3802              const posA = network.getPositions(movableA);
3803              const posB = network.getPositions(movableB);
3804
3805              movableA.forEach(id => {
3806                const p = posA[id];
3807                network.moveNode(id, p.x - ux * overlap / 2, p.y - uy * overlap / 2);
3808              });
3809
3810              movableB.forEach(id => {
3811                const p = posB[id];
3812                network.moveNode(id, p.x + ux * overlap / 2, p.y + uy * overlap / 2);
3813              });
3814
3815              movedAny = true;
3816            }
3817          }
3818        }
3819
3820        if (!movedAny) break;
3821      }
3822    }
3823
3824
3825    // ——— Loading progress (vis-network physics) ———
3826    const loadingEl = document.getElementById('loadingMessage');
3827    const loadingPct = document.getElementById('loadingProgress');
3828
3829    network.on('stabilizationProgress', function (params) {
3830      if (!loadingEl || !loadingPct) return;
3831      const pct = Math.max(1, Math.min(100, Math.round((params.iterations / params.total) * 100)));
3832      loadingPct.textContent = pct + '%';
3833    });
3834
3835    function loadFullImages() {
3836      const imageUpdates = data.nodes
3837        .filter(node =>
3837 node.image)
3838        .map(node => ({ id: node.id, image: node.image }));
3839      
3840      if (imageUpdates.length > 0) {
3841        nodes.update(imageUpdates);
3842      }
3843    }
3844    
3845  network.once("stabilizationIterationsDone", function () {
3846    document.getElementById('loadingMessage').style.display = 'none';
3847
3848    // 1) Carga imágenes
3849    loadFullImages();
3850
3851    // 2) Apaga física para fijar posiciones
3852    network.setOptions({ physics: { enabled: false } });
3853
3854    // 3) Check if we have saved node positions - if so, apply them and skip all positioning
3855    if (Object.keys(nodePositions).length > 0) {
3856      console.log('Applying saved node positions, skipping cluster positioning');
3857      applySavedNodePositions();
3858      network.redraw();  // Force immediate redraw
3859      
3860      // Trigger hash navigation after a small delay to ensure render
3861      setTimeout(() => {
3862        if (!__defaultNodeInfoHTML) {
3863          __defaultNodeInfoHTML = document.getElementById('nodeInfo').innerHTML;
3864        }
3865        if (typeof handleInitialHash === 'function' && !__hashProcessed) {
3866          console.log('🎯 Triggering hash navigation after render');
3867          handleInitialHash();
3868        }
3869      }, 200);  // Increased from 100ms to allow rendering
3870    } else {
3871      // 3) Empujón anti-overlap cuando ya están puestas las imágenes
3872      setTimeout(() => {
3873        if (!window.__didNudgeOnce) {
3874          window.__didNudgeOnce = true;
3875
3876          // 1) Small initial nudge to prevent overlaps
3877          nudgeOverlaps(network, nodes, window.__clusterOf, 20);
3878
3879
3880          network.redraw();
3881        }
3882      }, 150);
3883      
3884      // For cases without saved positions, call hash navigation after a short delay
3885      setTimeout(() => {
3886        if (!__defaultNodeInfoHTML) {
3887          __defaultNodeInfoHTML = document.getElementById('nodeInfo').innerHTML;
3888        }
3889        if (typeof handleInitialHash === 'function' && !__hashProcessed) {
3890          handleInitialHash();
3891        }
3892      }, 200);
3893    }
3894
3895    // Listen for hash changes (when user clicks links with hash URLs)
3896    window.addEventListener('hashchange', () => {
3897      __hashProcessed = false;
3898      handleInitialHash();
3899    });
3900    
3901    // Safety fallback: ensure physics is disabled after max time
3902    setTimeout(() => {
3903      network.setOptions({ physics: { enabled: false } });
3904      console.log('⏱ Force disabled physics after timeout');
3905    }, 3000);  // Force disable after 3 seconds if stabilization hasn't happened yet
3906  });
3907
3908    function highlightNeighborhood(nodeId) {
3909      // 1) Obtener edges conectados
3910      const connectedEdges = edges.get({
3911        filter: edge => !edge._isClusterEdge && (edge.from === nodeId || edge.to === nodeId)
3912      });
3913
3914      // 2) Conjunto de nodos conectados (vecinos directos)
3915      const connectedNodes = new Set();
3916      connectedEdges.forEach(edge => {
3917        connectedNodes.add(edge.from === nodeId ? edge.to : edge.from);
3918      });
3919
3920      // 3) Listas de ids
3921      const allNodeIds = nodes.getIds();
3922      const nonConnected = allNodeIds.filter(
3923        id => id !== nodeId && !connectedNodes.has(id)
3924      );
3925
3926      const allEdges = edges.get();
3927      const connectedEdgeIds = new Set(connectedEdges.map(e => e.id));
3928      activeEdgeIds = new Set(connectedEdgeIds); // por si lo usas en otros sitios
3929
3930      // 4) Actualizar NODOS en un solo batch
3931      const nodeUpdates = [];
3932
3933      // Nodo clicado: rojo
3934      nodeUpdates.push({
3935        id: nodeId,
3936        color: { border: 'red' },
3937        borderWidth: 4,
3938        opacity: 1
3939      });
3940
3941      // Vecinos: naranja
3942      connectedNodes.forEach(id => {
3943        nodeUpdates.push({
3944          id,
3945          color: { border: '#ffa500' },
3946          borderWidth: 3,
3947          opacity: 1
3948        });
3949      });
3950
3951      // No conectados: atenuados (pero visibles)
3952      nonConnected.forEach(id => {
3953        nodeUpdates.push({
3954          id,
3955          opacity: 0.4
3956        });
3957      });
3958
3959      nodes.update(nodeUpdates);
3960
3961      // 5) Actualizar EDGES en un solo batch
3962      const edgeUpdates = allEdges.map(edge => {
3963        const isConnected = connectedEdgeIds.has(edge.id);
3964        return {
3965          id: edge.id,
3966          color: {
3967            color: isConnected
3968              ? 'rgba(255, 0, 0, 0.9)'    // rojo fuerte para edges del vecindario
3969              : 'rgba(200, 200, 200, 0.15)' // un poco más atenuado el resto
3970          },
3971          width: isConnected ? 3 : 1.5
3972        };
3973      });
3974
3975      edges.update(edgeUpdates);
3976
3977      // 6) Guardar estado para clearHighlights()
3978      lastHighlightedNode = nodeId;
3979      lastHighlightedNodes = Array.from(connectedNodes);
3980      lastNonHighlightedNodes = nonConnected;
3981    }
3982    
3983    function pointToSegmentDistance(px, py, x1, y1, x2, y2) {
3984      const vx = x2 - x1, vy = y2 - y1;
3985      const wx = px - x1, wy = py - y1;
3986      const c1 = vx * wx + vy * wy;
3987      if (c1 <= 0) return Math.hypot(px - x1, py - y1);
3988      const c2 = vx * vx + vy * vy;
3989      if (c2 <= c1) return Math.hypot(px - x2, py - y2);
3990      const t = c1 / c2;
3991      const projx = x1 + t * vx, projy = y1 + t * vy;
3992      return Math.hypot(px - projx, py - projy);
3993    }
3994
3995    network.on("click", function (params) {
3996      
3997      // --- CLICK EN FONDO: limpiar selección de cluster ---
3998      if (params.nodes.length === 0 && params.edges.length === 0) {
3999        if (selectedClusterId) {
4000          window.clearClusterSelection();
4001        }
4002      }
4003
4004      // Cerrar "New in" solo cuando se hace clic en nodos o edges
4005      if ((params.nodes.length > 0 || params.edges.length > 0) && typeof showNewInPanel === 'function') {
4006        showNewInPanel(false);
4007      }
4008
4009      // --- NODO ---
4010      if (params.nodes.length > 0) {
4011        const node = nodes.get(params.nodes[0]);
4012
4013        if (!node || node.clusterAnchor) return;
4014
4015        updateNodeURL(node.id);
4016
4017        clearHighlights();
4018        highlightNeighborhood(node.id);
4019        lastHighlightedNode = node.id;
4020
4021        const degree = edgeCount[node.id] || 0;
4022        let html = `<div class="node-info">`;
4023
4024        // ——— Barra de logos de autoridades junto a la foto (vertical, a la derecha) ———
4025        {
4026          const ICON_SRC = {
4027            'wikidata':     'images/Wikidata_logo.svg',
4028            'getty ulan':   'images/Getty_logo.jpg',
4029            'bnf':          'images/BnF_logo.jpg',
4030            'rah':          'images/RAH_logo.jpg'
4031          };
4032
4033          // Merge: objeto authorities + posibles campos sueltos
4034          const authorities = Object.assign({}, node.authorities || {});
4035          if (node['wikidata'])     authorities['wikidata']   = node['wikidata'];
4036          if (node['getty ulan'])   authorities['getty ulan'] = node['getty ulan'];
4037          if (node['bnf'])          authorities['bnf']        = node['bnf'];
4038          if (node['rah'])          authorities['rah']        = node['rah'];
4039
4040          const badges = Object.entries(authorities)
4041            .filter(([_, url]) => url)
4042            .map(([key, url]) => {
4043              const k = String(key).toLowerCase();
4044              const src = ICON_SRC[k] || ICON_SRC['getty ulan'];
4045              const title =
4046                (k === 'getty ulan' || k === 'ulan' || k === 'getty') ? 'Getty (ULAN)' :
4047                (k === 'bnf' ? 'BnF' :
4048                (k === 'wikidata' ? 'Wikidata' :
4049                (k === 'rah' ? 'RAH' : key)));
4050              // 👇 Importante: SIN height inline (que pisaba el CSS)
4051              return `
4052                <a href="${url}" target="_blank" rel="noopener noreferrer" title="${title}" style="display:block;">
4053                  <img src="${src}" alt="${title}">
4054                </a>`;
4055            }).join('');
4056
4057          if (node.image) {
4058            html += `
4059              <div class="photo-with-authorities">
4060                <img src="${node.image}" alt="${node.id}" class="node-portrait">
4061                ${badges ? `<div class="authority-badges">${badges}</div>` : ``}
4062              </div>
4063            `;
4064          } else if (badges) {
4065            html += `<div class="authority-badges">${badges}</div>`;
4066          }
4067
4068          // Flag global para no repetir los enlaces en el cuerpo
4069          window.__hasAuthoritiesBar = !!badges;
4070        }
4071
4072
4073        const displayName =
4074          (CURRENT_LANG === 'es' && node['display name es'])
4075            ? node['display name es']
4076            : node.id;
4077
4078        html += `<h2>${displayName}</h2>`;
4079
4080        const fieldsToShow = [
4081          { type: "section", label: "Identity and Personal Data" },
4082          { type: "field", key: "full name", label: "Full name" },
4083          { type: "field", key: "also known as", label: "Also known as" },
4084          { type: "field", key: "other names", label: "Other names" },
4085          { type: "field", key: "pseudonyms", label: "Pseudonyms" },
4086          { type: "field", key: "life dates", label: "Life dates" },
4087          { type: "field", key: "sex", label: "Sex" },
4088          { type: "field", key: "profession", label: "Profession" },
4089          { type: "field", key: "nationality", label: "Nationality" },
4090          { type: "field", key: "born in", label: "Born in" },
4091          { type: "field", key: "dies in", label: "Dies in" },
4092        
4093          { type: "section", label: "Social and Political Identity" },
4094          { type: "field", key: "aristocratic titles", label: "Aristocratic titles" },
4095          { type: "field", key: "considered as", label: "Considered as" },
4096          { type: "field", key: "political views", label: "Political views" },
4097          { type: "field", key: "military activity", label: "Military activity" },
4098          { type: "field", key: "participates in", label: "Participates in" },
4099          { type: "field", key: "ties with slavery/colonialism", label: "Ties with slavery/colonialism" },
4100        
4101          { type: "section", label: "Family and Personal Relationships" },
4102          { type: "field", key: "parents", label: "Parents" },
4103          { type: "field", key: "siblings", label: "Siblings" },
4104          { type: "field", key: "married to", label: "Married to" },
4105          { type: "field", key: "partners/lovers", label: "Partners/lovers" },
4106          { type: "field", key: "children", label: "Children" },
4107          { type: "field", key: "friends", label: "Friends" },
4108          { type: "field", key: "rivals", label: "Rivals" },
4109          { type: "field", key: "correspondence", label: "Correspondence" },
4110          { type: "field", key: "meets", label: "Meets" },
4111        
4112          { type: "section", label: "Education, Profession and Institutions" },
4113          { type: "field", key: "studies in", label: "Studies in" },
4114          { type: "field", key: "masters", label: "Masters" },
4115          { type: "field", key: "influenced by", label: "Influenced by" },
4116          { type: "field", key: "follower of", label: "Follower of" },
4117          { type: "field", key: "works as", label: "Works as" },
4118          { type: "field", key: "works for", label: "Works for" },
4119          { type: "field", key: "collaborates with", label: "Collaborates with" },
4120          { type: "field", key: "students", label: "Students" },
4121          { type: "field", key: "registered in", label: "Registered in" },
4122          { type: "field", key: "member of", label: "Member of" },
4123          { type: "field", key: "founder of", label: "Founder of" },
4124          { type: "field", key: "editor of", label: "Editor of" },
4125          { type: "field", key: "writes in", label: "Writes in" },
4126          { type: "field", key: "literary salon or tertulia", label: "Literary salon or tertulia" },
4127          { type: "field", key: "decorations/awards", label: "Decorations/awards" },
4128
4129          { type: "section", label: "Artistic Activity, Art Collecting and Patronage" },
4130          { type: "field", key: "author of", label: "Author of" },
4131          { type: "field", key: "collection", label: "Collection" },
4132          { type: "field", key: "art collection", label: "Art collection" },
4133          { type: "field", key: "collector of", label: "Collector of" },
4134          { type: "field", key: "sales", label: "Sales" },
4135          { type: "field", key: "exhibitions", label: "Exhibitions" },
4136          { type: "field", key: "museum collections", label: "Museum collections" },
4137          { type: "field", key: "patronage", label: "Patronage" },
4138          { type: "field", key: "patrons", label: "Patrons" },
4139          { type: "field", key: "portrayed by", label: "Portrayed by" },
4140          { type: "field", key: "views on art and cultural heritage", label: "Views on art and cultural heritage" },
4141
4142          { type: "section", label: "Geographic Presence and Movement" },
4143          { type: "field", key: "lives in", label: "Lives in" },
4144          { type: "field", key: "address", label: "Address" },
4145          { type: "field", key: "trips", label: "Trips" },
4146          { type: "field", key: "in Spain", label: "In Spain" },
4147          { type: "field", key: "in Madrid", label: "In Madrid" },
4148          { type: "field", key: "visits the Prado Museum", label: "Visits the Prado Museum" },
4149          { type: "field", key: "in France", label: "In France" },
4150          { type: "field", key: "in Paris", label: "In Paris" },
4151          { type: "field", key: "in Italy", label: "In Italy" },
4152        
4153          { type: "section", label: "Relationship to Goya and His Work" },
4154          { type: "field", key: "link to Goya's work", label: "Link to Goya's work" },
4155          { type: "field", key: "discovers Goya's works", label: "Discovers Goya's works" },
4156          { type: "field", key: "knows Goya's works", label: "Knows Goya's works" },
4157          { type: "field", key: "copies after Goya", label: "Copies after Goya" },
4158          { type: "field", key: "writes about Goya", label: "Writes about Goya" },
4159          { type: "field", key: "mentions of Goya", label: "Mentions of Goya" },
4160          { type: "field", key: "commissions Goya with", label: "Commissions Goya with" },
4161          { type: "field", key: "influence of Goya", label: "Influence of Goya" },
4162          { type: "field", key: "collector of Goya's works", label: "Collector of Goya's works" },
4163          { type: "field", key: "disseminates Goya's works", label: "Disseminates Goya's works" },
4164          { type: "field", key: "shows Goya", label: "Shows Goya" },
4165          { type: "field", key: "mentions the Prado commentaries", label: "Mentions the Prado commentaries" },
4166          { type: "field", key: "interest in Spanish art", label: "Interest in Spanish art" },
4167          { type: "field", key: "copies after Spanish artworks", label: "Copies after Spanish artworks" },
4168        
4169          { type: "section", label: "Curiosities" },
4170          { type: "field", key: "curiosities", label: "Curiosities" },
4171        
4172          { type: "section", label: "Sources and Documentation" },
4173          { type: "field", key: "mentioned in the French press", label: "Mentioned in the French press" },
4174          { type: "field", key: "mentions", label: "Mentions" },
4175          { type: "field", key: "archives", label: "Archives" },
4176          { type: "field", key: "bibliography", label: "Bibliography" },
4177          { type: "field", key: "conferences/seminars", label: "Conferences/seminars" },
4178          { type: "field", key: "newspaper archive", label: "Newspaper archive" },
4179          { type: "field", key: "websites", label: "Websites" },
4180          { type: "field", key: "podcasts", label: "Podcasts" },
4181          { type: "field", key: "image source", label: "Image source" },
4182          { type: "field", key: "wikidata", label: "Wikidata" },
4183          { type: "field", key: "getty ulan", label: "Getty ULAN" },
4184          { type: "field", key: "bnf", label: "BnF" },
4185          { type: "field", key: "rah", label: "RAH" },
4186          { type: "field", key: "added", label: "Added" },
4187          { type: "field", key: "last_modified", label: "Last modified" },
4188        ];
4189
4190      const nodesMapByLabel = {};
4191      nodes.get().forEach(n => nodesMapByLabel[n.label] = n);
4192
4193      fieldsToShow.forEach((field, idx) => {
4194        if (field.type === "section") {
4195          // 🔧 Buscar los campos que pertenecen solo a esta sección
4196          const fieldsInThisSection = [];
4197          for (let i = idx + 1; i < fieldsToShow.length; i++) {
4198            if (fieldsToShow[i].type === "section") break;
4199            fieldsInThisSection.push(fieldsToShow[i]);
4200          }
4201      
4202          const hasData = fieldsInThisSection.some(f => node[f.key]);
4203          if (hasData) {
4204            html += `<h3 class="section-heading">${t(field.label)}</h3>`;
4205          }
4206        } else if (field.type === "field" && node[field.key]) {
4207            // 🔗 Caso especial: Wikidata / Getty ULAN / BnF
4208            if (field.key === 'wikidata' || field.key === 'getty ulan' || field.key === 'bnf' || field.key === 'rah') {
4209              // Si ya mostramos la barra de logos arriba, NO repetir aquí
4210              if (window.__hasAuthoritiesBar) {
4211                return; // evita duplicado
4212              }
4213
4214              // Si NO hay barra (porque no había imagen o logos), renderiza como enlace con icono
4215              const url = String(node[field.key]).trim();
4216
4217              let iconClass, linkLabel;
4218              if (field.key === 'wikidata') {
4219                iconClass = 'fa-brands fa-wikipedia-w';
4220                linkLabel = 'Wikidata';
4221              } else if (field.key === 'getty ulan') {
4222                iconClass = 'fa-solid fa-landmark';
4223                linkLabel = 'Getty (ULAN)';
4224              } else if (field.key === 'bnf') {
4225                iconClass = 'fa-solid fa-book';
4226                linkLabel = 'BnF';
4227              } else { // 'rah'
4228                iconClass = 'fa-solid fa-building-columns';
4229                linkLabel = 'RAH';
4230              }
4231
4232              html += `<p style="margin-top:0.3rem;">
4233                <a href="${url}" target="_blank" rel="noopener noreferrer" style="color:#66ccff;">
4234                  <i class="${iconClass}" style="margin-right:6px;"></i>${linkLabel}
4235                </a>
4236              </p>`;
4237              return; // evita el formateo genérico
4238            }
4239
4240
4241          let value = node[field.key];
4242          let htmlText;
4243      
4244          // helper: traduce valores "cortos" tipo direct/acquaintances/etc.
4245          const isProfessionField = (field.key === "profession");
4246          function translateValue(v) {
4247            if (typeof v !== "string") return v;
4248
4249            const raw = v.trim();
4250            if (!raw) return raw;
4251
4252            // 1) intento literal (por si ya viene "Poet", "Spanish", etc.)
4253            let out = t(raw);
4254            if (out !== raw) return isProfessionField ? out.toLowerCase() : out;
4255
4256            const capFirst = raw.charAt(0).toUpperCase() + raw.slice(1);
4257            out = t(capFirst);
4258            if (out !== capFirst) return isProfessionField ? out.toLowerCase() : out;
4259
4260            // 3) sin traducción
4261            return isProfessionField ? raw.toLowerCase() : raw;
4262          }
4263
4264          if (Array.isArray(value)) {
4265            const processedItems = value.map(item => {
4266
4267              if (typeof item === "string") {
4268                const translatedItem = translateValue(item);
4269                return `<li>${autoLinkNames(processMarkdownLinks(translatedItem), nodesMap)}</li>`;
4270              }
4271
4272              if (typeof item === "object" && item !== null) {
4273                const u = item.url ? String(item.url).trim() : "";
4274
4275                // Links (websites)
4276                if (u && !isImageUrl(u)) {
4277                  const text = item.label || item.caption || u;
4278                  const lab = autoLinkNames(processMarkdownLinks(text), nodesMap);
4279                  return `<li><a href="${u}" target="_blank" rel="noopener noreferrer" style="color:#66ccff;">${lab}</a></li>`;
4280                }
4281
4282                // Images
4283                if (u && isImageUrl(u)) {
4284                  const caption = item.caption
4285                    ? autoLinkNames(processMarkdownLinks(item.caption), nodesMap)
4286                    : "";
4287                  return `<li>${caption}${caption ? " " : ""}<img src="${u}" alt="Image" style="max-width:100%; margin-left:0.5rem; vertical-align:middle;"></li>`;
4288                }
4289              }
4290
4291              return null;
4292            }
4292).filter(Boolean);
4293
4294            htmlText = `<ul>${processedItems.join("")}</ul>`;
4295          } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
4296            // Handle plain objects (e.g., "author of" with nested categories)
4297            const sections = [];
4298            for (const [key, items] of Object.entries(value)) {
4299              if (Array.isArray(items)) {
4300                const processedItems = items.map(item => {
4301                  if (typeof item === "string") {
4302                    const translatedItem = translateValue(item);
4303                    return `<li>${autoLinkNames(processMarkdownLinks(translatedItem), nodesMap)}</li>`;
4304                  }
4305                  return null;
4306                }).filter(Boolean);
4307                
4308                if (processedItems.length > 0) {
4309                  sections.push(`<div style="margin-top:0.5rem;"><strong>${key}:</strong><ul>${processedItems.join("")}</ul></div>`);
4310                }
4311              }
4312            }
4313            htmlText = sections.join("");
4314          } else {
4315            let translatedValue = value;
4316
4317            // Handle professions with sex-specific adaptation (pintor/a -> pintor for male, pintora for female)
4318            if (typeof value === "string" && field.key === "profession") {
4319              translatedValue = value
4320                .split(',')
4321                .map(v => {
4322                  v = v.trim();
4323                  // First translate the profession
4324                  let translated = translateValue(v);
4325                  // Then apply gender-specific adaptation
4326                  if (node.sex === "female") {
4327                    // For female: "pintor/a" becomes "pintora", "editor/a" becomes "editora"
4328                    translated = translated.replace(/(\w+)\/a\b/g, '$1a');
4329                  } else if (node.sex === "male") {
4330                    // For male: remove /a - "pintor/a" becomes "pintor"
4331                    translated = translated.replace(/\/a\b/g, '');
4332                  }
4333                  // else: keep as-is (with /a) if sex is unknown
4334                  return translated;
4335                })
4336                .join(', ');
4337            } else if (typeof value === "string" && field.key === "nationality") {
4338              // Nationality handling (unchanged)
4339              translatedValue = value
4340                .split(',')
4341                .map(v => translateValue(v.trim()))
4342                .join(', ');
4343            } else {
4344              translatedValue = translateValue(value);
4345            }
4346
4347            htmlText = autoLinkNames(processMarkdownLinks(translatedValue), nodesMap);
4348          }
4349
4350          html += `<p style="margin-top:0.3rem;"><strong>${labelI18N(field.label)}:</strong> ${htmlText}</p>`;
4351        }
4352      });
4353
4354        const connections = [];
4355        edges.get().forEach(edge => {
4356          if (edge._isClusterEdge) return;
4357          if (edge.from === node.id || edge.to === node.id) {
4358            const otherId = edge.from === node.id ? edge.to : edge.from;
4359            if (String(otherId).startsWith('ANCHOR__')) return;
4360            const otherNode = nodes.get(otherId);
4361            if (otherNode) {
4362              connections.push({ id: otherId, name: otherNode.id || otherId || 'Unknown' });
4363            }
4364          }
4365        });
4366
4367        const degreeCalc = connections.length;
4368        html += `<p style="margin-top:0.5rem; margin-bottom:0.3rem;"><strong>${t('Connections')}:</strong> ${degreeCalc}</p>`;
4369        html += `<p style="font-size:0.85em; color:#999; margin:0.2rem 0 0.5rem 0;">🔗 = edge  &nbsp;  👤 = node</p><ul style="margin-top:0.3rem;">`;
4370
4371        connections
4372          .sort((a, b) => (a.name || '').localeCompare(b.name || ''))
4373          .forEach(conn => {
4374            html += `<li style="margin:0.25rem 0; display:flex; align-items:center; gap:0.5rem;">
4375              <span style="flex-grow:1;">
4376                <a href="#" style="color:#66ccff; font-weight:bold;" onclick="selectEdgeFromNodes('${node.id}', '${conn.id}'); return false;">${conn.name}</a>
4377              </span>
4378              <a href="#" style="color:#66ccff; font-size:1.1em; text-decoration:none;" onclick="selectEdgeFromNodes('${node.id}', '${conn.id}'); return false;" title="View edge">🔗</a>
4379              <a href="#" style="color:#999; font-size:1.1em; text-decoration:none;" onclick="focusNode('${conn.id}'); return false;" title="View node">👤</a>
4380            </li>`;
4381          });
4382
4383        html += `</ul></div>`;
4384        document.getElementById("nodeInfo").innerHTML = html;
4385        addShareButton(node.label);
4386
4387        // recordar esta selección para refrescar en cambios de idioma
4388        window.__lastSelection = { type: 'node', id: node.id };
4389
4390          } else if (params.edges.length > 0) {
4391
4392            // === Smart selection: elegir el edge correcto entre varios candidatos ===
4393            let selectedEdgeId = params.edges[0];
4394
4395            if (params.edges.length > 1) {
4396              const pointer = (params.pointer && params.pointer.canvas) ? params.pointer.canvas : null;
4397
4398              // 1) Prioriza los edges activos (conectados al nodo resaltado)
4399              let pool = params.edges.filter(id => activeEdgeIds.has(id));
4400              if (pool.length === 0) pool = params.edges.slice();
4401
4402              // 2) Si sigue habiendo varios, elige el más cercano al cursor
4403              if (pointer) {
4404                let best = pool[0];
4405                let bestDist = Infinity;
4406                for (const id of pool) {
4407                  const eObj = edges.get(id);
4408                  if (!eObj) continue;
4409                  const pos = network.getPositions([eObj.from, eObj.to]);
4410                  const p1 = pos[eObj.from], p2 = pos[eObj.to];
4411                  if (!p1 || !p2) continue;
4412                  const d = pointToSegmentDistance(pointer.x, pointer.y, p1.x, p1.y, p2.x, p2.y);
4413                  if (d < bestDist) { bestDist = d; best = id; }
4414                }
4415                selectedEdgeId = best;
4416              }
4417            }
4418
4419      // Fuerza la selección de ese edge "mejor"
4420      network.selectEdges([selectedEdgeId]);
4421      // === Fin smart selection ===
4422      clearNodeHighlightsOnly();   //  limpia el rojo de nodos del edge anterior
4423
4424      const edge = edges.get(selectedEdgeId);
4425      if (!edge) return;
4426      if (edge._isClusterEdge) return;
4427      const fromNode = nodes.get(edge.from);
4428      const toNode = nodes.get(edge.to);
4429      
4430      if (fromNode && toNode) {
4431        nodes.update([
4432          { id: fromNode.id, color: { border: 'red' }, borderWidth: 4 },
4433          { id: toNode.id, color: { border: 'red' }, borderWidth: 4 }
4434        ]);
4435        lastHighlightedNodes = [fromNode.id, toNode.id];
4436      }
4437      
4438        // ❌ Aquí viene el error: ya declaraste fromNode y toNode arriba
4439        // 🔁 Solución: elimina "const"
4440        const fromNodeMap = nodesMap[edge.from];
4441        const toNodeMap = nodesMap[edge.to];
4442
4443
4444        let html = `<div style="display:flex; align-items:center; gap:1rem; padding-bottom:1rem;">`;
4445
4446        if (fromNodeMap?.image) {
4447          html += `<img src="${fromNodeMap.image}" style="max-height:80px;">`;
4448        }
4449        if (toNodeMap?.image) {
4450          html += `<img src="${toNodeMap.image}" style="max-height:80px;">`;
4451        }
4452
4453        html += `</div>`;
4454
4455        // Título traducible
4456        html += `<h3 class="section-heading">${t('Connection')}</h3>`;
4457
4458        // Línea "Between … and …" traducible
4459        const betweenLabel = t('Between');
4460        const andWord = t('and');
4461
4462        html += `<p><strong>${betweenLabel}:</strong> 
4463          <a href="#" style="color:#66ccff; text-transform:none;" onclick="focusNode('${fromNodeMap.id}')">${fromNodeMap.id}</a> 
4464          ${andWord} 
4465          <a href="#" style="color:#66ccff; text-transform:none;" onclick="focusNode('${toNodeMap.id}')">${toNodeMap.id}</a>
4466        </p>`;
4467
4468        const edgeFields = [
4469          { key: "connection_level", label: "Type of connection" },
4470          { key: "strength", label: "Connection Strength" },
4471          { key: "relationship type", label: "Type of relationship" },
4472          { key: "know each other since", label: "Know each other since" },
4473          { key: "correspondence", label: "Correspondence" },
4474          { key: "they met", label: "They met" },
4475          { key: "shared", label: "Shared" },
4476          { key: "they shared", label: "They shared" },
4477          { key: "interactions", label: "Interactions" },
4478          { key: "introductions", label: "Introductions" },
4479          { key: "feuds", label: "Feuds" },
4480          { key: "common friends", label: "Common friends" },
4481          { key: "mentions", label: "mentions" },
4482          { key: "collaborations", label: "Collaborations" },
4483          { key: "curiosities", label: "Curiosities" },
4484          { key: "children", label: "Children" },
4485          { key: "married in", label: "Married in" },
4486          { key: "link to Goya's work", label: "Link to Goya's work" },
4487          { key: "link to Goya", label: "Link to Goya" },
4488          { key: "copies after Goya", label: "Copies after Goya" },
4489          { key: "copies", label: "Copies" },
4490          { key: "commissions Goya with", label: "Commissions Goya with" },
4491          { key: "writes about Goya", label: "Writes about Goya" },
4492          { key: "mentions of Goya", label: "Mentions of Goya" },
4493          { key: "quotations of Goya", label: "Quotations of Goya" },
4494          { key: "influence of Goya", label: "Influence of Goya" },
4495          { key: "collector of Goya's works", label: "Collector of Goya's works" },
4496          { key: "disseminates Goya's works", label: "Disseminates Goya's works" },
4497          { key: "discovers Goya's works", label: "Discovers Goya's works" },
4498          { key: "knows Goya's works", label: "Knows Goya's works" },
4499          { key: "mentions the Prado commentaries", label: "Mentions the Prado commentaries" },
4500          { key: "shows Goya", label: "Shows Goya" },
4501          { key: "artworks influenced by Goya", label: "Artworks influenced by Goya" },
4502          { key: "interest in Spanish art", label: "Interest in Spanish art" },
4503          { key: "artworks bought/sold/given", label: "Artworks bought/sold/given" },
4504          { key: "artworks bought/sold", label: "Artworks bought/sold" },
4505          { key: "comparisons", label: "Comparisons" },
4506          { key: "criticism", label: "Criticism" },
4507          { key: "rivalry", label: "Rivalry" },
4508          { key: "employment/patronage", label: "Employment/patronage" },
4509          { key: "commissions", label: "Commissions" },
4510          { key: "influence", label: "Influence" },
4511          { key: "exchanges", label: "Exchanges" },
4512          { key: "collection", label: "Collection" },
4513          { key: "patronage", label: "Patronage" },
4514          { key: "portraits", label: "Portraits" },
4515          { key: "exhibitions", label: "Exhibitions" },
4516          { key: "bibliography", label: "Bibliography" },
4517          { key: "websites", label: "Websites" },
4518          { key: "podcasts", label: "Podcasts" },
4519          { key: "newspaper archive", label: "Newspaper archive" },
4520          { key: "archives", label: "Archives" },
4521          { key: "added", label: "Added" },
4522          { key: "last_modified", label: "Last modified" }
4523        ];
4524
4525        edgeFields.forEach(field => {
4526          if (edge[field.key]) {
4527            let value = edge[field.key];
4528            let htmlText;
4529        
4530            // Debugging: Check the raw value
4531            console.log("Raw value:", value);
4532        
4533            if (Array.isArray(value)) {
4534              // ✅ Si es un array de strings u objetos con imagen
4535              const processedItems = value
4536                .map(item => {
4537
4538                  // ✅ Caso 1: array de strings
4539                  if (typeof item === "string") {
4540                    return `<li>${autoLinkNames(processMarkdownLinks(item), nodesMap)}</li>`;
4541                  }
4542
4543                  // ✅ Caso 2: objetos (y no null)
4544                  if (typeof item === "object" && item !== null) {
4545
4546                    // ✅ websites: {label, url}
4547                    if (item.label && item.url) {
4548                      const lab = autoLinkNames(processMarkdownLinks(item.label), nodesMap);
4549                      const u = String(item.url).trim();
4550                      return `<li><a href="${u}" target="_blank" rel="noopener noreferrer" style="color:#66ccff;">${lab}</a></li>`;
4551                    }
4552
4553                    // ✅ portraits/images: {caption, url}
4554                    const caption = item.caption
4555                      ? autoLinkNames(processMarkdownLinks(item.caption), nodesMap)
4556                      : "";
4557                    const url = item.url ? item.url : "";
4558                    return `<li>${caption}${url ? `<img src="${url}" alt="Portrait" style="max-width:100%; margin-left: 0.5rem; vertical-align: middle;">` : ""}</li>`;
4559                  }
4560
4561                  return null;
4562                })
4563                .filter(Boolean);
4564
4565              htmlText = `<ul>${processedItems.join("")}</ul>`;
4566            
4567            } else {
4568              let translatedValue = value;
4569
4570              if (typeof value === "string") {
4571                translatedValue = value
4572                  .split(',')
4573                  .map(v => {
4574                    const raw = v.trim();
4575
4576                    // 1) intento literal
4577                    let out = t(raw);
4578                    if (out !== raw) return out;
4579
4580                    // 2) intento con mayúscula inicial: "poet" → "Poet"
4581                    const cap = raw.charAt(0).toUpperCase() + raw.slice(1);
4582                    out = t(cap);
4583                    if (out !== cap) return out;
4584
4585                    // 3) fallback
4586                    return raw;
4587                  })
4588                  .join(', ');
4589              }
4590
4591              htmlText = autoLinkNames(processMarkdownLinks(translatedValue), nodesMap);
4592            }
4593
4594            // Debugging: Check the processed HTML
4595            console.log("Processed HTML:", htmlText);
4596        
4597            html += `<p style="margin-top:0.3rem;"><strong>${labelI18N(field.label)}:</strong> ${htmlText}</p>`;
4598          }
4599        });
4600
4601        document.getElementById("nodeInfo").innerHTML = html;
4602        updateEdgeURL(selectedEdgeId);
4603        // recordar esta selección para refrescar en cambios de idioma
4604        window.__lastSelection = { type: 'edge', id: selectedEdgeId };
4605      }
4606
4607      if (params.nodes.length === 0 && params.edges.length === 0) {
4608        clearHighlights();
4609        window.__lastSelection = null; // nada seleccionado
4610
4611        if (window.VIS_NETWORK) {
4612          window.VIS_NETWORK.unselectAll();
4613        }
4614
4615        if (typeof window.showDefaultNodeInfo === 'function') {
4616          window.showDefaultNodeInfo();
4617        }
4618
4619        return;
4620      }
4621
4622    });
4623
4624    // ===== SELECT EDGE FROM TWO NODES =====
4625    window.selectEdgeFromNodes = function (fromNodeId, toNodeId) {
4626      // Find the edge connecting these two nodes
4627      let edgeId = null;
4628      edges.get().forEach(edge => {
4629        if ((edge.from === fromNodeId && edge.to === toNodeId) ||
4630            (edge.from === toNodeId && edge.to === fromNodeId)) {
4631          edgeId = edge.id;
4632        }
4633      });
4634
4635      if (!edgeId) return;
4636
4637      clearHighlights();
4638
4639      // Get the midpoint of the edge for centering
4640      const fromPos = window.VIS_NETWORK.getPosition(fromNodeId);
4641      const toPos = window.VIS_NETWORK.getPosition(toNodeId);
4642      if (!fromPos || !toPos) return;
4643
4644      const midX = (fromPos.x + toPos.x) / 2;
4645      const midY = (fromPos.y + toPos.y) / 2;
4646
4647      // Move to the edge and select it
4648      window.VIS_NETWORK.moveTo({
4649        position: { x: midX, y: midY },
4650        scale: window.VIS_NETWORK.getScale() * 1.2
4651      });
4652
4653      // Emit click event to trigger edge display logic
4654      setTimeout(() => {
4655        window.VIS_NETWORK.emit('click', {
4656          nodes: [],
4657          edges: [edgeId],
4658          pointer: { DOM: { x: 0, y: 0 }, canvas: { x: 0, y: 0 } }
4659        });
4660      }, 100);
4661    };
4662
4663    window.focusNode = function (nodeId) {
4664      clearHighlights();
4665
4666      const scale = window.VIS_NETWORK.getScale();
4667      const pos = window.VIS_NETWORK.getPosition(nodeId);
4668
4669      window.VIS_NETWORK.moveTo({
4670        position: pos,
4671        scale
4672      });
4673
4674      window.VIS_NETWORK.selectNodes([nodeId]);
4675
4676      setTimeout(() => {
4677        window.VIS_NETWORK.emit('click', {
4678          nodes: [nodeId],
4679          edges: [],
4680          pointer: { DOM: { x: 0, y: 0 }, canvas: { x: 0, y: 0 } }
4681        });
4682      }, 50);
4683    };
4684
4685    function showShareModal(label = '') {
4686      const modal = document.getElementById('shareModal');
4687      const hash = '#' + label.replace(/ /g, '_');
4688      const shareUrl = `${window.location.origin}${window.location.pathname}${hash}`;
4689      const text = `Check out ${label} on the Goya Network`;
4690    
4691      document.getElementById('shareUrl').value = shareUrl;
4692    
4693      document.getElementById('twitterShare').href =
4694        `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(shareUrl)}`;
4695    
4696      document.getElementById('linkedinShare').href =
4697        `https://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(shareUrl)}&title=${encodeURIComponent(text)}`;
4698    
4699      document.getElementById('facebookShare').href =
4700        `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}&quote=${encodeURIComponent(text)}`;
4701    
4702      modal.style.display = 'flex';
4703    }
4704
4705    // Crear y añadir botón
4706    function addShareButton(nodeName) {
4707      const shareBtn = document.createElement('button');
4708      shareBtn.innerHTML = '<i class="fas fa-share-alt"></i> Share';
4709      shareBtn.style.marginTop = '1rem';
4710      shareBtn.style.padding = '0.5rem 1rem';
4711      shareBtn.style.background = '#333';
4712      shareBtn.style.color = 'white';
4713      shareBtn.style.border = '1px solid #555';
4714      shareBtn.style.borderRadius = '4px';
4715      const label = nodes.get(nodeName)?.label || nodeName;
4716      shareBtn.onclick = () => showShareModal(label);
4717
4718      const nodeInfo = document.getElementById('nodeInfo');
4719      if (nodeInfo) {
4720        const existingShareBtn = nodeInfo.querySelector('.share-btn');
4721        if (existingShareBtn) existingShareBtn.remove();
4722        shareBtn.className = 'share-btn';
4723        nodeInfo.appendChild(shareBtn);
4724      }
4725    }
4726
4727    function updateNodeURL(nodeId) {
4728      const newUrl = window.location.pathname + makeNodeHash(nodeId);
4729      window.history.pushState({}, '', newUrl);
4730    }
4731
4732    function updateEdgeURL(edgeId) {
4733      const edge = edges.get(edgeId);
4734      if (!edge) return '';
4735
4736      const fromNode = nodes.get(edge.from);
4737      const toNode = nodes.get(edge.to);
4738      if (!fromNode || !toNode) return '';
4739
4740      const a = fromNode.id;
4741      const b = toNode.id;
4742
4743      // Ordenar para que el hash sea consistente
4744      const ordered = [a, b].sort((x, y) =>
4745        x.localeCompare(y, undefined, { sensitivity: 'base' })
4746      );
4747
4748      const hash = '#edge/' + slugifyName(ordered[0]) + '--' + slugifyName(ordered[1]);
4749      const newUrl = window.location.pathname + hash;
4750      window.history.pushState({}, '', newUrl);
4751    }
4752
4753
4754      // Restaura el panel nodeInfo a su estado por defecto (texto + Members list)
4755      window.showDefaultNodeInfo = function () {
4756        const el = document.getElementById('nodeInfo');
4757        if (!el) return;
4758
4759        // Reconstruir SIEMPRE el panel inicial real.
4760        // No depender de __defaultNodeInfoHTML.
4761        el.innerHTML = `
4762          <p data-i18n="HELP_TEXT_1">${t('HELP_TEXT_1')}</p>
4763          <p data-i18n="HELP_TEXT_2">${t('HELP_TEXT_2')}</p>
4764
4765          <div id="newNodesSection" style="margin-top: 1rem; position: relative;">
4766            <div id="newNodesToggle"
4767                class="section-heading"
4768                data-i18n="NEW_NODES_TITLE"
4769                style="cursor:pointer; user-select:none; margin-bottom:0;">
4770              ${t('NEW_NODES_TITLE')}
4771            </div>
4772
4773            <div id="newNodesDropdown"
4774                style="display:none; margin-top:0.5rem; background:#1a1a1a; border:1px solid #444; border-radius:6px; padding:0.75rem;">
4775              <div id="newNodesList" style="line-height:1.5;"></div>
4776            </div>
4777          </div>
4778
4779          <div id="membersSection" style="margin-top: 1rem;">
4780            <div class="section-heading">${t('MEMBERS_TITLE')}</div>
4781            <div id="membersList"></div>
4782          </div>
4783        `;
4784
4785        buildMembersList(data);
4786        buildNewNodesList(data);
4787
4788        // Reenganchar el desplegable de New members
4789        const newNodesToggle = document.getElementById('newNodesToggle');
4790        const newNodesDropdown = document.getElementById('newNodesDropdown');
4791
4792        if (newNodesToggle && newNodesDropdown) {
4793          newNodesToggle.onclick = function (e) {
4794            e.stopPropagation();
4795            const isOpen = newNodesDropdown.style.display === 'block';
4796            newNodesDropdown.style.display = isOpen ? 'none' : 'block';
4797            newNodesToggle.classList.toggle('open', !isOpen);
4798          };
4799        }
4800
4801        window.__lastSelection = null;
4802
4803        if (window.VIS_NETWORK) {
4804          window.VIS_NETWORK.unselectAll();
4805        }
4806
4807        if (typeof applyUIStrings === 'function') {
4808          applyUIStrings();
4809        }
4810      };
4811
4812    // Búsqueda funcional
4813    const searchInput = document.getElementById('searchInput');
4814    const searchButton = document.querySelector('.search-button');
4815
4816    // Replace the existing searchButton event listener with this:
4817    searchButton.addEventListener('click', () => {
4818      const query = searchInput.value.trim().toLowerCase();
4819      if (!query) return;
4820    
4821      // Clear previous highlights
4822      clearHighlights();
4823    
4824      // 1. Buscar primero en nodos (prioridad)
4825      let found = data.nodes.find(n =>
4826        n.id.toLowerCase() === query || 
4827        (typeof n.label === 'string' && n.label.toLowerCase() === query)
4828      );
4829      
4830      // 2. Si no hay coincidencia exacta, buscar por startsWith
4831      if (!found) {
4832        found = data.nodes.find(n =>
4833          n.id.toLowerCase().startsWith(query) || 
4834          (typeof n.label === 'string' && n.label.toLowerCase().startsWith(query))
4835        );
4836      }
4837      
4838      // 3. Si aún no, buscar por contiene
4839      if (!found) {
4840        found = data.nodes.find(n =>
4841          n.id.toLowerCase().includes(query) || 
4842          (typeof n.label === 'string' && n.label.toLowerCase().includes(query))
4843        );
4844      }
4845      
4846      // 4. Si no está en id o label, buscar en otros campos del nodo
4847      if (!found) {
4848        found = data.nodes.find(n =>
4849          Object.entries(n).some(([key, value]) =>
4850            typeof value === 'string' &&
4851            !key.includes('image') &&
4852            !value.includes('PFayos') &&
4853            value.toLowerCase().includes(query)
4854          )
4855        );
4856      }
4857      
4858      // 5. Si aún no hay nodo, buscar en los edges
4859      if (!found) {
4860        const matchingEdge = data.edges.find(edge =>
4861          Object.entries(edge).some(([key, value]) => {
4862            if (key === "from" || key === "to" || key === "portraits") return false;
4863            return typeof value === 'string' &&
4864                  !key.includes('image') &&
4865                  value.toLowerCase().includes(query);
4866          })
4867        );
4868      
4869        if (matchingEdge) {
4870          const edge = edges.get().find(e =>
4871            e.from === matchingEdge.from && e.to === matchingEdge.to
4872          );
4873          
4874          if (edge) {
4875            network.selectEdges([edge.id]);
4876            network.emit('click', { edges: [edge.id], nodes: [] });
4877            return;
4878          }
4879        }
4880      }
4881      
4882      // 6. Mostrar resultado o alerta
4883      if (found) {
4884        window.search(found.id);
4885      } else {
4886        alert("No match found.");
4887      }
4888    });
4889
4890    // Add Enter key support
4891    searchInput.addEventListener('keydown', (e) => {
4892      if (e.key === 'Enter') {
4893        e.preventDefault();
4894        searchButton.click();
4895      }
4896    });
4897
4898        window.toggleFullScreen = function () {
4899          const elem = document.documentElement;
4900          if (!document.fullscreenElement) {
4901            elem.requestFullscreen().catch(err => {
4902              alert(`Error trying to enable full-screen mode: ${err.message}`);
4903            });
4904          } else {
4905            document.exitFullscreen();
4906          }
4907        }
4908
4909  // Activar física solo mientras se arrastra un nodo
4910  /*network.on("dragStart", () => {
4911    network.setOptions({ physics: { enabled: true } });
4912  });
4913  
4914  network.on("dragEnd", () => {
4915    network.setOptions({ physics: { enabled: false } });
4916  });*/
4917
4918  window.returnToNetworkHome = function () {
4919    clearHighlights();
4920    window.__lastSelection = null;
4921
4922    if (window.VIS_NETWORK) {
4923      window.VIS_NETWORK.unselectAll();
4924    }
4925
4926    if (typeof window.showDefaultNodeInfo === 'function') {
4927      window.showDefaultNodeInfo();
4928    }
4929  };
4930    
4931  } catch (err) {
4932    console.error("Error cargando o renderizando la red:", err);
4933  }
4934});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.