Compare commits
No commits in common. "master" and "1.0.5" have entirely different histories.
12 changed files with 6989 additions and 8400 deletions
10
.idea/.gitignore
vendored
10
.idea/.gitignore
vendored
|
|
@ -1,10 +0,0 @@
|
||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Ignored default folder with query files
|
|
||||||
/queries/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
|
|
@ -1,443 +0,0 @@
|
||||||
import { Controller } from '@hotwired/stimulus';
|
|
||||||
|
|
||||||
export default class extends Controller {
|
|
||||||
#canvas;
|
|
||||||
#tip;
|
|
||||||
#resizeHandler;
|
|
||||||
|
|
||||||
connect() {
|
|
||||||
const root = this.element;
|
|
||||||
const $ = id => root.querySelector("#" + id);
|
|
||||||
|
|
||||||
// debug
|
|
||||||
try{ console.debug && console.debug('calculateur: connect', root); }catch(e){}
|
|
||||||
|
|
||||||
// Store references for cleanup
|
|
||||||
this.#canvas = $("aero-chart");
|
|
||||||
this.#tip = $("aero-tip");
|
|
||||||
|
|
||||||
const C = {};
|
|
||||||
const readConsts = () => {
|
|
||||||
try {
|
|
||||||
const ct = $("aero-ct"); if(ct) C.CT=parseFloat(ct.value);
|
|
||||||
const sair = $("aero-sair"); if(sair) C.sAir=parseFloat(sair.value);
|
|
||||||
const g = $("aero-g"); if(g) C.g=parseFloat(g.value);
|
|
||||||
const sea = $("aero-sea"); if(sea) C.sea=parseFloat(sea.value);
|
|
||||||
const k = $("aero-k"); if(k) C.k=parseFloat(k.value);
|
|
||||||
const pmax = $("aero-pmax"); if(pmax) C.pMax=parseFloat(pmax.value);
|
|
||||||
const ytop = $("aero-ytop"); if(ytop) C.yTop=parseFloat(ytop.value);
|
|
||||||
const ybot = $("aero-ybot"); if(ybot) C.yBot=parseFloat(ybot.value);
|
|
||||||
} catch(err) {
|
|
||||||
console.error('calculateur:readConsts error:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const pressure = (y) => {
|
|
||||||
let p = Math.exp(C.k * (y - C.sea));
|
|
||||||
if (p > C.pMax) p = C.pMax;
|
|
||||||
return p < 0 ? 0 : p;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Y_OFFSET = 0.5;
|
|
||||||
const propThrust = (P, N) => C.CT * Math.pow(Math.max(P, 0), 1.5) * N;
|
|
||||||
const balloonForce = (V, y) => Math.max(V, 0) * C.sAir * C.g * pressure(y);
|
|
||||||
const balloonLift = (V, y) => Math.max(V, 0) * C.sAir * pressure(y);
|
|
||||||
|
|
||||||
const maxAltitude = (V, m) => {
|
|
||||||
if (V <= 0 || m <= 0) return { y: C.yTop, status: "ceiling" };
|
|
||||||
const t = m / (V * C.sAir);
|
|
||||||
if (t > C.pMax) return { y: C.yBot, status: "grounded" };
|
|
||||||
let y = C.sea + Math.log(t) / C.k;
|
|
||||||
if (y >= C.yTop) return { y: C.yTop, status: "ceiling" };
|
|
||||||
if (y <= C.yBot) return { y: C.yBot, status: "bottom" };
|
|
||||||
return { y, status: "ok" };
|
|
||||||
};
|
|
||||||
|
|
||||||
const requiredVolume = (m, y) => m / (C.sAir * pressure(y));
|
|
||||||
|
|
||||||
const nf = new Intl.NumberFormat("fr-FR", { maximumFractionDigits: 0 });
|
|
||||||
const nf2 = new Intl.NumberFormat("fr-FR", { maximumFractionDigits: 2 });
|
|
||||||
const nf3 = new Intl.NumberFormat("fr-FR", { maximumFractionDigits: 3 });
|
|
||||||
const big = (x) => {
|
|
||||||
if (!isFinite(x)) return "n/a";
|
|
||||||
return Math.abs(x) >= 1000 ? nf.format(x) : nf2.format(x);
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateProp = () => {
|
|
||||||
try {
|
|
||||||
const sails = $("aero-sails");
|
|
||||||
const rpm = $("aero-rpm");
|
|
||||||
const palt = $("aero-palt");
|
|
||||||
const P = sails ? parseFloat(sails.value) || 0 : 0;
|
|
||||||
const N = rpm ? parseFloat(rpm.value) || 0 : 0;
|
|
||||||
const y = palt ? parseFloat(palt.value) || 0 : 0;
|
|
||||||
const T = propThrust(P, N);
|
|
||||||
const thrust = $("aero-othrust");
|
|
||||||
const thrustapp = $("aero-othrustapp");
|
|
||||||
if (thrust) thrust.innerHTML = big(Math.abs(T)) + ' <small>pN</small>';
|
|
||||||
if (thrustapp) thrustapp.innerHTML = big(Math.abs(T) * pressure(y + Y_OFFSET)) + ' <small>pN</small>';
|
|
||||||
try { console.debug && console.debug('calculateur:updateProp', { P, N, y, T }); } catch (e) {}
|
|
||||||
} catch(err) {
|
|
||||||
console.error('calculateur:updateProp error:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateBalloon = () => {
|
|
||||||
try {
|
|
||||||
const vol = $("aero-vol");
|
|
||||||
const balt = $("aero-balt");
|
|
||||||
const V = vol ? parseFloat(vol.value) || 0 : 0;
|
|
||||||
const y = balt ? parseFloat(balt.value) || 0 : 0;
|
|
||||||
const force = $("aero-oforce");
|
|
||||||
const lift = $("aero-olift");
|
|
||||||
const press = $("aero-opress");
|
|
||||||
if (force) force.innerHTML = big(balloonForce(V, y)) + ' <small>pN</small>';
|
|
||||||
if (lift) lift.innerHTML = big(balloonLift(V, y)) + ' <small>kpg</small>';
|
|
||||||
if (press) press.textContent = nf3.format(pressure(y));
|
|
||||||
try { console.debug && console.debug('calculateur:updateBalloon', { V, y }); } catch (e) {}
|
|
||||||
} catch(err) {
|
|
||||||
console.error('calculateur:updateBalloon error:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateVolume = () => {
|
|
||||||
try {
|
|
||||||
const vmass = $("aero-vmass");
|
|
||||||
const valt = $("aero-valt");
|
|
||||||
const m = vmass ? parseFloat(vmass.value) || 0 : 0;
|
|
||||||
const y = valt ? parseFloat(valt.value) || 0 : 0;
|
|
||||||
const V = requiredVolume(m, y);
|
|
||||||
const volreq = $("aero-ovolreq");
|
|
||||||
const cube = $("aero-ocube");
|
|
||||||
const warn = $("aero-vwarn");
|
|
||||||
if (volreq) volreq.innerHTML = big(V) + ' <small>m³</small>';
|
|
||||||
if (cube) cube.textContent = (isFinite(V) && V > 0)
|
|
||||||
? "≈ cube de " + nf2.format(Math.cbrt(V)) + " blocs de côté · " + nf.format(Math.ceil(V)) + " blocs d'air"
|
|
||||||
: "";
|
|
||||||
if (warn) warn.innerHTML = (pressure(y) <= 0)
|
|
||||||
? "<b>Altitude hors atmosphère :</b> pression nulle, aucun volume fini ne suffit." : "";
|
|
||||||
try { console.debug && console.debug('calculateur:updateVolume', { m, y, V }); } catch (e) {}
|
|
||||||
} catch(err) {
|
|
||||||
console.error('calculateur:updateVolume error:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---- graphe ----
|
|
||||||
let mode = "mass";
|
|
||||||
// prefer document-level IDs for canvas/tip (unique across page)
|
|
||||||
let canvas = null;
|
|
||||||
let tip = null;
|
|
||||||
let ctx = null;
|
|
||||||
let geom = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
canvas = document.getElementById("aero-chart");
|
|
||||||
tip = document.getElementById("aero-tip");
|
|
||||||
if (!canvas || !tip) {
|
|
||||||
throw new Error('canvas or tip element not found');
|
|
||||||
}
|
|
||||||
this.#canvas = canvas;
|
|
||||||
this.#tip = tip;
|
|
||||||
ctx = canvas.getContext("2d");
|
|
||||||
if (!ctx) {
|
|
||||||
throw new Error('2d context not available');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('calculateur: graph init failed:', err.message);
|
|
||||||
try{ root.dataset.calculateurConnected = '1'; }catch(e){}
|
|
||||||
// mark as connected but disable graph features
|
|
||||||
canvas = null;
|
|
||||||
tip = null;
|
|
||||||
ctx = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tv = (n) => getComputedStyle(root).getPropertyValue(n).trim() || "#888";
|
|
||||||
|
|
||||||
const niceStep = (raw) => {
|
|
||||||
const p = Math.pow(10, Math.floor(Math.log10(raw)));
|
|
||||||
const n = raw / p;
|
|
||||||
return (n < 1.5 ? 1 : n < 3 ? 2 : n < 7 ? 5 : 10) * p;
|
|
||||||
};
|
|
||||||
|
|
||||||
const abbr = (v) => {
|
|
||||||
const a = Math.abs(v);
|
|
||||||
if (a >= 1e6) return nf2.format(v / 1e6) + "M";
|
|
||||||
if (a >= 1e3) return nf2.format(v / 1e3) + "k";
|
|
||||||
return nf.format(v);
|
|
||||||
};
|
|
||||||
|
|
||||||
const drawChart = () => {
|
|
||||||
// skip if graph not available
|
|
||||||
if (!canvas || !ctx) return;
|
|
||||||
const dpr = window.devicePixelRatio || 1;
|
|
||||||
const cssW = canvas.parentElement.clientWidth;
|
|
||||||
const cssH = Math.max(260, Math.round(cssW * 0.44));
|
|
||||||
canvas.style.height = cssH + "px";
|
|
||||||
canvas.width = Math.round(cssW * dpr);
|
|
||||||
canvas.height = Math.round(cssH * dpr);
|
|
||||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
||||||
ctx.clearRect(0, 0, cssW, cssH);
|
|
||||||
|
|
||||||
const padL = 54, padR = 16, padT = 14, padB = 38, pw = cssW - padL - padR, ph = cssH - padT - padB;
|
|
||||||
const muted = tv("--aero-muted"), border = tv("--aero-border"), accent = tv("--aero-accent"), warn = tv("--aero-warn");
|
|
||||||
|
|
||||||
let xMin, xMax, xLabel, fixed;
|
|
||||||
if (mode === "mass") {
|
|
||||||
const fixvol = $("aero-fixvol");
|
|
||||||
fixed = Math.max(1, fixvol ? parseFloat(fixvol.value) || 1 : 1);
|
|
||||||
xMin = 1;
|
|
||||||
xMax = Math.max(2, fixed * C.sAir * C.pMax * 1.15);
|
|
||||||
xLabel = "poids m (kpg)";
|
|
||||||
} else {
|
|
||||||
const fixmass = $("aero-fixmass");
|
|
||||||
fixed = Math.max(1, fixmass ? parseFloat(fixmass.value) || 1 : 1);
|
|
||||||
xMin = Math.max(1, fixed / (C.sAir * C.pMax) * 0.5);
|
|
||||||
xMax = Math.max(xMin * 1.5, 4 * fixed / C.sAir);
|
|
||||||
xLabel = "volume V (m³)";
|
|
||||||
}
|
|
||||||
const yMin = C.yBot, yMax = C.yTop;
|
|
||||||
const X = x => padL + (x - xMin) / (xMax - xMin) * pw;
|
|
||||||
const Y = y => padT + (1 - (y - yMin) / (yMax - yMin)) * ph;
|
|
||||||
|
|
||||||
ctx.font = '11px ' + (getComputedStyle(root).fontFamily || "sans-serif");
|
|
||||||
|
|
||||||
// grille
|
|
||||||
ctx.strokeStyle = border;
|
|
||||||
ctx.fillStyle = muted;
|
|
||||||
ctx.lineWidth = 1;
|
|
||||||
ctx.textBaseline = "middle";
|
|
||||||
ctx.textAlign = "right";
|
|
||||||
const yStep = niceStep((yMax - yMin) / 6);
|
|
||||||
for (let yy = Math.ceil(yMin / yStep) * yStep; yy <= yMax; yy += yStep) {
|
|
||||||
const py = Y(yy);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(padL, py);
|
|
||||||
ctx.lineTo(padL + pw, py);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.fillText(String(yy), padL - 7, py);
|
|
||||||
}
|
|
||||||
ctx.textAlign = "center";
|
|
||||||
ctx.textBaseline = "top";
|
|
||||||
const xStep = niceStep((xMax - xMin) / 6);
|
|
||||||
for (let xx = Math.ceil(xMin / xStep) * xStep; xx <= xMax; xx += xStep) {
|
|
||||||
const px = X(xx);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(px, padT);
|
|
||||||
ctx.lineTo(px, padT + ph);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.fillText(abbr(xx), px, padT + ph + 7);
|
|
||||||
}
|
|
||||||
ctx.fillStyle = muted;
|
|
||||||
ctx.fillText(xLabel, padL + pw / 2, padT + ph + 22);
|
|
||||||
ctx.save();
|
|
||||||
ctx.translate(13, padT + ph / 2);
|
|
||||||
ctx.rotate(-Math.PI / 2);
|
|
||||||
ctx.fillText("altitude max y", 0, 0);
|
|
||||||
ctx.restore();
|
|
||||||
|
|
||||||
const refLine = (yv, label) => {
|
|
||||||
if (yv < yMin || yv > yMax) return;
|
|
||||||
const py = Y(yv);
|
|
||||||
ctx.strokeStyle = muted;
|
|
||||||
ctx.setLineDash([4, 4]);
|
|
||||||
ctx.lineWidth = 1;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(padL, py);
|
|
||||||
ctx.lineTo(padL + pw, py);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.setLineDash([]);
|
|
||||||
ctx.fillStyle = muted;
|
|
||||||
ctx.textAlign = "left";
|
|
||||||
ctx.textBaseline = "bottom";
|
|
||||||
ctx.fillText(label, padL + 5, py - 2);
|
|
||||||
};
|
|
||||||
refLine(C.sea, "niveau mer " + C.sea);
|
|
||||||
refLine(C.yTop, "limite " + C.yTop);
|
|
||||||
|
|
||||||
// courbe
|
|
||||||
const steps = 240, pts = [];
|
|
||||||
for (let i = 0; i <= steps; i++) {
|
|
||||||
const x = xMin + (xMax - xMin) * i / steps;
|
|
||||||
const r = (mode === "mass") ? maxAltitude(fixed, x) : maxAltitude(x, fixed);
|
|
||||||
pts.push({ x, y: r.y, status: r.status });
|
|
||||||
}
|
|
||||||
|
|
||||||
// zone grounded
|
|
||||||
ctx.fillStyle = warn;
|
|
||||||
ctx.globalAlpha = .10;
|
|
||||||
let gs = null;
|
|
||||||
for (let i = 0; i <= steps; i++) {
|
|
||||||
const g = pts[i].status === "grounded";
|
|
||||||
if (g && gs === null) gs = pts[i].x;
|
|
||||||
if ((!g || i === steps) && gs !== null) {
|
|
||||||
ctx.fillRect(X(gs), padT, X(pts[i].x) - X(gs), ph);
|
|
||||||
gs = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.globalAlpha = 1;
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.strokeStyle = accent;
|
|
||||||
ctx.beginPath();
|
|
||||||
let started = false;
|
|
||||||
for (const p of pts) {
|
|
||||||
if (p.status === "grounded") {
|
|
||||||
started = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const px = X(p.x), py = Y(p.y);
|
|
||||||
if (!started) {
|
|
||||||
ctx.moveTo(px, py);
|
|
||||||
started = true;
|
|
||||||
} else {
|
|
||||||
ctx.lineTo(px, py);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
geom = { padL, pw, padT, ph, xMin, xMax, X, Y, fixed };
|
|
||||||
};
|
|
||||||
|
|
||||||
// Event listeners (only if canvas available)
|
|
||||||
if (canvas && tip) {
|
|
||||||
canvas.addEventListener("mousemove", e => {
|
|
||||||
if (!geom) return;
|
|
||||||
const rect = canvas.getBoundingClientRect();
|
|
||||||
const mx = e.clientX - rect.left;
|
|
||||||
if (mx < geom.padL || mx > geom.padL + geom.pw) {
|
|
||||||
tip.style.opacity = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const x = geom.xMin + (mx - geom.padL) / geom.pw * (geom.xMax - geom.xMin);
|
|
||||||
const r = (mode === "mass") ? maxAltitude(geom.fixed, x) : maxAltitude(x, geom.fixed);
|
|
||||||
tip.textContent = r.status === "grounded" ? abbr(x) + " : ne décolle pas"
|
|
||||||
: r.status === "ceiling" ? abbr(x) + " : plafond (y " + Math.round(r.y) + ")"
|
|
||||||
: abbr(x) + " : y " + Math.round(r.y);
|
|
||||||
tip.style.left = geom.X(x) + "px";
|
|
||||||
tip.style.top = geom.Y(r.y) + "px";
|
|
||||||
tip.style.opacity = 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
canvas.addEventListener("mouseleave", () => {
|
|
||||||
tip.style.opacity = 0;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (!canvas) console.warn('calculateur: canvas not available, graph interactions disabled');
|
|
||||||
if (!tip) console.warn('calculateur: tip not available, graph tooltip disabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Affiche uniquement l'input correspondant au mode courant
|
|
||||||
const applyModeVisibility = () => {
|
|
||||||
const fixvolwrap = $("aero-fixvolwrap");
|
|
||||||
const fixmasswrap = $("aero-fixmasswrap");
|
|
||||||
if (fixvolwrap) fixvolwrap.style.display = mode === "mass" ? "" : "none";
|
|
||||||
if (fixmasswrap) fixmasswrap.style.display = mode === "vol" ? "" : "none";
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---- Partage par URL (paramètres GET, tous optionnels) ----
|
|
||||||
// Clé = id de l'input sans le préfixe "aero-". On ne sérialise que les
|
|
||||||
// valeurs qui diffèrent de leur valeur par défaut → URLs courtes.
|
|
||||||
const paramInputs = Array.from(root.querySelectorAll("input, select"))
|
|
||||||
.filter(el => el.id && el.id.startsWith("aero-"));
|
|
||||||
const keyOf = el => el.id.slice(5);
|
|
||||||
const defaults = new Map(paramInputs.map(el => [el, el.value]));
|
|
||||||
|
|
||||||
// Applique les paramètres présents dans l'URL aux inputs (au chargement)
|
|
||||||
const applyUrlParams = () => {
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
paramInputs.forEach(el => {
|
|
||||||
const k = keyOf(el);
|
|
||||||
if (params.has(k)) el.value = params.get(k);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reflète l'état courant des inputs dans l'URL (sans polluer l'historique)
|
|
||||||
const syncUrl = () => {
|
|
||||||
try {
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
paramInputs.forEach(el => {
|
|
||||||
if (el.value !== defaults.get(el)) params.set(keyOf(el), el.value);
|
|
||||||
});
|
|
||||||
const qs = params.toString();
|
|
||||||
const url = window.location.pathname + (qs ? "?" + qs : "") + window.location.hash;
|
|
||||||
window.history.replaceState(null, "", url);
|
|
||||||
} catch(err) {
|
|
||||||
console.error('calculateur:syncUrl error:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Store event handlers for cleanup
|
|
||||||
const handleModeChange = e => {
|
|
||||||
try {
|
|
||||||
mode = e.target.value;
|
|
||||||
applyModeVisibility();
|
|
||||||
drawChart();
|
|
||||||
syncUrl();
|
|
||||||
} catch(err) {
|
|
||||||
console.error('calculateur:handleModeChange error:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInput = () => {
|
|
||||||
try {
|
|
||||||
readConsts();
|
|
||||||
updateProp();
|
|
||||||
updateBalloon();
|
|
||||||
updateVolume();
|
|
||||||
drawChart();
|
|
||||||
syncUrl();
|
|
||||||
} catch(err) {
|
|
||||||
console.error('calculateur:handleInput error:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.#resizeHandler = () => {
|
|
||||||
if (geom) drawChart();
|
|
||||||
};
|
|
||||||
|
|
||||||
const modeEl = $("aero-mode");
|
|
||||||
if (modeEl) modeEl.addEventListener("change", handleModeChange);
|
|
||||||
root.querySelectorAll("input,select").forEach(el => el.addEventListener("input", handleInput));
|
|
||||||
window.addEventListener("resize", this.#resizeHandler);
|
|
||||||
|
|
||||||
// Store handlers for cleanup
|
|
||||||
this.handleModeChange = handleModeChange;
|
|
||||||
this.handleInput = handleInput;
|
|
||||||
this.root = root;
|
|
||||||
this.$ = $;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Charge l'état partagé depuis l'URL, puis synchronise le mode
|
|
||||||
applyUrlParams();
|
|
||||||
if (modeEl) mode = modeEl.value;
|
|
||||||
applyModeVisibility();
|
|
||||||
readConsts();
|
|
||||||
updateProp();
|
|
||||||
updateBalloon();
|
|
||||||
updateVolume();
|
|
||||||
drawChart();
|
|
||||||
} catch(err) {
|
|
||||||
console.error('calculateur: error during initialization:', err);
|
|
||||||
}
|
|
||||||
try{ root.dataset.calculateurConnected = '1'; }catch(e){}
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect() {
|
|
||||||
// Clean up event listeners
|
|
||||||
if (this.root && this.$) {
|
|
||||||
const $ = this.$;
|
|
||||||
if (this.handleModeChange) {
|
|
||||||
const modeEl = $("aero-mode");
|
|
||||||
if (modeEl) modeEl.removeEventListener("change", this.handleModeChange);
|
|
||||||
}
|
|
||||||
if (this.handleInput) {
|
|
||||||
this.root.querySelectorAll("input,select").forEach(el =>
|
|
||||||
el.removeEventListener("input", this.handleInput)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.#resizeHandler) {
|
|
||||||
window.removeEventListener("resize", this.#resizeHandler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,233 +0,0 @@
|
||||||
"use strict";
|
|
||||||
(function(){
|
|
||||||
const root = document.querySelector(".aero-calc");
|
|
||||||
if (!root) {
|
|
||||||
console.error('calculateur legacy: .aero-calc container not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const $ = id => {
|
|
||||||
const el = root.querySelector("#"+id);
|
|
||||||
// silencieusement retourner null si l'élément n'existe pas
|
|
||||||
return el;
|
|
||||||
};
|
|
||||||
|
|
||||||
const C = {};
|
|
||||||
function readConsts(){
|
|
||||||
try {
|
|
||||||
const ct = $("aero-ct"); if(ct) C.CT=parseFloat(ct.value); else console.warn('aero-ct not found');
|
|
||||||
const sair = $("aero-sair"); if(sair) C.sAir=parseFloat(sair.value); else console.warn('aero-sair not found');
|
|
||||||
const g = $("aero-g"); if(g) C.g=parseFloat(g.value); else console.warn('aero-g not found');
|
|
||||||
const sea = $("aero-sea"); if(sea) C.sea=parseFloat(sea.value); else console.warn('aero-sea not found');
|
|
||||||
const k = $("aero-k"); if(k) C.k=parseFloat(k.value); else console.warn('aero-k not found');
|
|
||||||
const pmax = $("aero-pmax"); if(pmax) C.pMax=parseFloat(pmax.value); else console.warn('aero-pmax not found');
|
|
||||||
const ytop = $("aero-ytop"); if(ytop) C.yTop=parseFloat(ytop.value); else console.warn('aero-ytop not found');
|
|
||||||
const ybot = $("aero-ybot"); if(ybot) C.yBot=parseFloat(ybot.value); else console.warn('aero-ybot not found');
|
|
||||||
} catch(e) { console.error('readConsts error:', e); }
|
|
||||||
}
|
|
||||||
|
|
||||||
function pressure(y){ let p=Math.exp(C.k*(y-C.sea)); if(p>C.pMax)p=C.pMax; return p<0?0:p; }
|
|
||||||
// Le mod echantillonne la pression au CENTRE du bloc : getBlockPos().getCenter() -> y+0.5
|
|
||||||
const Y_OFFSET = 0.5;
|
|
||||||
const propThrust = (P,N)=>C.CT*Math.pow(Math.max(P,0),1.5)*N;
|
|
||||||
const balloonForce = (V,y)=>Math.max(V,0)*C.sAir*C.g*pressure(y);
|
|
||||||
const balloonLift = (V,y)=>Math.max(V,0)*C.sAir*pressure(y);
|
|
||||||
function maxAltitude(V,m){
|
|
||||||
if(V<=0||m<=0) return {y:C.yTop,status:"ceiling"};
|
|
||||||
const t=m/(V*C.sAir);
|
|
||||||
if(t>C.pMax) return {y:C.yBot,status:"grounded"};
|
|
||||||
let y=C.sea+Math.log(t)/C.k;
|
|
||||||
if(y>=C.yTop) return {y:C.yTop,status:"ceiling"};
|
|
||||||
if(y<=C.yBot) return {y:C.yBot,status:"bottom"};
|
|
||||||
return {y,status:"ok"};
|
|
||||||
}
|
|
||||||
const requiredVolume=(m,y)=>m/(C.sAir*pressure(y));
|
|
||||||
|
|
||||||
const nf=new Intl.NumberFormat("fr-FR",{maximumFractionDigits:0});
|
|
||||||
const nf2=new Intl.NumberFormat("fr-FR",{maximumFractionDigits:2});
|
|
||||||
const nf3=new Intl.NumberFormat("fr-FR",{maximumFractionDigits:3});
|
|
||||||
function big(x){ if(!isFinite(x)) return "n/a"; return Math.abs(x)>=1000?nf.format(x):nf2.format(x); }
|
|
||||||
|
|
||||||
function updateProp(){
|
|
||||||
try {
|
|
||||||
const sails = $("aero-sails"); const P = sails ? parseFloat(sails.value)||0 : 0;
|
|
||||||
const rpm = $("aero-rpm"); const N = rpm ? parseFloat(rpm.value)||0 : 0;
|
|
||||||
const palt = $("aero-palt"); const y = palt ? parseFloat(palt.value)||0 : 0;
|
|
||||||
const T=propThrust(P,N);
|
|
||||||
const thrust = $("aero-othrust"); if(thrust) thrust.innerHTML=big(Math.abs(T))+' <small>pN</small>';
|
|
||||||
const thrustapp = $("aero-othrustapp"); if(thrustapp) thrustapp.innerHTML=big(Math.abs(T)*pressure(y+Y_OFFSET))+' <small>pN</small>';
|
|
||||||
} catch(e) { console.error('updateProp error:', e); }
|
|
||||||
}
|
|
||||||
function updateBalloon(){
|
|
||||||
try {
|
|
||||||
const vol = $("aero-vol"); const V = vol ? parseFloat(vol.value)||0 : 0;
|
|
||||||
const balt = $("aero-balt"); const y = balt ? parseFloat(balt.value)||0 : 0;
|
|
||||||
const force = $("aero-oforce"); if(force) force.innerHTML=big(balloonForce(V,y))+' <small>pN</small>';
|
|
||||||
const lift = $("aero-olift"); if(lift) lift.innerHTML=big(balloonLift(V,y))+' <small>kpg</small>';
|
|
||||||
const press = $("aero-opress"); if(press) press.textContent=nf3.format(pressure(y));
|
|
||||||
} catch(e) { console.error('updateBalloon error:', e); }
|
|
||||||
}
|
|
||||||
function updateVolume(){
|
|
||||||
try {
|
|
||||||
const vmass = $("aero-vmass"); const m = vmass ? parseFloat(vmass.value)||0 : 0;
|
|
||||||
const valt = $("aero-valt"); const y = valt ? parseFloat(valt.value)||0 : 0;
|
|
||||||
const V=requiredVolume(m,y);
|
|
||||||
const volreq = $("aero-ovolreq"); if(volreq) volreq.innerHTML=big(V)+' <small>m³</small>';
|
|
||||||
const cube = $("aero-ocube"); if(cube) cube.textContent=(isFinite(V)&&V>0)
|
|
||||||
? "≈ cube de "+nf2.format(Math.cbrt(V))+" blocs de côté · "+nf.format(Math.ceil(V))+" blocs d'air"
|
|
||||||
: "";
|
|
||||||
const warn = $("aero-vwarn"); if(warn) warn.innerHTML=(pressure(y)<=0)
|
|
||||||
? "<b>Altitude hors atmosphère :</b> pression nulle, aucun volume fini ne suffit." : "";
|
|
||||||
} catch(e) { console.error('updateVolume error:', e); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- graphe ----
|
|
||||||
let mode="mass";
|
|
||||||
const canvas=$("aero-chart"), tip=$("aero-tip");
|
|
||||||
if (!canvas) {
|
|
||||||
console.error('calculateur legacy: canvas not found, graph disabled');
|
|
||||||
}
|
|
||||||
const ctx = canvas ? canvas.getContext("2d") : null;
|
|
||||||
let geom=null;
|
|
||||||
const tv=n=>getComputedStyle(root).getPropertyValue(n).trim()||"#888";
|
|
||||||
|
|
||||||
function niceStep(raw){ const p=Math.pow(10,Math.floor(Math.log10(raw))); const n=raw/p; return (n<1.5?1:n<3?2:n<7?5:10)*p; }
|
|
||||||
function abbr(v){ const a=Math.abs(v); if(a>=1e6)return nf2.format(v/1e6)+"M"; if(a>=1e3)return nf2.format(v/1e3)+"k"; return nf.format(v); }
|
|
||||||
|
|
||||||
function drawChart(){
|
|
||||||
if(!canvas||!ctx) { console.warn('calculateur legacy: graph not available'); return; }
|
|
||||||
const dpr=window.devicePixelRatio||1;
|
|
||||||
const cssW=canvas.parentElement.clientWidth;
|
|
||||||
const cssH=Math.max(260,Math.round(cssW*0.44));
|
|
||||||
canvas.style.height=cssH+"px";
|
|
||||||
canvas.width=Math.round(cssW*dpr); canvas.height=Math.round(cssH*dpr);
|
|
||||||
ctx.setTransform(dpr,0,0,dpr,0,0); ctx.clearRect(0,0,cssW,cssH);
|
|
||||||
|
|
||||||
const padL=54,padR=16,padT=14,padB=38, pw=cssW-padL-padR, ph=cssH-padT-padB;
|
|
||||||
const fg=tv("--aero-fg"), muted=tv("--aero-muted"), border=tv("--aero-border"), accent=tv("--aero-accent"), warn=tv("--aero-warn");
|
|
||||||
|
|
||||||
let xMin,xMax,xLabel,fixed;
|
|
||||||
if(mode==="mass"){
|
|
||||||
const fixvol = $("aero-fixvol");
|
|
||||||
fixed=Math.max(1, fixvol ? parseFloat(fixvol.value)||1 : 1);
|
|
||||||
xMin=1; xMax=Math.max(2,fixed*C.sAir*C.pMax*1.15); xLabel="poids m (kpg)";
|
|
||||||
} else {
|
|
||||||
const fixmass = $("aero-fixmass");
|
|
||||||
fixed=Math.max(1, fixmass ? parseFloat(fixmass.value)||1 : 1);
|
|
||||||
xMin=Math.max(1,fixed/(C.sAir*C.pMax)*0.5); xMax=Math.max(xMin*1.5,4*fixed/C.sAir); xLabel="volume V (m³)";
|
|
||||||
}
|
|
||||||
const yMin=C.yBot,yMax=C.yTop;
|
|
||||||
const X=x=>padL+(x-xMin)/(xMax-xMin)*pw;
|
|
||||||
const Y=y=>padT+(1-(y-yMin)/(yMax-yMin))*ph;
|
|
||||||
|
|
||||||
ctx.font='11px '+(getComputedStyle(root).fontFamily||"sans-serif");
|
|
||||||
// grille
|
|
||||||
ctx.strokeStyle=border; ctx.fillStyle=muted; ctx.lineWidth=1;
|
|
||||||
ctx.textBaseline="middle"; ctx.textAlign="right";
|
|
||||||
const yStep=niceStep((yMax-yMin)/6);
|
|
||||||
for(let yy=Math.ceil(yMin/yStep)*yStep; yy<=yMax; yy+=yStep){
|
|
||||||
const py=Y(yy); ctx.beginPath();ctx.moveTo(padL,py);ctx.lineTo(padL+pw,py);ctx.stroke();
|
|
||||||
ctx.fillText(String(yy),padL-7,py);
|
|
||||||
}
|
|
||||||
ctx.textAlign="center"; ctx.textBaseline="top";
|
|
||||||
const xStep=niceStep((xMax-xMin)/6);
|
|
||||||
for(let xx=Math.ceil(xMin/xStep)*xStep; xx<=xMax; xx+=xStep){
|
|
||||||
const px=X(xx); ctx.beginPath();ctx.moveTo(px,padT);ctx.lineTo(px,padT+ph);ctx.stroke();
|
|
||||||
ctx.fillText(abbr(xx),px,padT+ph+7);
|
|
||||||
}
|
|
||||||
ctx.fillStyle=muted; ctx.fillText(xLabel,padL+pw/2,padT+ph+22);
|
|
||||||
ctx.save();ctx.translate(13,padT+ph/2);ctx.rotate(-Math.PI/2);ctx.fillText("altitude max y",0,0);ctx.restore();
|
|
||||||
|
|
||||||
function refLine(yv,label){
|
|
||||||
if(yv<yMin||yv>yMax) return;
|
|
||||||
const py=Y(yv);
|
|
||||||
ctx.strokeStyle=muted; ctx.setLineDash([4,4]); ctx.lineWidth=1;
|
|
||||||
ctx.beginPath();ctx.moveTo(padL,py);ctx.lineTo(padL+pw,py);ctx.stroke(); ctx.setLineDash([]);
|
|
||||||
ctx.fillStyle=muted; ctx.textAlign="left"; ctx.textBaseline="bottom"; ctx.fillText(label,padL+5,py-2);
|
|
||||||
}
|
|
||||||
refLine(C.sea,"niveau mer "+C.sea);
|
|
||||||
refLine(C.yTop,"limite "+C.yTop);
|
|
||||||
|
|
||||||
// courbe
|
|
||||||
const steps=240,pts=[];
|
|
||||||
for(let i=0;i<=steps;i++){ const x=xMin+(xMax-xMin)*i/steps; const r=(mode==="mass")?maxAltitude(fixed,x):maxAltitude(x,fixed); pts.push({x,y:r.y,status:r.status}); }
|
|
||||||
// zone grounded
|
|
||||||
ctx.fillStyle=warn; ctx.globalAlpha=.10; let gs=null;
|
|
||||||
for(let i=0;i<=steps;i++){
|
|
||||||
const g=pts[i].status==="grounded";
|
|
||||||
if(g&&gs===null)gs=pts[i].x;
|
|
||||||
if((!g||i===steps)&&gs!==null){ ctx.fillRect(X(gs),padT,X(pts[i].x)-X(gs),ph); gs=null; }
|
|
||||||
}
|
|
||||||
ctx.globalAlpha=1;
|
|
||||||
ctx.lineWidth=2; ctx.strokeStyle=accent; ctx.beginPath(); let started=false;
|
|
||||||
for(const p of pts){ if(p.status==="grounded"){started=false;continue;} const px=X(p.x),py=Y(p.y); if(!started){ctx.moveTo(px,py);started=true;}else ctx.lineTo(px,py); }
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
geom={padL,pw,padT,ph,xMin,xMax,X,Y,fixed};
|
|
||||||
}
|
|
||||||
|
|
||||||
if(canvas && tip) {
|
|
||||||
canvas.addEventListener("mousemove",e=>{
|
|
||||||
if(!geom)return; const rect=canvas.getBoundingClientRect(); const mx=e.clientX-rect.left;
|
|
||||||
if(mx<geom.padL||mx>geom.padL+geom.pw){tip.style.opacity=0;return;}
|
|
||||||
const x=geom.xMin+(mx-geom.padL)/geom.pw*(geom.xMax-geom.xMin);
|
|
||||||
const r=(mode==="mass")?maxAltitude(geom.fixed,x):maxAltitude(x,geom.fixed);
|
|
||||||
let txt = r.status==="grounded" ? abbr(x)+" : ne décolle pas"
|
|
||||||
: r.status==="ceiling" ? abbr(x)+" : plafond (y "+Math.round(r.y)+")"
|
|
||||||
: abbr(x)+" : y "+Math.round(r.y);
|
|
||||||
tip.textContent=txt; tip.style.left=geom.X(x)+"px"; tip.style.top=geom.Y(r.y)+"px"; tip.style.opacity=1;
|
|
||||||
});
|
|
||||||
canvas.addEventListener("mouseleave",()=>{tip.style.opacity=0;});
|
|
||||||
}
|
|
||||||
|
|
||||||
const modeSelector = $("aero-mode");
|
|
||||||
// Affiche uniquement l'input correspondant au mode courant
|
|
||||||
function applyModeVisibility(){
|
|
||||||
const fixVolWrap = $("aero-fixvolwrap");
|
|
||||||
const fixMassWrap = $("aero-fixmasswrap");
|
|
||||||
if(fixVolWrap) fixVolWrap.style.display = mode==="mass"?"":"none";
|
|
||||||
if(fixMassWrap) fixMassWrap.style.display= mode==="vol"?"":"none";
|
|
||||||
}
|
|
||||||
// ---- Partage par URL (paramètres GET, tous optionnels) ----
|
|
||||||
const paramInputs = Array.from(root.querySelectorAll("input, select")).filter(el=>el.id&&el.id.startsWith("aero-"));
|
|
||||||
const keyOf = el => el.id.slice(5);
|
|
||||||
const defaults = new Map(paramInputs.map(el=>[el, el.value]));
|
|
||||||
function applyUrlParams(){
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
paramInputs.forEach(el=>{ const k=keyOf(el); if(params.has(k)) el.value=params.get(k); });
|
|
||||||
}
|
|
||||||
function syncUrl(){
|
|
||||||
try {
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
paramInputs.forEach(el=>{ if(el.value!==defaults.get(el)) params.set(keyOf(el), el.value); });
|
|
||||||
const qs = params.toString();
|
|
||||||
window.history.replaceState(null, "", window.location.pathname + (qs?"?"+qs:"") + window.location.hash);
|
|
||||||
} catch(e){ console.error('calculateur legacy: syncUrl error:', e); }
|
|
||||||
}
|
|
||||||
|
|
||||||
if(modeSelector) {
|
|
||||||
modeSelector.addEventListener("change",e=>{
|
|
||||||
mode=e.target.value;
|
|
||||||
applyModeVisibility();
|
|
||||||
drawChart();
|
|
||||||
syncUrl();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
root.querySelectorAll("input,select").forEach(el=>el.addEventListener("input",()=>{
|
|
||||||
try {
|
|
||||||
readConsts(); updateProp(); updateBalloon(); updateVolume(); drawChart(); syncUrl();
|
|
||||||
} catch(e) {
|
|
||||||
console.error('calculateur legacy: error during input event:', e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
window.addEventListener("resize",()=>{ if(geom) drawChart(); });
|
|
||||||
|
|
||||||
try {
|
|
||||||
applyUrlParams();
|
|
||||||
if(modeSelector) mode = modeSelector.value;
|
|
||||||
applyModeVisibility();
|
|
||||||
readConsts(); updateProp(); updateBalloon(); updateVolume(); drawChart();
|
|
||||||
} catch(e) {
|
|
||||||
console.error('calculateur legacy: error during initialization:', e);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
@ -1,534 +0,0 @@
|
||||||
/* ================================================================
|
|
||||||
assets/styles/calculator/index.css
|
|
||||||
Styles du calculateur Create Aeronautics
|
|
||||||
Cohérent avec le système « bulle » + tokens globaux (app.css).
|
|
||||||
Lisible en thème clair ET sombre.
|
|
||||||
================================================================ */
|
|
||||||
|
|
||||||
/* ── TOKENS DU GRAPHE ───────────────────────────────────────────
|
|
||||||
Lus en JS via getComputedStyle(.aero-calc) pour dessiner le canvas
|
|
||||||
(accent, warn, muted, border, fg). Doivent exister pour les 2 thèmes. */
|
|
||||||
.aero-calc {
|
|
||||||
--aero-fg: var(--text-light);
|
|
||||||
--aero-muted: var(--muted-light);
|
|
||||||
--aero-border: rgba(0, 0, 0, 0.14);
|
|
||||||
--aero-accent: var(--violet);
|
|
||||||
--aero-warn: #ef4444;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc {
|
|
||||||
--aero-fg: var(--text-dark);
|
|
||||||
--aero-muted: var(--muted-dark);
|
|
||||||
--aero-border: rgba(255, 255, 255, 0.16);
|
|
||||||
--aero-accent: var(--violet-light);
|
|
||||||
--aero-warn: #f87171;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Couleur de texte par défaut des bulles en thème clair
|
|
||||||
(les bulles ne fixent la couleur qu'en sombre) */
|
|
||||||
.aero-calc .bulle {
|
|
||||||
color: var(--text-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .bulle {
|
|
||||||
color: var(--text-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── EN-TÊTE ────────────────────────────────────────────────────── */
|
|
||||||
.aero-calc .bulle-header {
|
|
||||||
text-align: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc h1 {
|
|
||||||
font-family: 'Syne', sans-serif;
|
|
||||||
font-size: clamp(1.5rem, 3vw, 2rem);
|
|
||||||
font-weight: 800;
|
|
||||||
letter-spacing: -0.01em;
|
|
||||||
color: var(--text-light);
|
|
||||||
transition: color var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc h1 {
|
|
||||||
color: var(--text-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-sub {
|
|
||||||
max-width: 62ch;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: var(--muted-light);
|
|
||||||
transition: color var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-sub {
|
|
||||||
color: var(--muted-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── TITRES DE SECTION ──────────────────────────────────────────── */
|
|
||||||
.aero-calc .ac-h2,
|
|
||||||
.aero-calc .ac-h3 {
|
|
||||||
font-family: 'Syne', sans-serif;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: -0.01em;
|
|
||||||
color: var(--text-light);
|
|
||||||
transition: color var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-h2 { font-size: 1.25rem; }
|
|
||||||
.aero-calc .ac-h3 { font-size: 1.05rem; }
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-h2,
|
|
||||||
[data-theme="dark"] .aero-calc .ac-h3 {
|
|
||||||
color: var(--text-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Filet accent sous les titres de section principaux */
|
|
||||||
.aero-calc .ac-h2 {
|
|
||||||
display: inline-block;
|
|
||||||
padding-bottom: 0.35rem;
|
|
||||||
border-bottom: 2px solid var(--violet);
|
|
||||||
margin-bottom: 0.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── TEXTES D'AIDE (hint / desc / note) ─────────────────────────── */
|
|
||||||
.aero-calc .ac-hint,
|
|
||||||
.aero-calc .ac-desc,
|
|
||||||
.aero-calc .ac-note,
|
|
||||||
.aero-calc .ac-cube {
|
|
||||||
font-size: 0.82rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: var(--muted-light);
|
|
||||||
transition: color var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-hint,
|
|
||||||
[data-theme="dark"] .aero-calc .ac-desc,
|
|
||||||
[data-theme="dark"] .aero-calc .ac-note,
|
|
||||||
[data-theme="dark"] .aero-calc .ac-cube {
|
|
||||||
color: var(--muted-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-desc { margin-bottom: 1rem; }
|
|
||||||
.aero-calc .ac-hint { margin-bottom: 0.85rem; }
|
|
||||||
.aero-calc .ac-note { margin-top: 0.85rem; }
|
|
||||||
.aero-calc .ac-cube { margin-top: 0.4rem; font-style: italic; }
|
|
||||||
|
|
||||||
.aero-calc .ac-note b { color: var(--violet); font-weight: 600; }
|
|
||||||
[data-theme="dark"] .aero-calc .ac-note b { color: var(--violet-light); }
|
|
||||||
|
|
||||||
/* ── CONSTANTES (details / summary) ─────────────────────────────── */
|
|
||||||
.aero-calc .ac-consts {
|
|
||||||
border: 1px solid var(--border-light);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 0.25rem 1rem;
|
|
||||||
transition: border-color var(--duration-slow), background var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-consts {
|
|
||||||
border-color: var(--border-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-consts summary {
|
|
||||||
cursor: pointer;
|
|
||||||
list-style: none;
|
|
||||||
padding: 0.75rem 0;
|
|
||||||
font-family: 'Syne', sans-serif;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
color: var(--text-light);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
transition: color var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-consts summary {
|
|
||||||
color: var(--text-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-consts summary::-webkit-details-marker { display: none; }
|
|
||||||
|
|
||||||
/* Chevron custom qui pivote à l'ouverture */
|
|
||||||
.aero-calc .ac-consts summary::before {
|
|
||||||
content: '';
|
|
||||||
width: 0.5rem;
|
|
||||||
height: 0.5rem;
|
|
||||||
border-right: 2px solid var(--violet);
|
|
||||||
border-bottom: 2px solid var(--violet);
|
|
||||||
transform: rotate(-45deg);
|
|
||||||
transition: transform var(--duration-normal);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-consts summary::before {
|
|
||||||
border-color: var(--violet-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-consts[open] summary::before {
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-consts-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
||||||
gap: 0.85rem;
|
|
||||||
padding: 0.5rem 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── CHAMPS (label + input) ─────────────────────────────────────── */
|
|
||||||
.aero-calc .ac-field {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-field label {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 0.5rem;
|
|
||||||
font-size: 0.78rem;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
color: var(--text-light);
|
|
||||||
transition: color var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-field label {
|
|
||||||
color: var(--text-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Unité affichée à droite du label */
|
|
||||||
.aero-calc .ac-field label .u {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.72rem;
|
|
||||||
color: var(--muted-light);
|
|
||||||
white-space: nowrap;
|
|
||||||
transition: color var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-field label .u {
|
|
||||||
color: var(--muted-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-field input,
|
|
||||||
.aero-calc .ac-field select,
|
|
||||||
.aero-calc .ac-controls select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.55rem 0.8rem;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid var(--border-light);
|
|
||||||
background-color: var(--nav-bg-light);
|
|
||||||
color: var(--text-light);
|
|
||||||
font-family: 'DM Sans', sans-serif;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
outline: none;
|
|
||||||
appearance: none;
|
|
||||||
transition: border-color var(--duration-normal), box-shadow var(--duration-normal),
|
|
||||||
background var(--duration-slow), color var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-field input,
|
|
||||||
[data-theme="dark"] .aero-calc .ac-field select,
|
|
||||||
[data-theme="dark"] .aero-calc .ac-controls select {
|
|
||||||
background-color: var(--nav-bg-dark);
|
|
||||||
border-color: var(--border-dark);
|
|
||||||
color: var(--text-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-field input:focus,
|
|
||||||
.aero-calc .ac-field select:focus {
|
|
||||||
border-color: var(--violet);
|
|
||||||
box-shadow: 0 0 0 3px var(--violet-glow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Flèche des <select> (reprise de form.css) */
|
|
||||||
.aero-calc select {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%237C3AED' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: right 0.7rem center;
|
|
||||||
background-size: 0.7rem;
|
|
||||||
padding-right: 2.2rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc select {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%23A78BFA' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── OUTILS (Hélice / Ballon) ───────────────────────────────────── */
|
|
||||||
.aero-calc .ac-tool {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── READOUTS (clé → valeur) ────────────────────────────────────── */
|
|
||||||
.aero-calc .ac-readout {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 0.55rem 0.75rem;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: var(--pill-bg-light);
|
|
||||||
transition: background var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-readout {
|
|
||||||
background: var(--pill-bg-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-rk {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: var(--muted-light);
|
|
||||||
transition: color var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-rk {
|
|
||||||
color: var(--muted-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-rv {
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
text-align: right;
|
|
||||||
white-space: nowrap;
|
|
||||||
color: var(--text-light);
|
|
||||||
transition: color var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-rv {
|
|
||||||
color: var(--text-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Valeur mise en avant */
|
|
||||||
.aero-calc .ac-rv.key {
|
|
||||||
font-size: 1.15rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--violet);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-rv.key {
|
|
||||||
color: var(--violet-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-rv small {
|
|
||||||
font-size: 0.72rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--muted-light);
|
|
||||||
margin-left: 0.15rem;
|
|
||||||
transition: color var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-rv small {
|
|
||||||
color: var(--muted-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── CONTRÔLES DU GRAPHE ────────────────────────────────────────── */
|
|
||||||
.aero-calc .ac-controls {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
||||||
gap: 0.85rem;
|
|
||||||
margin-bottom: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── ZONE GRAPHE ────────────────────────────────────────────────── */
|
|
||||||
.aero-calc .ac-chart-shell {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 1px solid var(--border-light);
|
|
||||||
background: rgba(255, 255, 255, 0.35);
|
|
||||||
transition: background var(--duration-slow), border-color var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-chart-shell {
|
|
||||||
border-color: var(--border-dark);
|
|
||||||
background: rgba(0, 0, 0, 0.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc #aero-chart {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Infobulle suivant le curseur (position gérée en JS) */
|
|
||||||
.aero-calc .ac-tip {
|
|
||||||
position: absolute;
|
|
||||||
transform: translate(-50%, -130%);
|
|
||||||
padding: 0.3rem 0.55rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--violet);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
white-space: nowrap;
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 0;
|
|
||||||
box-shadow: 0 6px 18px rgba(124, 58, 237, 0.35);
|
|
||||||
transition: opacity var(--duration-fast);
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── LÉGENDE ────────────────────────────────────────────────────── */
|
|
||||||
.aero-calc .ac-legend {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem 1.25rem;
|
|
||||||
margin-top: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-legend span {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.45rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: var(--muted-light);
|
|
||||||
transition: color var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-legend span {
|
|
||||||
color: var(--muted-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-sw {
|
|
||||||
width: 1.1rem;
|
|
||||||
height: 0.3rem;
|
|
||||||
border-radius: 999px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
background: var(--aero-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Correspondance avec les couleurs tracées sur le canvas */
|
|
||||||
.aero-calc .ac-legend span:nth-child(1) .ac-sw {
|
|
||||||
background: var(--aero-accent);
|
|
||||||
}
|
|
||||||
.aero-calc .ac-legend span:nth-child(2) .ac-sw {
|
|
||||||
background: var(--aero-muted);
|
|
||||||
height: 0;
|
|
||||||
border-top: 2px dashed var(--aero-muted);
|
|
||||||
}
|
|
||||||
.aero-calc .ac-legend span:nth-child(3) .ac-sw {
|
|
||||||
background: color-mix(in srgb, var(--aero-warn) 45%, transparent);
|
|
||||||
height: 0.7rem;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── VOLUME REQUIS ──────────────────────────────────────────────── */
|
|
||||||
/* Inputs en ligne, résultat en dessous (comme les sections Hélice/Ballon) */
|
|
||||||
.aero-calc .ac-vol {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-vol-inputs {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── FORMULES ───────────────────────────────────────────────────── */
|
|
||||||
.aero-calc .ac-formulas {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
||||||
gap: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-formulas > div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.4rem;
|
|
||||||
padding: 0.85rem;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid var(--border-light);
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
transition: border-color var(--duration-slow), background var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-formulas > div {
|
|
||||||
border-color: var(--border-dark);
|
|
||||||
background: rgba(0, 0, 0, 0.16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-fk {
|
|
||||||
font-size: 0.78rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--muted-light);
|
|
||||||
transition: color var(--duration-slow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-fk {
|
|
||||||
color: var(--muted-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-formulas code {
|
|
||||||
font-family: 'DM Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--violet);
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .aero-calc .ac-formulas code {
|
|
||||||
color: var(--violet-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── RESPONSIVE ─────────────────────────────────────────────────── */
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
/* Conteneur + bulles plus compacts pour gagner de la largeur utile */
|
|
||||||
.aero-calc.bulle-container {
|
|
||||||
padding: 0.5rem;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .bulle {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Titres un cran plus petits */
|
|
||||||
.aero-calc h1 { font-size: 1.35rem; }
|
|
||||||
.aero-calc .ac-h2 { font-size: 1.1rem; }
|
|
||||||
.aero-calc .ac-h3 { font-size: 1rem; }
|
|
||||||
|
|
||||||
/* Grilles denses : on resserre les colonnes minimales */
|
|
||||||
.aero-calc .ac-consts-grid {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(115px, 1fr));
|
|
||||||
gap: 0.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-controls {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Readout empilé : libellé au-dessus, valeur en dessous */
|
|
||||||
.aero-calc .ac-readout {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-rv,
|
|
||||||
.aero-calc .ac-rv.key {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-legend {
|
|
||||||
gap: 0.4rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aero-calc .ac-chart-shell {
|
|
||||||
padding: 0.35rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Très petits écrans : formules et constantes sur une seule colonne */
|
|
||||||
@media screen and (max-width: 420px) {
|
|
||||||
.aero-calc .ac-consts-grid,
|
|
||||||
.aero-calc .ac-formulas {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,10 +7,10 @@
|
||||||
"php": ">=8.2",
|
"php": ">=8.2",
|
||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"doctrine/doctrine-bundle": "^2.18.3",
|
"doctrine/doctrine-bundle": "^2.18",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.7",
|
"doctrine/doctrine-migrations-bundle": "^3.7",
|
||||||
"doctrine/orm": "^3.6.7",
|
"doctrine/orm": "^3.6",
|
||||||
"phpdocumentor/reflection-docblock": "^6.0.3",
|
"phpdocumentor/reflection-docblock": "^6.0",
|
||||||
"phpstan/phpdoc-parser": "^2.3",
|
"phpstan/phpdoc-parser": "^2.3",
|
||||||
"symfony/asset": "7.4.*",
|
"symfony/asset": "7.4.*",
|
||||||
"symfony/asset-mapper": "7.4.*",
|
"symfony/asset-mapper": "7.4.*",
|
||||||
|
|
@ -18,14 +18,14 @@
|
||||||
"symfony/doctrine-messenger": "7.4.*",
|
"symfony/doctrine-messenger": "7.4.*",
|
||||||
"symfony/dotenv": "7.4.*",
|
"symfony/dotenv": "7.4.*",
|
||||||
"symfony/expression-language": "7.4.*",
|
"symfony/expression-language": "7.4.*",
|
||||||
"symfony/flex": "^2.11",
|
"symfony/flex": "^2",
|
||||||
"symfony/form": "7.4.*",
|
"symfony/form": "7.4.*",
|
||||||
"symfony/framework-bundle": "7.4.*",
|
"symfony/framework-bundle": "7.4.*",
|
||||||
"symfony/http-client": "7.4.*",
|
"symfony/http-client": "7.4.*",
|
||||||
"symfony/intl": "7.4.*",
|
"symfony/intl": "7.4.*",
|
||||||
"symfony/mailer": "7.4.*",
|
"symfony/mailer": "7.4.*",
|
||||||
"symfony/mime": "7.4.*",
|
"symfony/mime": "7.4.*",
|
||||||
"symfony/monolog-bundle": "^3.0|^4.0.2",
|
"symfony/monolog-bundle": "^3.0|^4.0",
|
||||||
"symfony/notifier": "7.4.*",
|
"symfony/notifier": "7.4.*",
|
||||||
"symfony/process": "7.4.*",
|
"symfony/process": "7.4.*",
|
||||||
"symfony/property-access": "7.4.*",
|
"symfony/property-access": "7.4.*",
|
||||||
|
|
@ -33,16 +33,16 @@
|
||||||
"symfony/runtime": "7.4.*",
|
"symfony/runtime": "7.4.*",
|
||||||
"symfony/security-bundle": "7.4.*",
|
"symfony/security-bundle": "7.4.*",
|
||||||
"symfony/serializer": "7.4.*",
|
"symfony/serializer": "7.4.*",
|
||||||
"symfony/stimulus-bundle": "^2.36",
|
"symfony/stimulus-bundle": "^2.32",
|
||||||
"symfony/string": "7.4.*",
|
"symfony/string": "7.4.*",
|
||||||
"symfony/translation": "7.4.*",
|
"symfony/translation": "7.4.*",
|
||||||
"symfony/twig-bundle": "7.4.*",
|
"symfony/twig-bundle": "7.4.*",
|
||||||
"symfony/ux-turbo": "^2.36",
|
"symfony/ux-turbo": "^2.32",
|
||||||
"symfony/validator": "7.4.*",
|
"symfony/validator": "7.4.*",
|
||||||
"symfony/web-link": "7.4.*",
|
"symfony/web-link": "7.4.*",
|
||||||
"symfony/yaml": "7.4.*",
|
"symfony/yaml": "7.4.*",
|
||||||
"twig/extra-bundle": "^2.12|^3.24",
|
"twig/extra-bundle": "^2.12|^3.0",
|
||||||
"twig/twig": "^2.12|^3.27.1",
|
"twig/twig": "^2.12|^3.0",
|
||||||
"ext-zip": "*"
|
"ext-zip": "*"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
|
|
@ -51,9 +51,6 @@
|
||||||
"symfony/flex": true,
|
"symfony/flex": true,
|
||||||
"symfony/runtime": true
|
"symfony/runtime": true
|
||||||
},
|
},
|
||||||
"platform": {
|
|
||||||
"php": "8.5"
|
|
||||||
},
|
|
||||||
"bump-after-update": true,
|
"bump-after-update": true,
|
||||||
"sort-packages": true
|
"sort-packages": true
|
||||||
},
|
},
|
||||||
|
|
@ -100,11 +97,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^12.5.30",
|
"phpunit/phpunit": "^12.5",
|
||||||
"symfony/browser-kit": "7.4.*",
|
"symfony/browser-kit": "7.4.*",
|
||||||
"symfony/css-selector": "7.4.*",
|
"symfony/css-selector": "7.4.*",
|
||||||
"symfony/debug-bundle": "7.4.*",
|
"symfony/debug-bundle": "7.4.*",
|
||||||
"symfony/maker-bundle": "^1.67",
|
"symfony/maker-bundle": "^1.0",
|
||||||
"symfony/stopwatch": "7.4.*",
|
"symfony/stopwatch": "7.4.*",
|
||||||
"symfony/web-profiler-bundle": "7.4.*"
|
"symfony/web-profiler-bundle": "7.4.*"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13864
app/composer.lock
generated
13864
app/composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -128,7 +128,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* @psalm-type FrameworkConfig = array{
|
* @psalm-type FrameworkConfig = array{
|
||||||
* secret?: scalar|Param|null,
|
* secret?: scalar|Param|null,
|
||||||
* http_method_override?: bool|Param, // Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. // Default: false
|
* http_method_override?: bool|Param, // Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. // Default: false
|
||||||
* allowed_http_method_override?: null|list<string|Param>,
|
* allowed_http_method_override?: list<string|Param>|null,
|
||||||
* trust_x_sendfile_type_header?: scalar|Param|null, // Set true to enable support for xsendfile in binary file responses. // Default: "%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%"
|
* trust_x_sendfile_type_header?: scalar|Param|null, // Set true to enable support for xsendfile in binary file responses. // Default: "%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%"
|
||||||
* ide?: scalar|Param|null, // Default: "%env(default::SYMFONY_IDE)%"
|
* ide?: scalar|Param|null, // Default: "%env(default::SYMFONY_IDE)%"
|
||||||
* test?: bool|Param,
|
* test?: bool|Param,
|
||||||
|
|
@ -136,9 +136,9 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* set_locale_from_accept_language?: bool|Param, // Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed). // Default: false
|
* set_locale_from_accept_language?: bool|Param, // Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed). // Default: false
|
||||||
* set_content_language_from_locale?: bool|Param, // Whether to set the Content-Language HTTP header on the Response using the Request locale. // Default: false
|
* set_content_language_from_locale?: bool|Param, // Whether to set the Content-Language HTTP header on the Response using the Request locale. // Default: false
|
||||||
* enabled_locales?: list<scalar|Param|null>,
|
* enabled_locales?: list<scalar|Param|null>,
|
||||||
* trusted_hosts?: string|list<scalar|Param|null>,
|
* trusted_hosts?: list<scalar|Param|null>,
|
||||||
* trusted_proxies?: mixed, // Default: ["%env(default::SYMFONY_TRUSTED_PROXIES)%"]
|
* trusted_proxies?: mixed, // Default: ["%env(default::SYMFONY_TRUSTED_PROXIES)%"]
|
||||||
* trusted_headers?: string|list<scalar|Param|null>,
|
* trusted_headers?: list<scalar|Param|null>,
|
||||||
* error_controller?: scalar|Param|null, // Default: "error_controller"
|
* error_controller?: scalar|Param|null, // Default: "error_controller"
|
||||||
* handle_all_throwables?: bool|Param, // HttpKernel will handle all kinds of \Throwable. // Default: true
|
* handle_all_throwables?: bool|Param, // HttpKernel will handle all kinds of \Throwable. // Default: true
|
||||||
* csrf_protection?: bool|array{
|
* csrf_protection?: bool|array{
|
||||||
|
|
@ -202,23 +202,23 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* property?: scalar|Param|null,
|
* property?: scalar|Param|null,
|
||||||
* service?: scalar|Param|null,
|
* service?: scalar|Param|null,
|
||||||
* },
|
* },
|
||||||
* supports?: string|list<scalar|Param|null>,
|
* supports?: list<scalar|Param|null>,
|
||||||
* definition_validators?: list<scalar|Param|null>,
|
* definition_validators?: list<scalar|Param|null>,
|
||||||
* support_strategy?: scalar|Param|null,
|
* support_strategy?: scalar|Param|null,
|
||||||
* initial_marking?: \BackedEnum|string|list<scalar|Param|null>,
|
* initial_marking?: list<scalar|Param|null>,
|
||||||
* events_to_dispatch?: null|list<string|Param>,
|
* events_to_dispatch?: list<string|Param>|null,
|
||||||
* places?: string|list<array{ // Default: []
|
* places?: list<array{ // Default: []
|
||||||
* name?: scalar|Param|null,
|
* name?: scalar|Param|null,
|
||||||
* metadata?: array<string, mixed>,
|
* metadata?: array<string, mixed>,
|
||||||
* }>,
|
* }>,
|
||||||
* transitions?: list<array{ // Default: []
|
* transitions?: list<array{ // Default: []
|
||||||
* name?: string|Param,
|
* name?: string|Param,
|
||||||
* guard?: string|Param, // An expression to block the transition.
|
* guard?: string|Param, // An expression to block the transition.
|
||||||
* from?: \BackedEnum|string|list<array{ // Default: []
|
* from?: list<array{ // Default: []
|
||||||
* place?: string|Param,
|
* place?: string|Param,
|
||||||
* weight?: int|Param, // Default: 1
|
* weight?: int|Param, // Default: 1
|
||||||
* }>,
|
* }>,
|
||||||
* to?: \BackedEnum|string|list<array{ // Default: []
|
* to?: list<array{ // Default: []
|
||||||
* place?: string|Param,
|
* place?: string|Param,
|
||||||
* weight?: int|Param, // Default: 1
|
* weight?: int|Param, // Default: 1
|
||||||
* }>,
|
* }>,
|
||||||
|
|
@ -271,7 +271,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* version_format?: scalar|Param|null, // Default: "%%s?%%s"
|
* version_format?: scalar|Param|null, // Default: "%%s?%%s"
|
||||||
* json_manifest_path?: scalar|Param|null, // Default: null
|
* json_manifest_path?: scalar|Param|null, // Default: null
|
||||||
* base_path?: scalar|Param|null, // Default: ""
|
* base_path?: scalar|Param|null, // Default: ""
|
||||||
* base_urls?: string|list<scalar|Param|null>,
|
* base_urls?: list<scalar|Param|null>,
|
||||||
* packages?: array<string, array{ // Default: []
|
* packages?: array<string, array{ // Default: []
|
||||||
* strict_mode?: bool|Param, // Throw an exception if an entry is missing from the manifest.json. // Default: false
|
* strict_mode?: bool|Param, // Throw an exception if an entry is missing from the manifest.json. // Default: false
|
||||||
* version_strategy?: scalar|Param|null, // Default: null
|
* version_strategy?: scalar|Param|null, // Default: null
|
||||||
|
|
@ -279,12 +279,12 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* version_format?: scalar|Param|null, // Default: null
|
* version_format?: scalar|Param|null, // Default: null
|
||||||
* json_manifest_path?: scalar|Param|null, // Default: null
|
* json_manifest_path?: scalar|Param|null, // Default: null
|
||||||
* base_path?: scalar|Param|null, // Default: ""
|
* base_path?: scalar|Param|null, // Default: ""
|
||||||
* base_urls?: string|list<scalar|Param|null>,
|
* base_urls?: list<scalar|Param|null>,
|
||||||
* }>,
|
* }>,
|
||||||
* },
|
* },
|
||||||
* asset_mapper?: bool|array{ // Asset Mapper configuration
|
* asset_mapper?: bool|array{ // Asset Mapper configuration
|
||||||
* enabled?: bool|Param, // Default: true
|
* enabled?: bool|Param, // Default: true
|
||||||
* paths?: string|array<string, scalar|Param|null>,
|
* paths?: array<string, scalar|Param|null>,
|
||||||
* excluded_patterns?: list<scalar|Param|null>,
|
* excluded_patterns?: list<scalar|Param|null>,
|
||||||
* exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true
|
* exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true
|
||||||
* server?: bool|Param, // If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default). // Default: true
|
* server?: bool|Param, // If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default). // Default: true
|
||||||
|
|
@ -303,7 +303,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* },
|
* },
|
||||||
* translator?: bool|array{ // Translator configuration
|
* translator?: bool|array{ // Translator configuration
|
||||||
* enabled?: bool|Param, // Default: true
|
* enabled?: bool|Param, // Default: true
|
||||||
* fallbacks?: string|list<scalar|Param|null>,
|
* fallbacks?: list<scalar|Param|null>,
|
||||||
* logging?: bool|Param, // Default: false
|
* logging?: bool|Param, // Default: false
|
||||||
* formatter?: scalar|Param|null, // Default: "translator.formatter.default"
|
* formatter?: scalar|Param|null, // Default: "translator.formatter.default"
|
||||||
* cache_dir?: scalar|Param|null, // Default: "%kernel.cache_dir%/translations"
|
* cache_dir?: scalar|Param|null, // Default: "%kernel.cache_dir%/translations"
|
||||||
|
|
@ -333,7 +333,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* enabled?: bool|Param, // Default: true
|
* enabled?: bool|Param, // Default: true
|
||||||
* cache?: scalar|Param|null, // Deprecated: Setting the "framework.validation.cache.cache" configuration option is deprecated. It will be removed in version 8.0.
|
* cache?: scalar|Param|null, // Deprecated: Setting the "framework.validation.cache.cache" configuration option is deprecated. It will be removed in version 8.0.
|
||||||
* enable_attributes?: bool|Param, // Default: true
|
* enable_attributes?: bool|Param, // Default: true
|
||||||
* static_method?: string|list<scalar|Param|null>,
|
* static_method?: list<scalar|Param|null>,
|
||||||
* translation_domain?: scalar|Param|null, // Default: "validators"
|
* translation_domain?: scalar|Param|null, // Default: "validators"
|
||||||
* email_validation_mode?: "html5"|"html5-allow-no-tld"|"strict"|"loose"|Param, // Default: "html5"
|
* email_validation_mode?: "html5"|"html5-allow-no-tld"|"strict"|"loose"|Param, // Default: "html5"
|
||||||
* mapping?: array{
|
* mapping?: array{
|
||||||
|
|
@ -396,7 +396,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* default_doctrine_dbal_provider?: scalar|Param|null, // Default: "database_connection"
|
* default_doctrine_dbal_provider?: scalar|Param|null, // Default: "database_connection"
|
||||||
* default_pdo_provider?: scalar|Param|null, // Default: null
|
* default_pdo_provider?: scalar|Param|null, // Default: null
|
||||||
* pools?: array<string, array{ // Default: []
|
* pools?: array<string, array{ // Default: []
|
||||||
* adapters?: string|list<scalar|Param|null>,
|
* adapters?: list<scalar|Param|null>,
|
||||||
* tags?: scalar|Param|null, // Default: null
|
* tags?: scalar|Param|null, // Default: null
|
||||||
* public?: bool|Param, // Default: false
|
* public?: bool|Param, // Default: false
|
||||||
* default_lifetime?: scalar|Param|null, // Default lifetime of the pool.
|
* default_lifetime?: scalar|Param|null, // Default lifetime of the pool.
|
||||||
|
|
@ -419,11 +419,11 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* },
|
* },
|
||||||
* lock?: bool|string|array{ // Lock configuration
|
* lock?: bool|string|array{ // Lock configuration
|
||||||
* enabled?: bool|Param, // Default: false
|
* enabled?: bool|Param, // Default: false
|
||||||
* resources?: string|array<string, string|list<scalar|Param|null>>,
|
* resources?: array<string, string|list<scalar|Param|null>>,
|
||||||
* },
|
* },
|
||||||
* semaphore?: bool|string|array{ // Semaphore configuration
|
* semaphore?: bool|string|array{ // Semaphore configuration
|
||||||
* enabled?: bool|Param, // Default: false
|
* enabled?: bool|Param, // Default: false
|
||||||
* resources?: string|array<string, scalar|Param|null>,
|
* resources?: array<string, scalar|Param|null>,
|
||||||
* },
|
* },
|
||||||
* messenger?: bool|array{ // Messenger configuration
|
* messenger?: bool|array{ // Messenger configuration
|
||||||
* enabled?: bool|Param, // Default: true
|
* enabled?: bool|Param, // Default: true
|
||||||
|
|
@ -453,7 +453,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* rate_limiter?: scalar|Param|null, // Rate limiter name to use when processing messages. // Default: null
|
* rate_limiter?: scalar|Param|null, // Rate limiter name to use when processing messages. // Default: null
|
||||||
* }>,
|
* }>,
|
||||||
* failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null
|
* failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null
|
||||||
* stop_worker_on_signals?: int|string|list<scalar|Param|null>,
|
* stop_worker_on_signals?: list<scalar|Param|null>,
|
||||||
* default_bus?: scalar|Param|null, // Default: null
|
* default_bus?: scalar|Param|null, // Default: null
|
||||||
* buses?: array<string, array{ // Default: {"messenger.bus.default":{"default_middleware":{"enabled":true,"allow_no_handlers":false,"allow_no_senders":true},"middleware":[]}}
|
* buses?: array<string, array{ // Default: {"messenger.bus.default":{"default_middleware":{"enabled":true,"allow_no_handlers":false,"allow_no_senders":true},"middleware":[]}}
|
||||||
* default_middleware?: bool|string|array{
|
* default_middleware?: bool|string|array{
|
||||||
|
|
@ -461,7 +461,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* allow_no_handlers?: bool|Param, // Default: false
|
* allow_no_handlers?: bool|Param, // Default: false
|
||||||
* allow_no_senders?: bool|Param, // Default: true
|
* allow_no_senders?: bool|Param, // Default: true
|
||||||
* },
|
* },
|
||||||
* middleware?: string|list<string|array{ // Default: []
|
* middleware?: list<string|array{ // Default: []
|
||||||
* id?: scalar|Param|null,
|
* id?: scalar|Param|null,
|
||||||
* arguments?: list<mixed>,
|
* arguments?: list<mixed>,
|
||||||
* }>,
|
* }>,
|
||||||
|
|
@ -510,9 +510,9 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* retry_failed?: bool|array{
|
* retry_failed?: bool|array{
|
||||||
* enabled?: bool|Param, // Default: false
|
* enabled?: bool|Param, // Default: false
|
||||||
* retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null
|
* retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null
|
||||||
* http_codes?: int|string|array<string, array{ // Default: []
|
* http_codes?: array<string, array{ // Default: []
|
||||||
* code?: int|Param,
|
* code?: int|Param,
|
||||||
* methods?: string|list<string|Param>,
|
* methods?: list<string|Param>,
|
||||||
* }>,
|
* }>,
|
||||||
* max_retries?: int|Param, // Default: 3
|
* max_retries?: int|Param, // Default: 3
|
||||||
* delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000
|
* delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000
|
||||||
|
|
@ -563,9 +563,9 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* retry_failed?: bool|array{
|
* retry_failed?: bool|array{
|
||||||
* enabled?: bool|Param, // Default: false
|
* enabled?: bool|Param, // Default: false
|
||||||
* retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null
|
* retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null
|
||||||
* http_codes?: int|string|array<string, array{ // Default: []
|
* http_codes?: array<string, array{ // Default: []
|
||||||
* code?: int|Param,
|
* code?: int|Param,
|
||||||
* methods?: string|list<string|Param>,
|
* methods?: list<string|Param>,
|
||||||
* }>,
|
* }>,
|
||||||
* max_retries?: int|Param, // Default: 3
|
* max_retries?: int|Param, // Default: 3
|
||||||
* delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000
|
* delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000
|
||||||
|
|
@ -582,8 +582,8 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* transports?: array<string, scalar|Param|null>,
|
* transports?: array<string, scalar|Param|null>,
|
||||||
* envelope?: array{ // Mailer Envelope configuration
|
* envelope?: array{ // Mailer Envelope configuration
|
||||||
* sender?: scalar|Param|null,
|
* sender?: scalar|Param|null,
|
||||||
* recipients?: string|list<scalar|Param|null>,
|
* recipients?: list<scalar|Param|null>,
|
||||||
* allowed_recipients?: string|list<scalar|Param|null>,
|
* allowed_recipients?: list<scalar|Param|null>,
|
||||||
* },
|
* },
|
||||||
* headers?: array<string, string|array{ // Default: []
|
* headers?: array<string, string|array{ // Default: []
|
||||||
* value?: mixed,
|
* value?: mixed,
|
||||||
|
|
@ -635,7 +635,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* cache_pool?: scalar|Param|null, // The cache pool to use for storing the current limiter state. // Default: "cache.rate_limiter"
|
* cache_pool?: scalar|Param|null, // The cache pool to use for storing the current limiter state. // Default: "cache.rate_limiter"
|
||||||
* storage_service?: scalar|Param|null, // The service ID of a custom storage implementation, this precedes any configured "cache_pool". // Default: null
|
* storage_service?: scalar|Param|null, // The service ID of a custom storage implementation, this precedes any configured "cache_pool". // Default: null
|
||||||
* policy?: "fixed_window"|"token_bucket"|"sliding_window"|"compound"|"no_limit"|Param, // The algorithm to be used by this limiter.
|
* policy?: "fixed_window"|"token_bucket"|"sliding_window"|"compound"|"no_limit"|Param, // The algorithm to be used by this limiter.
|
||||||
* limiters?: string|list<scalar|Param|null>,
|
* limiters?: list<scalar|Param|null>,
|
||||||
* limit?: int|Param, // The maximum allowed hits in a fixed interval or burst.
|
* limit?: int|Param, // The maximum allowed hits in a fixed interval or burst.
|
||||||
* interval?: scalar|Param|null, // Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).
|
* interval?: scalar|Param|null, // Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).
|
||||||
* rate?: array{ // Configures the fill rate if "policy" is set to "token_bucket".
|
* rate?: array{ // Configures the fill rate if "policy" is set to "token_bucket".
|
||||||
|
|
@ -658,20 +658,20 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* allow_safe_elements?: bool|Param, // Allows "safe" elements and attributes. // Default: false
|
* allow_safe_elements?: bool|Param, // Allows "safe" elements and attributes. // Default: false
|
||||||
* allow_static_elements?: bool|Param, // Allows all static elements and attributes from the W3C Sanitizer API standard. // Default: false
|
* allow_static_elements?: bool|Param, // Allows all static elements and attributes from the W3C Sanitizer API standard. // Default: false
|
||||||
* allow_elements?: array<string, mixed>,
|
* allow_elements?: array<string, mixed>,
|
||||||
* block_elements?: string|list<string|Param>,
|
* block_elements?: list<string|Param>,
|
||||||
* drop_elements?: string|list<string|Param>,
|
* drop_elements?: list<string|Param>,
|
||||||
* allow_attributes?: array<string, mixed>,
|
* allow_attributes?: array<string, mixed>,
|
||||||
* drop_attributes?: array<string, mixed>,
|
* drop_attributes?: array<string, mixed>,
|
||||||
* force_attributes?: array<string, array<string, string|Param>>,
|
* force_attributes?: array<string, array<string, string|Param>>,
|
||||||
* force_https_urls?: bool|Param, // Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. // Default: false
|
* force_https_urls?: bool|Param, // Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. // Default: false
|
||||||
* allowed_link_schemes?: string|list<string|Param>,
|
* allowed_link_schemes?: list<string|Param>,
|
||||||
* allowed_link_hosts?: null|string|list<string|Param>,
|
* allowed_link_hosts?: list<string|Param>|null,
|
||||||
* allow_relative_links?: bool|Param, // Allows relative URLs to be used in links href attributes. // Default: false
|
* allow_relative_links?: bool|Param, // Allows relative URLs to be used in links href attributes. // Default: false
|
||||||
* allowed_media_schemes?: string|list<string|Param>,
|
* allowed_media_schemes?: list<string|Param>,
|
||||||
* allowed_media_hosts?: null|string|list<string|Param>,
|
* allowed_media_hosts?: list<string|Param>|null,
|
||||||
* allow_relative_medias?: bool|Param, // Allows relative URLs to be used in media source attributes (img, audio, video, ...). // Default: false
|
* allow_relative_medias?: bool|Param, // Allows relative URLs to be used in media source attributes (img, audio, video, ...). // Default: false
|
||||||
* with_attribute_sanitizers?: string|list<string|Param>,
|
* with_attribute_sanitizers?: list<string|Param>,
|
||||||
* without_attribute_sanitizers?: string|list<string|Param>,
|
* without_attribute_sanitizers?: list<string|Param>,
|
||||||
* max_input_length?: int|Param, // The maximum length allowed for the sanitized input. // Default: 0
|
* max_input_length?: int|Param, // The maximum length allowed for the sanitized input. // Default: 0
|
||||||
* }>,
|
* }>,
|
||||||
* },
|
* },
|
||||||
|
|
@ -718,7 +718,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter.
|
* servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter.
|
||||||
* sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver
|
* sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver
|
||||||
* server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere.
|
* server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere.
|
||||||
* default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connection.
|
* default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connexion.
|
||||||
* sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL.
|
* sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL.
|
||||||
* sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities.
|
* sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities.
|
||||||
* sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL.
|
* sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL.
|
||||||
|
|
@ -769,7 +769,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter.
|
* servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter.
|
||||||
* sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver
|
* sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver
|
||||||
* server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere.
|
* server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere.
|
||||||
* default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connection.
|
* default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connexion.
|
||||||
* sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL.
|
* sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL.
|
||||||
* sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities.
|
* sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities.
|
||||||
* sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL.
|
* sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL.
|
||||||
|
|
@ -801,7 +801,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter.
|
* servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter.
|
||||||
* sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver
|
* sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver
|
||||||
* server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere.
|
* server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere.
|
||||||
* default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connection.
|
* default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connexion.
|
||||||
* sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL.
|
* sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL.
|
||||||
* sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities.
|
* sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities.
|
||||||
* sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL.
|
* sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL.
|
||||||
|
|
@ -967,7 +967,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* auto_reload?: scalar|Param|null,
|
* auto_reload?: scalar|Param|null,
|
||||||
* optimizations?: int|Param,
|
* optimizations?: int|Param,
|
||||||
* default_path?: scalar|Param|null, // The default path used to load templates. // Default: "%kernel.project_dir%/templates"
|
* default_path?: scalar|Param|null, // The default path used to load templates. // Default: "%kernel.project_dir%/templates"
|
||||||
* file_name_pattern?: string|list<scalar|Param|null>,
|
* file_name_pattern?: list<scalar|Param|null>,
|
||||||
* paths?: array<string, mixed>,
|
* paths?: array<string, mixed>,
|
||||||
* date?: array{ // The default format options used by the date filter.
|
* date?: array{ // The default format options used by the date filter.
|
||||||
* format?: scalar|Param|null, // Default: "F j, Y H:i"
|
* format?: scalar|Param|null, // Default: "F j, Y H:i"
|
||||||
|
|
@ -1049,7 +1049,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* use_underscore?: bool|Param, // Default: true
|
* use_underscore?: bool|Param, // Default: true
|
||||||
* unordered_list_markers?: list<scalar|Param|null>,
|
* unordered_list_markers?: list<scalar|Param|null>,
|
||||||
* },
|
* },
|
||||||
* ...<string, mixed>
|
* ...<mixed>
|
||||||
* },
|
* },
|
||||||
* }
|
* }
|
||||||
* @psalm-type SecurityConfig = array{
|
* @psalm-type SecurityConfig = array{
|
||||||
|
|
@ -1067,7 +1067,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* },
|
* },
|
||||||
* password_hashers?: array<string, string|array{ // Default: []
|
* password_hashers?: array<string, string|array{ // Default: []
|
||||||
* algorithm?: scalar|Param|null,
|
* algorithm?: scalar|Param|null,
|
||||||
* migrate_from?: string|list<scalar|Param|null>,
|
* migrate_from?: list<scalar|Param|null>,
|
||||||
* hash_algorithm?: scalar|Param|null, // Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. // Default: "sha512"
|
* hash_algorithm?: scalar|Param|null, // Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. // Default: "sha512"
|
||||||
* key_length?: scalar|Param|null, // Default: 40
|
* key_length?: scalar|Param|null, // Default: 40
|
||||||
* ignore_case?: bool|Param, // Default: false
|
* ignore_case?: bool|Param, // Default: false
|
||||||
|
|
@ -1081,7 +1081,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* providers?: array<string, array{ // Default: []
|
* providers?: array<string, array{ // Default: []
|
||||||
* id?: scalar|Param|null,
|
* id?: scalar|Param|null,
|
||||||
* chain?: array{
|
* chain?: array{
|
||||||
* providers?: string|list<scalar|Param|null>,
|
* providers?: list<scalar|Param|null>,
|
||||||
* },
|
* },
|
||||||
* entity?: array{
|
* entity?: array{
|
||||||
* class?: scalar|Param|null, // The full entity class name of your user class.
|
* class?: scalar|Param|null, // The full entity class name of your user class.
|
||||||
|
|
@ -1091,7 +1091,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* memory?: array{
|
* memory?: array{
|
||||||
* users?: array<string, array{ // Default: []
|
* users?: array<string, array{ // Default: []
|
||||||
* password?: scalar|Param|null, // Default: null
|
* password?: scalar|Param|null, // Default: null
|
||||||
* roles?: string|list<scalar|Param|null>,
|
* roles?: list<scalar|Param|null>,
|
||||||
* }>,
|
* }>,
|
||||||
* },
|
* },
|
||||||
* ldap?: array{
|
* ldap?: array{
|
||||||
|
|
@ -1100,7 +1100,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* search_dn?: scalar|Param|null, // Default: null
|
* search_dn?: scalar|Param|null, // Default: null
|
||||||
* search_password?: scalar|Param|null, // Default: null
|
* search_password?: scalar|Param|null, // Default: null
|
||||||
* extra_fields?: list<scalar|Param|null>,
|
* extra_fields?: list<scalar|Param|null>,
|
||||||
* default_roles?: string|list<scalar|Param|null>,
|
* default_roles?: list<scalar|Param|null>,
|
||||||
* role_fetcher?: scalar|Param|null, // Default: null
|
* role_fetcher?: scalar|Param|null, // Default: null
|
||||||
* uid_key?: scalar|Param|null, // Default: "sAMAccountName"
|
* uid_key?: scalar|Param|null, // Default: "sAMAccountName"
|
||||||
* filter?: scalar|Param|null, // Default: "({uid_key}={user_identifier})"
|
* filter?: scalar|Param|null, // Default: "({uid_key}={user_identifier})"
|
||||||
|
|
@ -1110,7 +1110,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* firewalls?: array<string, array{ // Default: []
|
* firewalls?: array<string, array{ // Default: []
|
||||||
* pattern?: scalar|Param|null,
|
* pattern?: scalar|Param|null,
|
||||||
* host?: scalar|Param|null,
|
* host?: scalar|Param|null,
|
||||||
* methods?: string|list<scalar|Param|null>,
|
* methods?: list<scalar|Param|null>,
|
||||||
* security?: bool|Param, // Default: true
|
* security?: bool|Param, // Default: true
|
||||||
* user_checker?: scalar|Param|null, // The UserChecker to use when authenticating users in this firewall. // Default: "security.user_checker"
|
* user_checker?: scalar|Param|null, // The UserChecker to use when authenticating users in this firewall. // Default: "security.user_checker"
|
||||||
* request_matcher?: scalar|Param|null,
|
* request_matcher?: scalar|Param|null,
|
||||||
|
|
@ -1129,8 +1129,8 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* path?: scalar|Param|null, // Default: "/logout"
|
* path?: scalar|Param|null, // Default: "/logout"
|
||||||
* target?: scalar|Param|null, // Default: "/"
|
* target?: scalar|Param|null, // Default: "/"
|
||||||
* invalidate_session?: bool|Param, // Default: true
|
* invalidate_session?: bool|Param, // Default: true
|
||||||
* clear_site_data?: string|list<"*"|"cache"|"cookies"|"storage"|"executionContexts"|Param>,
|
* clear_site_data?: list<"*"|"cache"|"cookies"|"storage"|"executionContexts"|Param>,
|
||||||
* delete_cookies?: string|array<string, array{ // Default: []
|
* delete_cookies?: array<string, array{ // Default: []
|
||||||
* path?: scalar|Param|null, // Default: null
|
* path?: scalar|Param|null, // Default: null
|
||||||
* domain?: scalar|Param|null, // Default: null
|
* domain?: scalar|Param|null, // Default: null
|
||||||
* secure?: scalar|Param|null, // Default: false
|
* secure?: scalar|Param|null, // Default: false
|
||||||
|
|
@ -1268,7 +1268,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* success_handler?: scalar|Param|null,
|
* success_handler?: scalar|Param|null,
|
||||||
* failure_handler?: scalar|Param|null,
|
* failure_handler?: scalar|Param|null,
|
||||||
* realm?: scalar|Param|null, // Default: null
|
* realm?: scalar|Param|null, // Default: null
|
||||||
* token_extractors?: string|list<scalar|Param|null>,
|
* token_extractors?: list<scalar|Param|null>,
|
||||||
* token_handler?: string|array{
|
* token_handler?: string|array{
|
||||||
* id?: scalar|Param|null,
|
* id?: scalar|Param|null,
|
||||||
* oidc_user_info?: string|array{
|
* oidc_user_info?: string|array{
|
||||||
|
|
@ -1283,7 +1283,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* },
|
* },
|
||||||
* oidc?: array{
|
* oidc?: array{
|
||||||
* discovery?: array{ // Enable the OIDC discovery.
|
* discovery?: array{ // Enable the OIDC discovery.
|
||||||
* base_uri?: string|list<scalar|Param|null>,
|
* base_uri?: list<scalar|Param|null>,
|
||||||
* cache?: array{
|
* cache?: array{
|
||||||
* id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration.
|
* id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration.
|
||||||
* },
|
* },
|
||||||
|
|
@ -1326,7 +1326,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* remember_me?: array{
|
* remember_me?: array{
|
||||||
* secret?: scalar|Param|null, // Default: "%kernel.secret%"
|
* secret?: scalar|Param|null, // Default: "%kernel.secret%"
|
||||||
* service?: scalar|Param|null,
|
* service?: scalar|Param|null,
|
||||||
* user_providers?: string|list<scalar|Param|null>,
|
* user_providers?: list<scalar|Param|null>,
|
||||||
* catch_exceptions?: bool|Param, // Default: true
|
* catch_exceptions?: bool|Param, // Default: true
|
||||||
* signature_properties?: list<scalar|Param|null>,
|
* signature_properties?: list<scalar|Param|null>,
|
||||||
* token_provider?: string|array{
|
* token_provider?: string|array{
|
||||||
|
|
@ -1354,12 +1354,12 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* path?: scalar|Param|null, // Use the urldecoded format. // Default: null
|
* path?: scalar|Param|null, // Use the urldecoded format. // Default: null
|
||||||
* host?: scalar|Param|null, // Default: null
|
* host?: scalar|Param|null, // Default: null
|
||||||
* port?: int|Param, // Default: null
|
* port?: int|Param, // Default: null
|
||||||
* ips?: string|list<scalar|Param|null>,
|
* ips?: list<scalar|Param|null>,
|
||||||
* attributes?: array<string, scalar|Param|null>,
|
* attributes?: array<string, scalar|Param|null>,
|
||||||
* route?: scalar|Param|null, // Default: null
|
* route?: scalar|Param|null, // Default: null
|
||||||
* methods?: string|list<scalar|Param|null>,
|
* methods?: list<scalar|Param|null>,
|
||||||
* allow_if?: scalar|Param|null, // Default: null
|
* allow_if?: scalar|Param|null, // Default: null
|
||||||
* roles?: string|list<scalar|Param|null>,
|
* roles?: list<scalar|Param|null>,
|
||||||
* }>,
|
* }>,
|
||||||
* role_hierarchy?: array<string, string|list<scalar|Param|null>>,
|
* role_hierarchy?: array<string, string|list<scalar|Param|null>>,
|
||||||
* }
|
* }
|
||||||
|
|
@ -1440,7 +1440,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* delay_between_messages?: bool|Param, // Default: false
|
* delay_between_messages?: bool|Param, // Default: false
|
||||||
* topic?: int|Param, // Default: null
|
* topic?: int|Param, // Default: null
|
||||||
* factor?: int|Param, // Default: 1
|
* factor?: int|Param, // Default: 1
|
||||||
* tags?: string|list<scalar|Param|null>,
|
* tags?: list<scalar|Param|null>,
|
||||||
* console_formatter_options?: mixed, // Default: []
|
* console_formatter_options?: mixed, // Default: []
|
||||||
* formatter?: scalar|Param|null,
|
* formatter?: scalar|Param|null,
|
||||||
* nested?: bool|Param, // Default: false
|
* nested?: bool|Param, // Default: false
|
||||||
|
|
@ -1484,7 +1484,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||||
* host?: scalar|Param|null,
|
* host?: scalar|Param|null,
|
||||||
* },
|
* },
|
||||||
* from_email?: scalar|Param|null,
|
* from_email?: scalar|Param|null,
|
||||||
* to_email?: string|list<scalar|Param|null>,
|
* to_email?: list<scalar|Param|null>,
|
||||||
* subject?: scalar|Param|null,
|
* subject?: scalar|Param|null,
|
||||||
* content_type?: scalar|Param|null, // Default: null
|
* content_type?: scalar|Param|null, // Default: null
|
||||||
* headers?: list<scalar|Param|null>,
|
* headers?: list<scalar|Param|null>,
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Controller;
|
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
|
||||||
|
|
||||||
#[Route('/calculator')]
|
|
||||||
final class CalculatorController extends AbstractController
|
|
||||||
{
|
|
||||||
#[Route('/', name: 'app_calculator')]
|
|
||||||
public function index(): Response
|
|
||||||
{
|
|
||||||
return $this->render('calculator/index.html.twig', [
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -26,9 +26,8 @@
|
||||||
|
|
||||||
{% block stylesheets %}{% endblock %}
|
{% block stylesheets %}{% endblock %}
|
||||||
|
|
||||||
{% block importmap %}{{ importmap('app') }}{% endblock %}
|
|
||||||
{% block javascripts %}
|
{% block javascripts %}
|
||||||
|
{% block importmap %}{{ importmap('app') }}{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
{% extends 'base.html.twig' %}
|
|
||||||
|
|
||||||
{% block title %}Calcul{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block stylesheets %}
|
|
||||||
{{ parent() }}
|
|
||||||
<link rel="stylesheet" href="{{ asset('styles/globals/bulles.css') }}">
|
|
||||||
{# Styles spécifiques au calculateur (inputs, readouts, tip, chart) #}
|
|
||||||
<link rel="stylesheet" href="{{ asset('styles/calculator/index.css') }}">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
<div class="bulle-container aero-calc" data-controller="calculateur">
|
|
||||||
<div class="bulle bulle-all-col bulle-header">
|
|
||||||
<h1>Calulateur pour Create Aeronautics.</h1>
|
|
||||||
<p class="ac-sub">Calculs dérivés des mods Aeronautics et Sable. Le ballon est supposé pleinement chauffé (volume rempli = capacité).</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bulle bulle-all-col">
|
|
||||||
<details class="ac-consts">
|
|
||||||
<summary>Constantes (config par défaut)</summary>
|
|
||||||
<div class="ac-consts-grid">
|
|
||||||
<div class="ac-field"><label>C_T <span class="u">poussée</span></label><input id="aero-ct" type="number" step="any" value="0.2"></div>
|
|
||||||
<div class="ac-field"><label>s_air <span class="u">kpg/m³</span></label><input id="aero-sair" type="number" step="any" value="1.5"></div>
|
|
||||||
<div class="ac-field"><label>g <span class="u">gravité</span></label><input id="aero-g" type="number" step="any" value="11"></div>
|
|
||||||
<div class="ac-field"><label>y_mer <span class="u">niveau mer</span></label><input id="aero-sea" type="number" step="any" value="63"></div>
|
|
||||||
<div class="ac-field"><label>k <span class="u">pente pression</span></label><input id="aero-k" type="number" step="any" value="-0.004"></div>
|
|
||||||
<div class="ac-field"><label>P_max <span class="u">plafond</span></label><input id="aero-pmax" type="number" step="any" value="1.5"></div>
|
|
||||||
<div class="ac-field"><label>y_top <span class="u">limite haute</span></label><input id="aero-ytop" type="number" step="any" value="320"></div>
|
|
||||||
<div class="ac-field"><label>y_bas <span class="u">limite basse</span></label><input id="aero-ybot" type="number" step="any" value="-64"></div>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bulle bulle-all-col">
|
|
||||||
|
|
||||||
<div class="ac-tool">
|
|
||||||
<h2 class="ac-h3">Hélice</h2>
|
|
||||||
<p class="ac-hint">Poussée le long de l'axe du bearing.</p>
|
|
||||||
<div class="ac-field"><label>Voiles (sails) <span class="u">P</span></label><input id="aero-sails" type="number" min="0" step="1" value="24"></div>
|
|
||||||
<div class="ac-field"><label>Vitesse de rotation <span class="u">N · RPM</span></label><input id="aero-rpm" type="number" step="any" value="256"></div>
|
|
||||||
<div class="ac-field"><label>Altitude <span class="u">y</span></label><input id="aero-palt" type="number" step="any" value="63"></div>
|
|
||||||
<div class="ac-readout"><div class="ac-rk">Poussée brute</div><div class="ac-rv key" id="aero-othrust">0 <small>pN</small></div></div>
|
|
||||||
<div class="ac-readout"><div class="ac-rk">Appliquée à l'arrêt (× P_air)</div><div class="ac-rv" id="aero-othrustapp">0 <small>pN</small></div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bulle bulle-all-col">
|
|
||||||
<div class="ac-tool">
|
|
||||||
<h2 class="ac-h3">Ballon chauffé</h2>
|
|
||||||
<p class="ac-hint">Pleinement chauffé : volume rempli = capacité V.</p>
|
|
||||||
<div class="ac-field"><label>Volume intérieur <span class="u">V · m³</span></label><input id="aero-vol" type="number" min="0" step="any" value="512"></div>
|
|
||||||
<div class="ac-field"><label>Altitude <span class="u">y</span></label><input id="aero-balt" type="number" step="any" value="63"></div>
|
|
||||||
<div class="ac-readout"><div class="ac-rk">Force ascensionnelle brute</div><div class="ac-rv key" id="aero-oforce">0 <small>pN</small></div></div>
|
|
||||||
<div class="ac-readout"><div class="ac-rk">Portance (masse soulevable)</div><div class="ac-rv" id="aero-olift">0 <small>kpg</small></div></div>
|
|
||||||
<div class="ac-readout"><div class="ac-rk">Pression locale</div><div class="ac-rv" id="aero-opress">1.000</div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bulle bulle-all-col">
|
|
||||||
<h2 class="ac-h2">Altitude maximale du ballon</h2>
|
|
||||||
<p class="ac-desc">Altitude de flottaison neutre où la portance compense le poids.</p>
|
|
||||||
|
|
||||||
<div class="ac-controls">
|
|
||||||
<div class="ac-field">
|
|
||||||
<label>Tracer en fonction</label>
|
|
||||||
<select id="aero-mode">
|
|
||||||
<option value="mass">du poids (volume fixé)</option>
|
|
||||||
<option value="vol">du volume (poids fixé)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="ac-field" id="aero-fixvolwrap"><label>Volume fixé <span class="u">V · m³</span></label><input id="aero-fixvol" type="number" min="1" step="any" value="512"></div>
|
|
||||||
<div class="ac-field" id="aero-fixmasswrap"><label>Poids fixé <span class="u">m · kpg</span></label><input id="aero-fixmass" type="number" min="1" step="any" value="400"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ac-chart-shell">
|
|
||||||
<canvas id="aero-chart"></canvas>
|
|
||||||
<div class="ac-tip" id="aero-tip"></div>
|
|
||||||
</div>
|
|
||||||
<div class="ac-legend">
|
|
||||||
<span><i class="ac-sw"></i> altitude max</span>
|
|
||||||
<span><i class="ac-sw"></i> repères (mer, limite)</span>
|
|
||||||
<span><i class="ac-sw"></i> ne décolle pas</span>
|
|
||||||
</div>
|
|
||||||
<p class="ac-note"><b>m</b> kpg = <b>m</b> blocs standard (masse défaut 1 kpg/bloc). L'atténuation de pression sur les 40 derniers mètres sous la limite haute n'est pas modélisée.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<section class="bulle bulle-all-col">
|
|
||||||
<h2 class="ac-h2">Volume requis</h2>
|
|
||||||
<p class="ac-desc">Volume d'air chaud minimal pour flotter à une altitude visée.</p>
|
|
||||||
<div class="ac-vol">
|
|
||||||
<div class="ac-vol-inputs">
|
|
||||||
<div class="ac-field"><label>Poids total <span class="u">m · kpg</span></label><input id="aero-vmass" type="number" min="0" step="any" value="400"></div>
|
|
||||||
<div class="ac-field"><label>Altitude visée <span class="u">y</span></label><input id="aero-valt" type="number" step="any" value="150"></div>
|
|
||||||
</div>
|
|
||||||
<div class="ac-vol-out">
|
|
||||||
<div class="ac-readout"><div class="ac-rk">Volume requis</div><div class="ac-rv key" id="aero-ovolreq">0 <small>m³</small></div></div>
|
|
||||||
<p class="ac-cube" id="aero-ocube"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="ac-note" id="aero-vwarn"></p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="bulle bulle-all-col">
|
|
||||||
<h2 class="ac-h2">Formules</h2>
|
|
||||||
<div class="ac-formulas">
|
|
||||||
<div><div class="ac-fk">Poussée hélice brute</div><code>T = C_T · P^(3/2) · N</code></div>
|
|
||||||
<div><div class="ac-fk">Appliquée (à l'arrêt, A_flux = 1)</div><code>F = T · A_flux · P_air</code></div>
|
|
||||||
<div><div class="ac-fk">Force ballon brute (pleinement chauffé)</div><code>F = V · s_air · g · P_air(y)</code></div>
|
|
||||||
<div><div class="ac-fk">Portance (masse)</div><code>L = V · s_air · P_air(y)</code></div>
|
|
||||||
<div><div class="ac-fk">Pression atmosphérique</div><code>P_air(y) = min( e^(k·(y − y_mer)) , P_max )</code></div>
|
|
||||||
<div><div class="ac-fk">Altitude max (flottaison neutre)</div><code>y_max = y_mer + ln( V·s_air / m ) / k</code></div>
|
|
||||||
<div><div class="ac-fk">Volume requis</div><code>V = m / ( s_air · P_air(y) )</code></div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block javascripts %}
|
|
||||||
{{ parent() }}
|
|
||||||
<script>
|
|
||||||
// Fallback: si Stimulus n'a pas attaché le controller (par ex. problème de chargement),
|
|
||||||
// on charge l'ancien script autonome qui cible la class .aero-calc.
|
|
||||||
setTimeout(function(){
|
|
||||||
try{
|
|
||||||
const root = document.querySelector('.aero-calc');
|
|
||||||
if(root && root.dataset.calculateurConnected !== '1'){
|
|
||||||
const s = document.createElement('script');
|
|
||||||
s.type = 'module';
|
|
||||||
s.src = '{{ asset('js/calcultateur_create.js') }}';
|
|
||||||
document.head.appendChild(s);
|
|
||||||
console.warn('calculateur: fallback legacy script injected');
|
|
||||||
}
|
|
||||||
}catch(e){ console.error(e); }
|
|
||||||
}, 300);
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
@ -40,17 +40,11 @@
|
||||||
Statistiques
|
Statistiques
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ path('app_accueil') }}"
|
<a href="{{ path('app_accueil') }}"
|
||||||
class="nav-link {{ current_route == 'app_news' ? 'active' }}"
|
class="nav-link {{ current_route == 'app_home' ? 'active' }}"
|
||||||
data-action="click->header-mobile#close"
|
data-action="click->header-mobile#close"
|
||||||
>
|
>
|
||||||
Actualité
|
Actualité
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ path('app_calculator') }}"
|
|
||||||
class="nav-link {{ current_route == 'app_calculator' ? 'active' }}"
|
|
||||||
data-action="click->header-mobile#close"
|
|
||||||
>
|
|
||||||
Calculateur
|
|
||||||
</a>
|
|
||||||
<a href="{{ path('app_maps') }}"
|
<a href="{{ path('app_maps') }}"
|
||||||
class="nav-link {{ current_route == 'app_maps' ? 'active' }}"
|
class="nav-link {{ current_route == 'app_maps' ? 'active' }}"
|
||||||
data-action="click->header-mobile#close"
|
data-action="click->header-mobile#close"
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
# Le code source est copié dans l'image au moment du build
|
# Le code source est copié dans l'image au moment du build
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
FROM php:8.5-apache
|
FROM php:8.3-apache
|
||||||
|
|
||||||
# ── Dépendances système ──────────────────────────────────────
|
# ── Dépendances système ──────────────────────────────────────
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
|
|
@ -17,6 +17,7 @@ RUN apt-get update && apt-get install -y \
|
||||||
pdo \
|
pdo \
|
||||||
pdo_pgsql \
|
pdo_pgsql \
|
||||||
zip \
|
zip \
|
||||||
|
opcache \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue