This commit is contained in:
parent
9529e3747c
commit
413486958c
13 changed files with 1410 additions and 88 deletions
|
|
@ -34,6 +34,101 @@
|
|||
const shell = canvas.closest("[data-field-shell]");
|
||||
const setState = (s) => shell && shell.setAttribute("data-field-state", s);
|
||||
|
||||
/* ── 데모 조작 UI ────────────────────────────────────────────────
|
||||
WebGL 이 없거나 모션을 줄여도 조작의 의미는 남아야 한다.
|
||||
그래서 슬라이더·장면·실물 뷰 전환은 그래픽 초기화보다 먼저 묶는다. */
|
||||
const controls = {
|
||||
load: Number(document.getElementById("ctlLoad")?.value ?? 70) / 100,
|
||||
supply: Number(document.getElementById("ctlSupply")?.value ?? 65) / 100,
|
||||
};
|
||||
|
||||
const presetButtons = [...document.querySelectorAll("[data-field-preset]")];
|
||||
const fieldState = document.getElementById("fieldState");
|
||||
const fieldScene = document.getElementById("fieldScene");
|
||||
const fieldWatch = document.getElementById("fieldWatch");
|
||||
const fieldAction = document.getElementById("fieldAction");
|
||||
|
||||
const activePreset = () => presetButtons.find((button) => button.getAttribute("aria-pressed") === "true");
|
||||
|
||||
function updateReadout() {
|
||||
const gap = Math.round((controls.load - controls.supply) * 100);
|
||||
const selected = activePreset();
|
||||
if (fieldScene) fieldScene.textContent = selected?.textContent.trim() || "수동 조정";
|
||||
|
||||
let state = "냉기와 발열이 맞서는 기준 상태입니다.";
|
||||
let watch = "랙 배면";
|
||||
let action = "부하와 급기를 따로 움직여 비교";
|
||||
|
||||
if (gap >= 35) {
|
||||
state = "발열이 급기보다 크게 앞섭니다. 배면에서 천장으로 오르는 열기둥을 확인하십시오.";
|
||||
watch = "랙 배면 · 천장 배기";
|
||||
action = "급기 상향과 부하 재배분을 비교";
|
||||
} else if (gap >= 15) {
|
||||
state = "발열 우세 구간입니다. 랙 뒤에 머무는 고온 입자의 경로를 비교하십시오.";
|
||||
watch = "랙 배면";
|
||||
action = "급기량을 한 단계씩 높여 비교";
|
||||
} else if (gap <= -15) {
|
||||
state = "급기가 발열을 앞섭니다. FWU 제트가 만드는 저온 구간을 확인하십시오.";
|
||||
watch = "FWU 제트 · 냉점";
|
||||
action = "과냉 구간과 팬 운전을 함께 검토";
|
||||
}
|
||||
|
||||
if (fieldState) fieldState.textContent = state;
|
||||
if (fieldWatch) fieldWatch.textContent = watch;
|
||||
if (fieldAction) fieldAction.textContent = action;
|
||||
}
|
||||
|
||||
function matchPreset() {
|
||||
const load = Math.round(controls.load * 100);
|
||||
const supply = Math.round(controls.supply * 100);
|
||||
let matched = false;
|
||||
for (const button of presetButtons) {
|
||||
const same = Number(button.dataset.load) === load && Number(button.dataset.supply) === supply;
|
||||
button.setAttribute("aria-pressed", String(same));
|
||||
matched ||= same;
|
||||
}
|
||||
if (!matched) presetButtons.forEach((button) => button.setAttribute("aria-pressed", "false"));
|
||||
}
|
||||
|
||||
function bindSlider(id, key, outId, fmt) {
|
||||
const el = document.getElementById(id);
|
||||
const out = document.getElementById(outId);
|
||||
if (!el) return;
|
||||
const sync = () => {
|
||||
controls[key] = Number(el.value) / 100;
|
||||
if (out) out.textContent = fmt(Number(el.value));
|
||||
matchPreset();
|
||||
updateReadout();
|
||||
};
|
||||
el.addEventListener("input", sync);
|
||||
sync();
|
||||
}
|
||||
|
||||
bindSlider("ctlLoad", "load", "outLoad", (v) => `${v}%`);
|
||||
bindSlider("ctlSupply", "supply", "outSupply", (v) => `${v}%`);
|
||||
|
||||
for (const button of presetButtons) {
|
||||
button.addEventListener("click", () => {
|
||||
const load = document.getElementById("ctlLoad");
|
||||
const supply = document.getElementById("ctlSupply");
|
||||
if (!(load instanceof HTMLInputElement) || !(supply instanceof HTMLInputElement)) return;
|
||||
load.value = button.dataset.load || load.value;
|
||||
supply.value = button.dataset.supply || supply.value;
|
||||
load.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
supply.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
});
|
||||
}
|
||||
|
||||
const visTabs = [...document.querySelectorAll("[data-vis-target]")];
|
||||
const visPanels = [...document.querySelectorAll("[data-vis-panel]")];
|
||||
for (const tab of visTabs) {
|
||||
tab.addEventListener("click", () => {
|
||||
const target = tab.dataset.visTarget;
|
||||
for (const button of visTabs) button.setAttribute("aria-pressed", String(button === tab));
|
||||
for (const panel of visPanels) panel.hidden = panel.dataset.visPanel !== target;
|
||||
});
|
||||
}
|
||||
|
||||
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||
if (reduced.matches) {
|
||||
setState("static");
|
||||
|
|
@ -450,27 +545,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* ── 조작 입력 ────────────────────────────────────────────────── */
|
||||
const controls = {
|
||||
load: Number(document.getElementById("ctlLoad")?.value ?? 70) / 100,
|
||||
supply: Number(document.getElementById("ctlSupply")?.value ?? 65) / 100,
|
||||
};
|
||||
|
||||
function bindSlider(id, key, outId, fmt) {
|
||||
const el = document.getElementById(id);
|
||||
const out = document.getElementById(outId);
|
||||
if (!el) return;
|
||||
const sync = () => {
|
||||
controls[key] = Number(el.value) / 100;
|
||||
if (out) out.textContent = fmt(Number(el.value));
|
||||
};
|
||||
el.addEventListener("input", sync);
|
||||
sync();
|
||||
}
|
||||
|
||||
bindSlider("ctlLoad", "load", "outLoad", (v) => `${v}%`);
|
||||
bindSlider("ctlSupply", "supply", "outSupply", (v) => `${v}%`);
|
||||
|
||||
const pointer = { x: 0, y: 0, on: 0 };
|
||||
const onMove = (e) => {
|
||||
const r = canvas.getBoundingClientRect();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue