{# Carte d'un module Scormia (bibliothèque) — même langage visuel que les cartes de cours du dashboard Moodia (pill de type, titre 2 lignes, stats inline, rangée méta, footer daté), avec les données Scormia : parties/écrans, quiz, score de réussite, accent du module. Requiert : module. #} {# Stats dérivées des écrans et blocs du module. #} {% set ecrans = module.screens|length %} {% set parties = module.screens|filter(s => s.nodeType == 'chapter')|length %} {% set quiz = module.screens|reduce((c, s) => c + (s.blocks|filter(b => b.type == 'quiz')|length), 0) %} {# Couleur identité de l'outil Scormia (indigo) — pour la conso tokens ; indépendante de l'accent du module (var(--acc)) qui, lui, peut varier par thème. #} {% set scormiaColor = (tool_color_details['scormia'] ?? {}).primary ?? '#1E40AF' %} {# Métriques de la rangée de tags en tête de carte (type + « Partagé »). Ces deux tags sont une PAIRE : ils doivent partager leurs métriques littéralement et ne se distinguer que par la couleur. D'où une seule chaîne, utilisée par les deux — les recopier serait se condamner à les voir diverger au prochain ajustement, ce qui est exactement ce qui était arrivé. Et non l'atome badge : sa table de tailles (h-6 px-2.5 text-xs font-medium) est calibrée pour d'autres contextes, et il n'expose ni casse ni interlettrage. Lui passer ces métriques en `class` reviendrait à parier sur l'ordre des utilitaires Tailwind dans la feuille compilée pour trancher les conflits. Retoucher l'atome est exclu : il sert partout dans l'application. (Motif déjà employé dans templates/slidia/index.html.twig : triggerBase, optBase.) #} {% set cardTagBase = 'inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[11px] font-semibold uppercase tracking-wide' %} {# Le dataset de partage est porté par la RACINE de carte, comme côté Slidia (_presentation_card.html.twig) : shared--share-link#writeBack y écrit les trois valeurs après chaque succès serveur, et y LIT l'état d'avant écriture pour dire aux listes si le partage a réellement changé. Sans rendu initial, cette lecture vaut `undefined` au premier passage — la première révocation après chargement de page passerait alors pour un non-changement, laissant une carte fantôme sous l'onglet « Partagés » et un compteur faux. Les trois attributs vont ensemble : n'en poser qu'un laisserait le dataset à moitié initialisé et le même piège au suivant. #}
{# Bordure supérieure à l'accent au hover (animation scale, comme Moodia) #}
{# Contenu principal (flex-grow pour pousser le footer en bas) #}
{# Header : pill de type à l'accent du module, suivi du tag « Partagé ». Le tag vit DANS le flux (et non plus en absolute) : posé au-dessus de la carte, il chevauchait ce pill. Il reste à gauche, jamais aligné à droite, pour ne pas passer sous le menu ⋯ (absolute top-3 right-3). Toujours rendu (pas de {% if %}) : shared--share-link#save() bascule son display sans recharger la page (cf. [data-module-card] plus haut, retrouvé par le contrôleur depuis le bouton « Partager » du menu). #}
{{ quiz > 0 ? 'Module + Quiz' : 'Module SCORM' }} {# Mêmes métriques que son voisin (cardTagBase), gris neutre volontaire : le tag ne doit pas concurrencer l'accent du module. L'icône est contrainte à 10 px en CSS, et non par le paramètre `size` de l'atome : son sizeMap PLANCHE à 12 px ('xs'), et lui passer un nombre ne marche pas — `sizeMap[10]` étant indéfini, le `?? 20` de l'atome ferait une icône de 20 px. Or 12 px est déjà trop gros à côté d'un texte de 11 px en capitales (hauteur de capitale ≈ 8 px). D'où la règle CSS, qui l'emporte sur les attributs width/height du SVG. Ne pas « simplifier » ceci en `size:` : la régression serait invisible en test. data-share-badge est sur CET élément : shared--share-link#writeBack bascule son style.display (cf. share_link_controller.js) ; le remettre à '' restitue l'inline-flex de la classe. #} {% include '_atoms/icon/icon.html.twig' with {name: 'share', size: 'xs', color: 'current'} only %} Partagé
{# Titre (hauteur fixe pour 2 lignes) #}

{{ module.title }}

{# Stats inline #}

{{ parties }} partie{{ parties > 1 ? 's' }} · {{ ecrans }} écran{{ ecrans > 1 ? 's' }}

{# Metadata row #}
{% include '_atoms/icon/icon.html.twig' with { name: 'check-circle', size: 'sm', color: 'gray' } only %} {{ quiz }} quiz {% if quiz > 0 %} {% include '_atoms/icon/icon.html.twig' with { name: 'target', size: 'sm', color: 'gray' } only %} Réussite {{ module.passingScore }} % {% endif %} {% if module.language %} {% include '_atoms/icon/icon.html.twig' with { name: 'earth', size: 'sm', color: 'gray' } only %} {{ module.language|capitalize }} {% endif %}
{# Footer daté + consommation IA à droite — même design que les cartes de cours Moodia (date à gauche ; tokens colorés + énergie en ambre). Tokens à l'accent du module (var(--acc), équivalent au text-primary de Moodia) ; énergie en ambre à l'identique. Affiché seulement pour un module généré par IA (tokensUsed renseigné). La date est RELATIVE, comme celle des cartes Slidia : sur une bibliothèque qu'on consulte pour retrouver ce qu'on vient de faire, « Il y a 26 min » répond à la question posée, là où « 3 août 2026 » oblige à la calculer. #}
{{ module.updatedAt|relative_date }}
{# Tokens — couleur identité Scormia (indigo), comme le text-primary des cartes Moodia #} {% if module.tokensUsed %} {% include '_atoms/icon/icon.html.twig' with { name: 'token', size: 'sm', color: 'current' } only %} {{ module.tokensUsed|format_tokens }} {% endif %} {# Énergie — ambre (identique aux cartes Moodia) #} {% if module.energyConsumptionWh %} {% include '_atoms/icon/icon.html.twig' with { name: 'bolt', size: 'sm', color: 'warning' } only %} {{ module.energyConsumptionWh|format_energy }} {% endif %}
{# Menu d'actions ⋯ (accent du module), hors du pour éviter la navigation au clic #}
{# ORDRE CANONIQUE des menus de carte, identique dans tous les outils — voir `_molecules/action-menu/action-menu.html.twig`. #} {% include '_molecules/action-menu/action-menu.html.twig' with { withTrigger: true, position: 'right-0 top-full mt-1', items: [ {label: 'Partager', icon: 'share', attr: { 'data-action': 'click->scormia--library#openShareModal', 'data-uuid': module.uuid, 'data-shared': module.isShared ? '1' : '0', 'data-download': module.isShareDownloadEnabled ? '1' : '0', 'data-share-url': module.isShared ? url('app_public_scormia_preview', {token: module.shareToken}) : '' }}, {label: 'Exporter (ZIP)', icon: 'download', href: path('app_scormia_export', { uuid: module.uuid })}, {label: 'Ajouter à Moodia', icon: 'bubul-moodia', attr: { 'data-action': 'click->shared--add-to-moodia#open', 'data-podcast-id': module.uuid, 'data-podcast-title': module.title }}, {label: 'Renommer', icon: 'edit', attr: { 'data-action': 'click->scormia--library#openRenameModal', 'data-uuid': module.uuid, 'data-title': module.title }}, {label: 'Dupliquer', icon: 'copy', attr: { 'data-action': 'click->scormia--library#duplicate', 'data-scormia--library-uuid-param': module.uuid }} ], dangerItems: [ {label: 'Supprimer', icon: 'trash', attr: { 'data-action': 'click->scormia--library#openDeleteModal', 'data-uuid': module.uuid, 'data-title': module.title }} ], accentHex: module.accent } only %}