diff --git a/CHANGELOG.md b/CHANGELOG.md
index d1a7860..8d4d7d8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,15 @@
Toutes les modifications importantes du projet sont consignées ici. Le format s’inspire de [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/) et le projet suit le [versionnage sémantique](https://semver.org/lang/fr/).
+## [1.0.0-rc.18] — 2026-09-09
+
+### Finitions du site public
+
+- présence des mentions légales et de la politique de confidentialité dans le pied de chaque page publique ;
+- insertion guidée des images de la bibliothèque dans les textes, avec description alternative ;
+- ajout d’une image sans rechargement de la page ni perte des textes en cours de saisie ;
+- présentation des réseaux sociaux sous forme de liens identifiables, colorés et accessibles.
+
## [1.0.0-rc.17] — 2026-09-08
### Interface mobile et médias publics
diff --git a/HISTORIQUE.md b/HISTORIQUE.md
index 57479cd..bfbf5b0 100644
--- a/HISTORIQUE.md
+++ b/HISTORIQUE.md
@@ -77,3 +77,7 @@ Création d’un parcours de mise à jour commun à l’interface et à la ligne
## `1.0.0-rc.17` — Utilisation mobile
Remplacement de la barre de navigation horizontale par un menu mobile repliable et fiabilisation des permissions appliquées aux nouvelles photos publiques.
+
+## `1.0.0-rc.18` — Finitions du site public
+
+Déplacement des informations légales dans le pied de page, insertion guidée des images sans perdre les textes en cours d’édition et identification visuelle des réseaux sociaux.
diff --git a/README.md b/README.md
index aa7ab98..aa0a1fb 100644
--- a/README.md
+++ b/README.md
@@ -200,7 +200,7 @@ L’option `--apply` ne doit être utilisée qu’après lecture des rapports. L
## État du projet
-La candidate actuelle est `1.0.0-rc.17`. La branche 1.0 est gelée fonctionnellement : seuls les défauts bloquants découverts pendant les validations terrain sont corrigés avant `1.0.0`.
+La candidate actuelle est `1.0.0-rc.18`. La branche 1.0 est gelée fonctionnellement : seuls les défauts bloquants découverts pendant les validations terrain sont corrigés avant `1.0.0`.
Le contrôle de stabilisation est disponible dans [docs/AUDIT-1.0.md](docs/AUDIT-1.0.md).
diff --git a/VERSION b/VERSION
index 17e3421..4744cd3 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0.0-rc.17
+1.0.0-rc.18
diff --git a/app/Controllers/SettingsController.php b/app/Controllers/SettingsController.php
index bac8d80..35d2ff4 100644
--- a/app/Controllers/SettingsController.php
+++ b/app/Controllers/SettingsController.php
@@ -775,8 +775,22 @@ final class SettingsController
$filename = ImageService::storeUploaded((string) $upload['tmp_name'], $directory, 'site', 'photo');
@chmod($directory, 0755);
AuditService::log('public_site_image_uploaded', '/settings/public-site/image', 'Image du site public ajoutée');
+ if (str_contains((string) ($_SERVER['HTTP_ACCEPT'] ?? ''), 'application/json')) {
+ header('Content-Type: application/json; charset=utf-8');
+ echo json_encode([
+ 'filename' => $filename,
+ 'url' => '/media/site/' . rawurlencode($filename),
+ ], JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
+ exit();
+ }
header('Location: /settings/public-site?image=' . rawurlencode($filename));
} catch (Throwable $e) {
+ if (str_contains((string) ($_SERVER['HTTP_ACCEPT'] ?? ''), 'application/json')) {
+ http_response_code(422);
+ header('Content-Type: application/json; charset=utf-8');
+ echo json_encode(['error' => SecurityService::publicError($e)], JSON_THROW_ON_ERROR);
+ exit();
+ }
header('Location: /settings/public-site?error=' . rawurlencode(SecurityService::publicError($e)));
}
exit();
diff --git a/app/Views/evolutions.php b/app/Views/evolutions.php
index 93ed841..00b7f17 100644
--- a/app/Views/evolutions.php
+++ b/app/Views/evolutions.php
@@ -31,10 +31,10 @@
-
1.0.0-rc.17
-
Utilisation mobile
-
Navigation repliable sur les petits écrans et publication fiable des nouvelles
- photographies, y compris depuis l’application Android.
+
1.0.0-rc.18
+
Site public
+
Informations légales accessibles sur chaque page, insertion guidée des images
+ dans les contenus et liens sociaux plus lisibles.
diff --git a/app/Views/public_site_contact.php b/app/Views/public_site_contact.php
index 5599cbe..f2a29a9 100644
--- a/app/Views/public_site_contact.php
+++ b/app/Views/public_site_contact.php
@@ -13,11 +13,16 @@ $obfuscate = static fn (string$value): string => implode('', array_reverse(mb_st
✉ = h($obfuscate($settings['association_email'])) ?>
-
- 'Facebook','instagram' => 'Instagram','youtube' => 'YouTube','tiktok' => 'TikTok','whatsapp' => 'WhatsApp','linkedin' => 'LinkedIn','x' => 'X','snapchat' => 'Snapchat'] as $network => $label):
+
+ ['Facebook','f'], 'instagram' => ['Instagram','◎'], 'youtube' => ['YouTube','▶'],
+ 'tiktok' => ['TikTok','♪'], 'whatsapp' => ['WhatsApp','☎'], 'linkedin' => ['LinkedIn','in'],
+ 'x' => ['X','𝕏'], 'snapchat' => ['Snapchat','👻'],
+ ];
+ foreach ($socialNetworks as $network => [$label,$icon]):
$url = trim((string)($settings['public_site_' . $network . '_url'] ?? ''));
if ($url === '') continue; ?>
-
= h($label) ?>
+
= h($icon) ?>= h($label) ?>
@@ -27,8 +32,6 @@ $obfuscate = static fn (string$value): string => implode('', array_reverse(mb_st
-
diff --git a/resources/lang/en.php b/resources/lang/en.php
index 2fbc98d..9626db6 100644
--- a/resources/lang/en.php
+++ b/resources/lang/en.php
@@ -120,7 +120,7 @@ return [
'accounting.document_save_failed'=>'The document could not be saved.',
'search.title'=>'Global search','search.placeholder'=>'Animal, contact, appointment or invoice…','search.action'=>'Search','search.hint'=>'Enter at least two characters.','search.none'=>'No accessible result.','search.open'=>'Open global search','search.shortcut'=>'Ctrl K','search.group.animals'=>'Animals','search.group.directory'=>'Directory','search.group.agenda'=>'Calendar','search.group.accounting'=>'Accounting','search.loading'=>'Searching…','search.all_results'=>'Show all results','search.error'=>'Search is temporarily unavailable.',
'public_site.settings_heading'=>'Public website','public_site.theme_custom'=>'Custom','public_site.site_name'=>'Website name','public_site.custom_css'=>'Custom CSS','public_site.custom_css_help'=>'Used by the custom theme. Imports and external resources are blocked.','public_site.invalid_css'=>'The stylesheet contains a forbidden instruction.','public_site.routing_management_help'=>'Management stays at / and the public website at /site.','public_site.publish_animal'=>'Publish this animal on the public website','public_site.publish_animal_help'=>'The profile will only be visible when the animal is also available for adoption. Only public photos and the text below are used.','lab_reference.add_species'=>'Add a species to lab reports','lab_reference.copy_from'=>'Copy reference ranges from','lab_reference.target_species'=>'New species','lab_reference.copy_help'=>'Ranges are copied as a starting point and remain editable.','lab_reference.invalid_species'=>'Choose two different valid species.','lab_reference.nothing_copied'=>'This species already has these reference ranges.','statistics.hair_types'=>'Hair types','animals.filtered_by'=>'Filter: :value','service.icad.home_unavailable'=>'The I-CAD website is unavailable','service.icad.search_unavailable'=>'I-CAD search is unavailable','service.icad.record_unavailable'=>'The I-CAD record is unavailable',
- 'public_site.image_library'=>'Image library','public_site.image_file'=>'Image to add','public_site.image_upload'=>'Upload image','public_site.image_help'=>'Upload an image, place the cursor in a text field, then choose “Insert”. Accepted formats: JPEG, PNG and WebP.','public_site.image_none'=>'No image uploaded.','public_site.image_insert'=>'Insert','public_site.image_invalid'=>'Choose a valid image.',
+ 'public_site.image_library'=>'Image library','public_site.image_file'=>'Image to add','public_site.image_upload'=>'Upload image','public_site.image_help'=>'From the text you want to enrich, choose “Insert”, then select an image. Accepted formats: JPEG, PNG and WebP.','public_site.image_none'=>'No image uploaded.','public_site.image_insert'=>'Insert','public_site.image_invalid'=>'Choose a valid image.','public_site.image_alt_prompt'=>'Briefly describe the image for people who cannot see it:','public_site.image_default_alt'=>'Association image','public_site.image_target_selected'=>'Text selected: now choose an image from the library.',
'rooms.duplicate'=>'A room with this name or code already exists.','settings.species_duplicate'=>'A species with this name or code already exists.',
'settings.invalid_rna'=>'The RNA number must start with W followed by 9 digits.','settings.invalid_siret'=>'The SIRET number must contain 14 digits and be valid.','rooms.name_too_long'=>'A room name cannot exceed 100 characters.','rooms.box_name_too_long'=>'A box name cannot exceed 100 characters.','animal.name_too_long'=>'An animal name cannot exceed 150 characters.',
'pricing.adoption_fees_help'=>'Indicative amounts shared by all animals, according to their sterilisation status.','rooms.inactive_help'=>'An unchecked room becomes inactive, while its previous assignments remain in the history. Its internal code never changes.',
diff --git a/resources/lang/fr.php b/resources/lang/fr.php
index 4762674..febc145 100644
--- a/resources/lang/fr.php
+++ b/resources/lang/fr.php
@@ -120,7 +120,7 @@ return [
'accounting.document_save_failed'=>'Impossible d’enregistrer le document.',
'search.title'=>'Recherche globale','search.placeholder'=>'Animal, contact, rendez-vous ou facture…','search.action'=>'Rechercher','search.hint'=>'Saisissez au moins deux caractères.','search.none'=>'Aucun résultat accessible.','search.open'=>'Ouvrir la recherche globale','search.shortcut'=>'Ctrl K','search.group.animals'=>'Animaux','search.group.directory'=>'Annuaire','search.group.agenda'=>'Agenda','search.group.accounting'=>'Comptabilité','search.loading'=>'Recherche…','search.all_results'=>'Afficher tous les résultats','search.error'=>'La recherche est momentanément indisponible.',
'public_site.settings_heading'=>'Site public','public_site.theme_custom'=>'Personnalisé','public_site.site_name'=>'Nom du site','public_site.custom_css'=>'CSS personnalisé','public_site.custom_css_help'=>'Actif avec le thème personnalisé. Les imports et les ressources externes sont bloqués.','public_site.invalid_css'=>'La feuille de style contient une instruction interdite.','public_site.routing_management_help'=>'La gestion reste sur / et le site public sur /site.','public_site.publish_animal'=>'Publier cet animal sur le site public','public_site.publish_animal_help'=>'La fiche ne sera visible que si l’animal est également disponible à l’adoption. Seules les photos publiques et le texte ci-dessous seront utilisés.','lab_reference.add_species'=>'Ajouter une espèce aux bilans','lab_reference.copy_from'=>'Copier les références de','lab_reference.target_species'=>'Nouvelle espèce','lab_reference.copy_help'=>'Les intervalles sont copiés comme point de départ, puis restent modifiables.','lab_reference.invalid_species'=>'Choisissez deux espèces différentes et valides.','lab_reference.nothing_copied'=>'Cette espèce possède déjà ces références.','statistics.hair_types'=>'Types de poil','animals.filtered_by'=>'Filtre : :value','service.icad.home_unavailable'=>'Le site I-CAD est indisponible','service.icad.search_unavailable'=>'La recherche I-CAD est indisponible','service.icad.record_unavailable'=>'La fiche I-CAD est indisponible',
- 'public_site.image_library'=>'Bibliothèque d’images','public_site.image_file'=>'Image à ajouter','public_site.image_upload'=>'Ajouter l’image','public_site.image_help'=>'Ajoutez une image, placez le curseur dans un texte puis choisissez « Insérer ». Formats acceptés : JPEG, PNG et WebP.','public_site.image_none'=>'Aucune image ajoutée.','public_site.image_insert'=>'Insérer','public_site.image_invalid'=>'Choisissez une image valide.',
+ 'public_site.image_library'=>'Bibliothèque d’images','public_site.image_file'=>'Image à ajouter','public_site.image_upload'=>'Ajouter l’image','public_site.image_help'=>'Depuis le texte à enrichir, choisissez « Insérer » puis sélectionnez une image. Formats acceptés : JPEG, PNG et WebP.','public_site.image_none'=>'Aucune image ajoutée.','public_site.image_insert'=>'Insérer','public_site.image_invalid'=>'Choisissez une image valide.','public_site.image_alt_prompt'=>'Décrivez brièvement l’image pour les personnes qui ne peuvent pas la voir :','public_site.image_default_alt'=>'Image de l’association','public_site.image_target_selected'=>'Texte sélectionné : choisissez maintenant une image dans la bibliothèque.',
'rooms.duplicate'=>'Une salle portant ce nom ou ce code existe déjà.','settings.species_duplicate'=>'Une espèce portant ce nom ou ce code existe déjà.',
'settings.invalid_rna'=>'Le numéro RNA doit commencer par W et être suivi de 9 chiffres.','settings.invalid_siret'=>'Le numéro SIRET doit contenir 14 chiffres et être valide.','rooms.name_too_long'=>'Le nom d’une salle ne peut pas dépasser 100 caractères.','rooms.box_name_too_long'=>'Le nom d’un box ne peut pas dépasser 100 caractères.','animal.name_too_long'=>'Le nom d’un animal ne peut pas dépasser 150 caractères.',
'pricing.adoption_fees_help'=>'Montants indicatifs communs à tous les animaux, selon leur état de stérilisation.','rooms.inactive_help'=>'Une salle décochée devient inactive, mais ses anciennes affectations restent dans l’historique. Son code interne ne change jamais.',