From cb468b0f3c0621fe8f4ff84d81262c6c34b20a70 Mon Sep 17 00:00:00 2001 From: bruno Date: Wed, 28 Oct 2026 08:48:54 -0400 Subject: [PATCH 1/2] fix(sidebar): style console, statut dans le titre, composer console, synchro domaine --- frontend/draco-panneau.js | 355 +++++++++++++++++++++++++++++++++++--- 1 file changed, 332 insertions(+), 23 deletions(-) diff --git a/frontend/draco-panneau.js b/frontend/draco-panneau.js index ff2ce2d..f8f9610 100644 --- a/frontend/draco-panneau.js +++ b/frontend/draco-panneau.js @@ -40,7 +40,10 @@ var convKind = null; // 'principale' | 'parallele' var convTitre = ""; var etatConnu = false; + var domaineCharge = null; // domaine des discussions affichées + var consoleUrlCharge = null; // URL (disc+domaine+app) de l'iframe console var root = null, msgsEl = null, etatEl = null, inputEl = null, sendBtn = null; + var microEl = null, piecesEl = null, fichierEl = null; var titreEl = null, tiroirEl = null; var versionEl = null, versionConnue = "", versionMaj = 0; @@ -68,11 +71,16 @@ return correspondance[h] || null; } function urlConsole() { + /* Le domaine de l'hôte est passé explicitement : la console + * vit sur une autre origine, son localStorage ne voit jamais + * celui de l'app hôte — sans ce paramètre, elle rouvrait + * toujours le domaine de sa propre origine, pas celui du site. */ var params = []; var id = convId || convStockee(); if (id) params.push("disc=" + encodeURIComponent(id)); var app = appHote(); if (app) params.push("app=" + encodeURIComponent(app)); + params.push("domain=" + encodeURIComponent(domaine())); return API + "/" + (params.length ? "?" + params.join("&") : ""); } @@ -147,7 +155,15 @@ function ouvrirConsole() { assurerSurimpression(); - if (!surCadre.getAttribute("src")) surCadre.src = urlConsole(); + /* L'iframe n'est rechargée que si sa cible a changé + * (discussion, domaine ou app) : même cible = état conservé, + * cible différente = la console s'ajuste, au lieu de garder + * la discussion du premier chargement. */ + var url = urlConsole(); + if (!surCadre.getAttribute("src") || url !== consoleUrlCharge) { + surCadre.src = url; + consoleUrlCharge = url; + } if (surOuverte) return; surOuverte = true; /* Naissance à la géométrie du panneau (ancrée à droite), puis @@ -175,7 +191,12 @@ setTimeout(terminer, 380); // filet si transitionend ne vient pas surEl.style.width = largeurPanneau() + "px"; } - function modeSidebarConsole() { retrecirConsole(); } + function modeSidebarConsole() { + retrecirConsole(); + /* Retour au panneau : si le domaine a changé pendant que la + * console occupait tout l'écran, le panneau s'ajuste aussi. */ + resynchroniserDomaine(); + } function fermerAgentConsole() { fermer(); retrecirConsole(); } /* ── Thème : suivre l'app hôte ─────────────────────────────── */ @@ -246,13 +267,10 @@ '
' + '
' + ' ✦' + - '
Spark — agent commun
' + - '
Service d\'agents dracodev
' + + '
Spark — agent commun
' + + '
Connexion au service…
' + ' ' + '
' + - '
' + - ' Connexion au service…' + - '
' + '
' + ' Discussion principale' + ' ' + @@ -266,17 +284,40 @@ ' ' + '
' + '
' + - '
' + - ' ' + - '
' + - '
Conversation réelle, sous-agents (J3) et missions longues (J5) vers les exécuteurs configurés ; toute écriture dans les apps passe par une approbation.
' + + '
' + + '
' + + ' ' + + ' ' + + ' ' + + '
' + + '
' + + ' ' + + '
' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + '
' + + ' ' + + '
' + ' ' + '
'; document.body.appendChild(root); msgsEl = root.querySelector(".draco-panneau-msgs"); - etatEl = root.querySelector(".draco-panneau-etat"); - inputEl = root.querySelector("input"); - sendBtn = root.querySelector("button[type=submit]"); + etatEl = root.querySelector(".draco-panneau-hd .soustitre"); + inputEl = root.querySelector(".draco-composer textarea"); + sendBtn = root.querySelector(".draco-composer button[type=submit]"); + microEl = root.querySelector('[data-comp="micro"]'); + piecesEl = root.querySelector(".draco-composer-pieces"); + fichierEl = root.querySelector(".draco-composer input[type=file]"); titreEl = root.querySelector(".d-titre"); tiroirEl = root.querySelector(".draco-discussions"); versionEl = root.querySelector(".draco-panneau-version"); @@ -288,6 +329,42 @@ root.querySelector('[data-outil="approbations"]').addEventListener("click", chargerApprobations); root.querySelector('[data-outil="missions"]').addEventListener("click", ouvrirMissions); root.querySelector("form").addEventListener("submit", envoyer); + inputEl.addEventListener("input", majComposer); + inputEl.addEventListener("keydown", function (ev) { + if (ev.key === "Enter" && !ev.shiftKey) { ev.preventDefault(); envoyerComposer(); } + }); + root.querySelector('[data-comp="plus"]').addEventListener("click", function () { fichierEl.click(); }); + fichierEl.addEventListener("change", function (ev) { + ajouterFichiers(ev.target.files); + ev.target.value = ""; + }); + microEl.addEventListener("click", demarrerDictee); + root.querySelector('[data-comp="enreg-stop"]').addEventListener("click", function () { arreterDictee("stop"); }); + root.querySelector('[data-comp="enreg-annuler"]').addEventListener("click", function () { arreterDictee("annuler"); }); + root.querySelector('[data-comp="enreg-envoyer"]').addEventListener("click", function () { arreterDictee("envoyer"); }); + Array.prototype.forEach.call(root.querySelectorAll("[data-sugg]"), function (b) { + b.addEventListener("click", function () { + var mode = b.getAttribute("data-sugg"); + if (mode === "resume") { + envoyerMessage("Résume ma journée : mes tâches du jour, les cartes en Révision et l'activité récente.", []); + } else if (mode === "projets") { + envoyerMessage("Où en sont mes projets ? Fais le point sur l'état des projets et du board.", []); + } else { + inputEl.value = "Cherche « » partout : "; + majComposer(); + inputEl.focus(); + inputEl.setSelectionRange(9, 9); + } + }); + }); + /* Domaine changé dans un autre onglet pendant que le panneau + * existe : l'événement storage ne part que vers les autres + * documents, donc ici il arrive bien. Le changement dans le + * même onglet est rattrapé à chaque ouverture (ouvrir()). */ + window.addEventListener("storage", function (ev) { + if (ev.key === "draco_domain") resynchroniserDomaine(); + }); + majComposer(); brancherPoignee(root.querySelector(".draco-poignee")); appliquerTheme(); surveillerTheme(); @@ -326,8 +403,13 @@ } function majEtat(cls, texte) { - etatEl.className = "draco-panneau-hd draco-panneau-etat " + cls; - etatEl.querySelector(".txt").textContent = texte; + /* Statut intégré au titre (sous-titre de l'en-tête) : plus de + * barre d'état séparée. Le point vert/rouge/gris reste lisible + * sans prendre une rangée entière. */ + if (!etatEl) return; + etatEl.className = "soustitre " + cls; + var t = etatEl.querySelector(".txt"); + if (t) t.textContent = texte; } function majTitre() { titreEl.textContent = convTitre || "Discussion principale"; @@ -400,9 +482,21 @@ .then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); }); } + /* Recharge la discussion du domaine courant quand il a changé + * depuis le dernier chargement (ouverture du panneau, autre + * onglet, retour du mode console). Sans changement : rien — + * on ne casse jamais une conversation en cours pour rien. */ + function resynchroniserDomaine() { + if (!root || domaine() === domaineCharge) return; + msgsEl.innerHTML = ""; + tiroirEl.hidden = true; + demarrerDiscussions(); + } + function demarrerDiscussions() { chargerListeDiscussions() .then(function (d) { + domaineCharge = domaine(); var precedente = convStockee(); var candidates = (d.paralleles || []).concat(d.principale ? [d.principale] : []); var trouvee = candidates.filter(function (c) { return c.id === precedente; })[0]; @@ -413,6 +507,7 @@ chargerHistorique(); }) .catch(function () { + domaineCharge = null; // échec : la prochaine ouverture réessaiera ajouterMsg("sys", "Discussions : service injoignable pour le moment."); }); } @@ -780,19 +875,232 @@ .catch(function (e) { ajouterMsg("sys", "Lancement refusé : " + e.message); }); } - function envoyer(ev) { - ev.preventDefault(); - var texte = inputEl.value.trim(); - if (!texte) return; + /* ── Composer façon console ──────────────────────────────── + * Mêmes règles que la console : + (images/documents, 4 max), + * textarea auto-extensible, micro affiché seulement sans texte + * ni pièce et si le navigateur le supporte, envoi rond bleu + * affiché seulement avec texte ou pièce. */ + var piecesEnAttente = []; + var TYPES_IMAGES = ["image/png", "image/jpeg", "image/gif", "image/webp"]; + var TYPES_DOCUMENTS = ["application/pdf", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.oasis.opendocument.text", + "text/plain", "text/markdown", "text/html", "text/csv"]; + var TAILLE_MAX_IMAGE = 5 * 1024 * 1024; + var TAILLE_MAX_DOC = 10 * 1024 * 1024; + + function majComposer() { + if (!inputEl) return; + var aTexte = !!(inputEl.value && inputEl.value.trim()); + sendBtn.hidden = !(aTexte || piecesEnAttente.length); + if (microEl) microEl.hidden = !dicteeSupportee || aTexte || piecesEnAttente.length; + inputEl.style.height = "auto"; + var sh = inputEl.scrollHeight; + inputEl.style.height = sh ? Math.min(sh, 152) + "px" : ""; + } + + function rendrePiecesEnAttente() { + piecesEl.innerHTML = ""; + piecesEl.hidden = !piecesEnAttente.length; + piecesEnAttente.forEach(function (p, i) { + var carte = document.createElement("div"); + carte.className = "piece-carte"; + var apercu; + if (TYPES_IMAGES.indexOf(p.type) >= 0) { + apercu = document.createElement("img"); + apercu.src = p.dataUrl; apercu.alt = p.nom; + } else { + apercu = document.createElement("span"); + apercu.className = "piece-doc-ext"; + var ext = document.createElement("span"); + ext.className = "piece-ext"; + ext.textContent = (p.nom || "").split(".").pop() || "doc"; + apercu.appendChild(ext); + } + var nom = document.createElement("span"); + nom.className = "piece-nom"; nom.textContent = p.nom; nom.title = p.nom; + var x = document.createElement("button"); + x.type = "button"; x.className = "piece-retirer"; x.textContent = "✕"; + x.title = "Retirer " + p.nom; + x.setAttribute("aria-label", "Retirer " + p.nom); + x.addEventListener("click", function () { + piecesEnAttente.splice(i, 1); + rendrePiecesEnAttente(); majComposer(); + }); + carte.appendChild(apercu); carte.appendChild(nom); carte.appendChild(x); + piecesEl.appendChild(carte); + }); + } + + function ajouterFichiers(fichiers) { + Array.prototype.forEach.call(fichiers, function (f) { + if (piecesEnAttente.length >= 4) { + ajouterMsg("sys", "Au plus 4 pièces par message."); + return; + } + var estImage = TYPES_IMAGES.indexOf(f.type) >= 0; + var estDoc = TYPES_DOCUMENTS.indexOf(f.type) >= 0; + if (!estImage && !estDoc) { + ajouterMsg("sys", "« " + f.name + " » refusée : images PNG, JPEG, GIF ou WebP, ou documents PDF, DOCX, ODT, TXT, MD, HTML ou CSV seulement."); + return; + } + if (estImage && f.size > TAILLE_MAX_IMAGE) { + ajouterMsg("sys", "« " + f.name + " » refusée : 5 Mo maximum par image."); + return; + } + if (estDoc && f.size > TAILLE_MAX_DOC) { + ajouterMsg("sys", "« " + f.name + " » refusée : 10 Mo maximum par document."); + return; + } + var lecteur = new FileReader(); + lecteur.onload = function () { + var dataUrl = String(lecteur.result || ""); + piecesEnAttente.push({ + nom: f.name, type: f.type, + base64: dataUrl.split(",")[1] || "", dataUrl: dataUrl + }); + rendrePiecesEnAttente(); majComposer(); + }; + lecteur.readAsDataURL(f); + }); + } + + function envoyerComposer() { + var t = inputEl.value; + var pieces = piecesEnAttente; + piecesEnAttente = []; inputEl.value = ""; - ajouterMsg("user", texte); + rendrePiecesEnAttente(); + majComposer(); + envoyerMessage(t, pieces); + } + + /* ── Dictée vocale — Web Speech API, 100 % navigateur, comme + * la console (fr-CA, résultats intérimaires en direct). */ + var RecoVoc = window.SpeechRecognition || window.webkitSpeechRecognition; + var dicteeSupportee = !!RecoVoc; + var reco = null, enregEnCours = false, enregDebut = 0, enregMinuteur = null; + var texteAvantDictee = "", transcriptionFinale = ""; + var modeFinDictee = "stop", dicteeErreur = null; + + function demarrerDictee() { + if (!dicteeSupportee || enregEnCours) return; + texteAvantDictee = inputEl.value; + transcriptionFinale = ""; + modeFinDictee = "stop"; + dicteeErreur = null; + try { reco = new RecoVoc(); } catch (e) { reco = null; return; } + reco.lang = "fr-CA"; + reco.interimResults = true; + reco.continuous = true; + reco.onresult = function (ev) { + var interim = ""; + for (var i = ev.resultIndex; i < ev.results.length; i++) { + var r = ev.results[i]; + if (r.isFinal) transcriptionFinale += r[0].transcript; + else interim += r[0].transcript; + } + var affiche = (texteAvantDictee + " " + transcriptionFinale + interim).trim(); + root.querySelector(".enreg-texte").textContent = affiche; + inputEl.value = affiche; + }; + reco.onerror = function (ev) { + if (ev.error === "aborted") return; + dicteeErreur = ev.error || "erreur"; + modeFinDictee = "annuler"; + }; + reco.onend = terminerDictee; + try { reco.start(); } catch (e) { reco = null; return; } + enregEnCours = true; + enregDebut = Date.now(); + root.querySelector(".enreg-temps").textContent = "0:00"; + root.querySelector(".enreg-texte").textContent = ""; + var onde = root.querySelector(".enreg-onde"); + if (!onde.children.length) { + for (var j = 0; j < 14; j++) onde.appendChild(document.createElement("span")); + } + root.querySelector(".draco-composer-ligne").hidden = true; + root.querySelector(".draco-composer-enreg").hidden = false; + root.querySelector(".draco-composer").classList.add("en-cours"); + enregMinuteur = window.setInterval(function () { + var s = Math.floor((Date.now() - enregDebut) / 1000); + root.querySelector(".enreg-temps").textContent = Math.floor(s / 60) + ":" + ("0" + (s % 60)).slice(-2); + }, 250); + } + + function arreterDictee(mode) { + modeFinDictee = mode; + if (reco) { + try { reco.stop(); } catch (e) { terminerDictee(); } + } else { + terminerDictee(); + } + } + + function terminerDictee() { + if (!enregEnCours) return; + enregEnCours = false; + if (enregMinuteur) { window.clearInterval(enregMinuteur); enregMinuteur = null; } + reco = null; + root.querySelector(".draco-composer-ligne").hidden = false; + root.querySelector(".draco-composer-enreg").hidden = true; + root.querySelector(".draco-composer").classList.remove("en-cours"); + if (modeFinDictee === "annuler") { + inputEl.value = texteAvantDictee; + if (dicteeErreur) { + ajouterMsg("sys", "Dictée vocale interrompue (" + dicteeErreur + ") — micro refusé ou indisponible ?"); + dicteeErreur = null; + } + } else { + inputEl.value = (texteAvantDictee + " " + transcriptionFinale).trim(); + } + var mode = modeFinDictee; + modeFinDictee = "stop"; + majComposer(); + if (mode === "envoyer") envoyerComposer(); + else inputEl.focus(); + } + + function envoyer(ev) { + if (ev) ev.preventDefault(); + envoyerComposer(); + } + + function envoyerMessage(texte, pieces) { + texte = (texte || "").trim(); + pieces = pieces || []; + if (!texte && !pieces.length) return; + var el = ajouterMsg("user", texte); + if (pieces.length) { + var zone = document.createElement("div"); + zone.className = "draco-msg-pieces"; + pieces.forEach(function (p) { + if (TYPES_IMAGES.indexOf(p.type) >= 0) { + var img = document.createElement("img"); + img.src = p.dataUrl; img.alt = p.nom; + zone.appendChild(img); + } else { + var past = document.createElement("span"); + past.className = "piece-fichier"; + past.textContent = p.nom; + zone.appendChild(past); + } + }); + el.appendChild(zone); + } sendBtn.disabled = true; var attente = ajouterMsg("agent", "Spark réfléchit…"); + var corps = { message: texte, domain: domaine(), conversation_id: convId }; + if (pieces.length) { + corps.pieces = pieces.map(function (p) { + return { nom: p.nom, type: p.type, donnees_base64: p.base64 }; + }); + } fetch(API + "/api/v1/chat", { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ message: texte, domain: domaine(), conversation_id: convId }) + body: JSON.stringify(corps) }) .then(function (r) { if (r.status === 401) { @@ -831,11 +1139,12 @@ attente.textContent = "Service injoignable. Vérifie que le conteneur dracodev-agent est démarré, puis réessaie."; majEtat("err", "Service d'agents injoignable"); }) - .finally(function () { sendBtn.disabled = false; inputEl.focus(); }); + .finally(function () { sendBtn.disabled = false; majComposer(); inputEl.focus(); }); } function ouvrir() { if (!root) { construire(); demarrerDiscussions(); } + else resynchroniserDomaine(); root.classList.add("open"); appliquerTheme(); verifierIdentite(); -- 2.54.0 From dcec510451e75c8c388d0db08a855715fc230ca6 Mon Sep 17 00:00:00 2001 From: bruno Date: Wed, 28 Oct 2026 08:49:05 -0400 Subject: [PATCH 2/2] =?UTF-8?q?fix(sidebar):=20CSS=20composer=20console,?= =?UTF-8?q?=20statut=20dans=20le=20titre,=20pied=20=C3=A9pur=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/draco-panneau.css | 106 +++++++++++++++++++++++++++++++------ 1 file changed, 89 insertions(+), 17 deletions(-) diff --git a/frontend/draco-panneau.css b/frontend/draco-panneau.css index d337013..a17fcd0 100644 --- a/frontend/draco-panneau.css +++ b/frontend/draco-panneau.css @@ -37,16 +37,19 @@ display: flex; align-items: center; gap: 9px; padding: 13px 15px; border-bottom: 1px solid var(--p-bord); background: var(--p-surface); } -.draco-panneau-hd .titre { font-weight: 700; font-size: 15px; } -.draco-panneau-hd .soustitre { font-size: 12px; color: var(--p-doux); } +.draco-panneau-titres { min-width: 0; flex: 1; } +.draco-panneau-hd .titre { font-weight: 700; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.draco-panneau-hd .soustitre { + font-size: 12px; color: var(--p-doux); display: flex; align-items: center; gap: 6px; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.draco-panneau-hd .soustitre .point { width: 8px; height: 8px; border-radius: 50%; background: #b9bdb2; flex: none; } +.draco-panneau-hd .soustitre.ok .point { background: #2ea36b; } +.draco-panneau-hd .soustitre.err .point { background: #cf4b32; } .draco-panneau-hd .fermer { margin-left: auto; border: 0; background: var(--p-agent); color: var(--p-texte); - border-radius: 8px; width: 30px; height: 30px; cursor: pointer; font-size: 14px; + border-radius: 8px; width: 30px; height: 30px; cursor: pointer; font-size: 14px; flex: none; } -.draco-panneau-etat { display: flex; align-items: center; gap: 7px; font-size: 12.5px; } -.draco-panneau-etat .point { width: 9px; height: 9px; border-radius: 50%; background: #b9bdb2; flex: none; } -.draco-panneau-etat.ok .point { background: #2ea36b; } -.draco-panneau-etat.err .point { background: #cf4b32; } /* Barre des discussions (façon Muse) */ .draco-disc-bar { display: flex; align-items: center; gap: 7px; @@ -112,18 +115,87 @@ .draco-msg.user { align-self: flex-end; background: #2f6fed; color: #fff; border-bottom-right-radius: 4px; } .draco-msg.agent { align-self: flex-start; background: var(--p-agent); border-bottom-left-radius: 4px; } .draco-msg.sys { align-self: center; background: none; color: var(--p-doux); font-size: 12.5px; text-align: center; max-width: 96%; } -.draco-panneau-ft { border-top: 1px solid var(--p-bord); padding: 11px 12px; background: var(--p-surface); } -.draco-panneau-form { display: flex; gap: 8px; } -.draco-panneau-form input { - flex: 1; border: 1px solid var(--p-chip-bord); border-radius: 10px; padding: 9px 11px; - font: inherit; background: var(--p-champ); color: var(--p-texte); +.draco-panneau-ft { padding: 10px 12px 11px; background: var(--p-fond); } +/* Puces de suggestions, comme la console : au-dessus du composer. */ +.draco-puces { + display: flex; flex-wrap: wrap; gap: 7px; justify-content: center; padding: 0 2px 8px; } -.draco-panneau-form button { - border: 0; border-radius: 10px; background: var(--p-accent); color: #fff; - padding: 9px 14px; font: inherit; font-weight: 600; cursor: pointer; +.draco-puces button { + border: 1px solid var(--p-chip-bord); background: var(--p-chip); color: var(--p-texte); + border-radius: 999px; padding: 5px 12px; font: 600 12.5px system-ui; cursor: pointer; +} +.draco-puces button:hover { background: var(--p-chip-survol); } +/* ── Composer façon console (pilule) ───────────────────────── + * +, textarea auto-extensible, micro ou envoi rond bleu ↑ ; + * pendant la dictée, la rangée d'enregistrement remplace la saisie. */ +.draco-composer { + display: flex; flex-direction: column; align-items: stretch; gap: 6px; + background: var(--p-surface); border: 1px solid var(--p-bord); + border-radius: 24px; padding: 8px 10px; + box-shadow: 0 1px 4px rgb(0 0 0 / .05); +} +.draco-composer [hidden] { display: none !important; } +.draco-composer-ligne { display: flex; align-items: flex-end; gap: 6px; } +.draco-composer textarea { + flex: 1; border: 0; background: none; resize: none; outline: none; + padding: 8px 2px; max-height: 152px; overflow-y: auto; line-height: 1.45; + min-height: calc(1.45em + 16px); font: inherit; color: var(--p-texte); + scrollbar-width: none; +} +.draco-composer textarea::-webkit-scrollbar { width: 0; height: 0; display: none; } +.draco-composer .composer-btn { + width: 36px; height: 36px; border-radius: 50%; border: 0; flex: none; + background: var(--p-chip); color: var(--p-texte); font-size: 17px; + display: flex; align-items: center; justify-content: center; padding: 0; cursor: pointer; +} +.draco-composer .composer-btn:hover { background: var(--p-chip-survol); } +.draco-composer .composer-btn.danger { color: var(--p-danger); } +.draco-composer .composer-envoi { + width: 36px; height: 36px; border-radius: 50%; border: 0; flex: none; + background: #2f6fed; color: #fff; font-size: 17px; font-weight: 700; + display: flex; align-items: center; justify-content: center; padding: 0; cursor: pointer; +} +.draco-composer .composer-envoi:disabled { opacity: .5; } +.draco-composer .composer-btn .ic, .draco-composer .composer-envoi .ic { width: 19px; height: 19px; } +.draco-composer-pieces { display: flex; flex-wrap: wrap; gap: 8px; padding: 2px 2px 0; } +.draco-composer-pieces .piece-carte { + position: relative; display: flex; align-items: center; gap: 8px; + background: var(--p-chip); border: 1px solid var(--p-chip-bord); + border-radius: 12px; padding: 5px 9px 5px 5px; max-width: 230px; +} +.draco-composer-pieces .piece-carte img { width: 42px; height: 42px; object-fit: cover; border-radius: 8px; display: block; } +.draco-composer-pieces .piece-nom { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.draco-composer-pieces .piece-doc-ext { + width: 42px; height: 42px; display: flex; align-items: center; justify-content: center; + background: var(--p-chip); border: 1px solid var(--p-chip-bord); border-radius: 8px; +} +.draco-composer-pieces .piece-ext { font-weight: 700; font-size: 10px; letter-spacing: .04em; text-transform: uppercase; opacity: .75; } +.draco-composer-pieces .piece-retirer { + position: absolute; top: -7px; right: -7px; width: 20px; height: 20px; + border-radius: 50%; border: 0; background: var(--p-texte); color: var(--p-fond); + font-size: 10px; line-height: 1; padding: 0; cursor: pointer; + display: flex; align-items: center; justify-content: center; +} +.draco-composer-enreg { display: flex; align-items: center; gap: 10px; padding: 0 2px; } +.draco-composer-enreg .enreg-temps { font-variant-numeric: tabular-nums; font-weight: 700; min-width: 36px; } +.draco-composer-enreg .enreg-texte { + flex: 1; min-width: 0; color: var(--p-doux); font-size: 13px; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.draco-composer-enreg .enreg-onde { display: flex; align-items: center; gap: 3px; height: 26px; flex: none; } +.draco-composer-enreg .enreg-onde span { width: 4px; height: 30%; border-radius: 2px; background: #2f6fed; } +.draco-composer.en-cours .enreg-onde span { animation: onde 1.05s ease-in-out infinite; } +.draco-composer-enreg .enreg-onde span:nth-child(3n+2) { animation-delay: .14s; } +.draco-composer-enreg .enreg-onde span:nth-child(3n) { animation-delay: .28s; } +@keyframes onde { 0%, 100% { height: 22%; } 50% { height: 96%; } } +/* Pièces jointes dans une bulle utilisateur */ +.draco-msg-pieces { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 7px; } +.draco-msg-pieces img { max-width: 150px; max-height: 120px; border-radius: 10px; display: block; } +.draco-msg-pieces .piece-fichier { + display: inline-flex; align-items: center; gap: 7px; + background: var(--p-chip); border: 1px solid var(--p-chip-bord); color: var(--p-texte); + border-radius: 9px; padding: 6px 9px; font-size: 12px; } -.draco-panneau-form button:disabled { opacity: .5; cursor: default; } -.draco-panneau-note { font-size: 11.5px; color: var(--p-faint); margin-top: 8px; text-align: center; } .draco-missions { align-self: stretch; background: var(--p-surface); border: 1px solid var(--p-bord); border-radius: 12px; padding: 10px 12px; } .draco-missions h5 { font-size: 12px; text-transform: uppercase; letter-spacing: .4px; color: var(--p-doux); margin: 0 0 6px; } .draco-missions label { display: block; font-size: 12px; font-weight: 600; color: var(--p-doux); margin: 8px 0 3px; } -- 2.54.0