Switch icons from a webfont to self-hosted SVG masks — the webfont was the actual problem

Self-hosting Font Awesome's font files (previous commit) didn't fix it either: every icon in the picker still failed identically. That rules out CDN/network blocking specifically and points to something in the browser blocking custom webfont loading altogether regardless of origin (common with strict anti-fingerprinting protection, e.g. Brave's font blocking or certain privacy extensions — @font-face loading is a well-known fingerprinting vector).

Replaced the whole mechanism: each of the 258 curated icons is now a real downloaded SVG file (static/icons/*.svg, sourced from Font Awesome 5 Free's official SVG package) displayed via CSS mask-image (.icon-svg in style.css) instead of a font glyph. A masked SVG isn't a font resource at all, so it isn't subject to webfont-blocking — and it still colors via the existing "color" style property (background-color:currentColor) and sizes via font-size (1em), so no change to how the widget's other controls work.

The "Icône" widget now stores just the icon's slug (e.g. "trophy") in a dedicated attribute instead of a full CSS class string, rendered by a new special_render (render_icone.py). Updated the add-gallery and the properties-panel picker modal to use the same mask technique for their previews, and element_add.py to validate the slug against the curated list. Removed the now-unused self-hosted Font Awesome CSS/webfont files and the <link> tags — no font dependency left for icons at all.

Verified end to end (gallery renders with real SVG previews, simulated add creates a correctly-attributed element, the SVG file is actually served, the per-element picker reflects the current icon). Full suite green (89).
This commit is contained in:
william
2026-08-24 20:11:49 +02:00
parent 15cad6c50f
commit 6d80ed6958
270 changed files with 346 additions and 46 deletions
+11 -12
View File
@@ -5,7 +5,6 @@
{% block extra_head %}
<link rel="stylesheet" href="{{ google_fonts_link }}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='fontawesome/css/all.min.css') }}">
{% endblock %}
{% block breadcrumb %}
<li><a href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a></li>
@@ -163,19 +162,19 @@
</div>
<div class="floatPanelBody">
<details class="propGroup">
<summary>🔤 Icônes (Font Awesome)</summary>
<summary>🔤 Icônes</summary>
<div class="propGroupBody">
<input type="text" class="input is-small" id="iconGallerySearch" placeholder="Rechercher une icône..." oninput="filterIconGallery(this.value)">
<div class="iconGallery" id="iconGallery">
{% for slug in fontawesome_icons %}
<button type="button" class="iconGalleryTile" draggable="true"
data-icon-class="fa-solid fa-{{ slug }}" data-icon-search="{{ slug }}"
title="fa-{{ slug }}" onclick="addIconElement('fa-solid fa-{{ slug }}')">
<i class="fa-solid fa-{{ slug }}"></i>
data-icon-class="{{ slug }}" data-icon-search="{{ slug }}"
title="{{ slug }}" onclick="addIconElement('{{ slug }}')">
<i class="icon-svg" style="--icon-url:url('/static/icons/{{ slug }}.svg');"></i>
</button>
{% endfor %}
</div>
<p class="help">Glisse une icône sur le canevas, ou clique dessus pour l'ajouter — sa couleur et sa taille se règlent ensuite dans ses propriétés ("Icône"). Sélection non exhaustive : n'importe quelle autre classe Font Awesome 5 reste utilisable en la tapant directement dans le réglage "Icône".</p>
<p class="help">Glisse une icône sur le canevas, ou clique dessus pour l'ajouter — sa couleur et sa taille se règlent ensuite dans ses propriétés ("Icône"). Sélection non exhaustive : d'autres icônes pourront être ajoutées à cette liste plus tard si besoin.</p>
</div>
</details>
@@ -196,8 +195,8 @@
<div class="iconGallery" id="iconPickerGrid" style="max-height:50vh;">
{% for slug in fontawesome_icons %}
<button type="button" class="iconGalleryTile" data-icon-search="{{ slug }}"
title="fa-{{ slug }}" onclick="chooseIcon('fa-solid fa-{{ slug }}')">
<i class="fa-solid fa-{{ slug }}"></i>
title="{{ slug }}" onclick="chooseIcon('{{ slug }}')">
<i class="icon-svg" style="--icon-url:url('/static/icons/{{ slug }}.svg');"></i>
</button>
{% endfor %}
</div>
@@ -372,7 +371,7 @@
{% if c.key == 'icon_class' %}
<div class="control">
<button type="button" class="button is-fullwidth" onclick="openIconPicker()">
<span class="icon"><i id="iconPickerPreview" class="{{ c.value or c.default }}"></i></span>
<span class="icon"><i id="iconPickerPreview" class="icon-svg" style="--icon-url:url('/static/icons/{{ c.value or c.default }}.svg');"></i></span>
<span>Changer l'icône</span>
</button>
<input type="hidden" name="ctrl_{{ c.key }}" id="field-{{ c.key }}" value="{{ c.value or c.default }}">
@@ -934,11 +933,11 @@ function closeIconPicker() {
if (modal) modal.classList.remove('is-active');
}
function chooseIcon(iconClass) {
function chooseIcon(iconSlug) {
var input = document.getElementById('field-icon_class');
var preview = document.getElementById('iconPickerPreview');
if (input) input.value = iconClass;
if (preview) preview.className = iconClass;
if (input) input.value = iconSlug;
if (preview) preview.style.setProperty('--icon-url', "url('/static/icons/" + iconSlug + ".svg')");
closeIconPicker();
submitPropsForm();
}