This commit is contained in:
parent
9529e3747c
commit
413486958c
13 changed files with 1410 additions and 88 deletions
|
|
@ -27,7 +27,8 @@
|
|||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap"
|
||||
/>
|
||||
<link rel="stylesheet" href="./styles/main.css" />
|
||||
<link rel="stylesheet" href="./styles/tokens.css?v=2" />
|
||||
<link rel="stylesheet" href="./styles/main.css?v=2" />
|
||||
<link
|
||||
rel="icon"
|
||||
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%230E1114'/%3E%3Cpath d='M4 21c4-6 8 4 12-2s8 3 12-3' stroke='%233E8FB0' stroke-width='2.4' fill='none' stroke-linecap='round'/%3E%3Cpath d='M4 27c4-6 8 4 12-2s8 3 12-3' stroke='%238CC152' stroke-width='2.4' fill='none' stroke-linecap='round' opacity='.75'/%3E%3C/svg%3E"
|
||||
|
|
@ -101,6 +102,40 @@
|
|||
시뮬레이터.
|
||||
</p>
|
||||
|
||||
<div class="scenario-strip" role="group" aria-label="브라우저 데모 운전 장면">
|
||||
<p class="scenario-label">운전 장면</p>
|
||||
<button
|
||||
class="scenario-btn"
|
||||
type="button"
|
||||
data-field-preset="baseline"
|
||||
data-load="70"
|
||||
data-supply="65"
|
||||
aria-pressed="true"
|
||||
>
|
||||
기준 운전
|
||||
</button>
|
||||
<button
|
||||
class="scenario-btn"
|
||||
type="button"
|
||||
data-field-preset="overload"
|
||||
data-load="95"
|
||||
data-supply="50"
|
||||
aria-pressed="false"
|
||||
>
|
||||
랙 과부하
|
||||
</button>
|
||||
<button
|
||||
class="scenario-btn"
|
||||
type="button"
|
||||
data-field-preset="supply-loss"
|
||||
data-load="65"
|
||||
data-supply="25"
|
||||
aria-pressed="false"
|
||||
>
|
||||
급기 저하
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="hero-controls">
|
||||
<div class="ctl">
|
||||
<div class="ctl-head">
|
||||
|
|
@ -118,6 +153,27 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-readout" aria-live="polite" aria-atomic="true">
|
||||
<div class="field-readout-head">
|
||||
<p class="field-readout-label mono">브라우저 데모 해석</p>
|
||||
<p class="field-readout-state" id="fieldState">냉기와 발열이 맞서는 기준 상태입니다.</p>
|
||||
</div>
|
||||
<dl class="field-readout-grid">
|
||||
<div>
|
||||
<dt>장면</dt>
|
||||
<dd id="fieldScene">기준 운전</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>먼저 볼 곳</dt>
|
||||
<dd id="fieldWatch">랙 배면</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>다음 조작</dt>
|
||||
<dd id="fieldAction">부하와 급기를 따로 움직여 비교</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<p class="hero-note">
|
||||
위 화면은 브라우저에서 도는 <strong>2D 축소판</strong>이다. 슬라이더를 움직이거나 화면을
|
||||
문지르면 유동이 즉시 반응한다. 실물 RFS 는 Unity 6.4 위에서 3D δ-SPH 를 돌린다 —
|
||||
|
|
@ -329,28 +385,42 @@
|
|||
</ol>
|
||||
|
||||
<div class="vis-shots">
|
||||
<figure>
|
||||
<img
|
||||
src="./assets/sph-topview.webp"
|
||||
width="689"
|
||||
height="680"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
alt="RFS 상면 뷰 — 랙 상부에서 데워진 입자가 초록으로 퍼지고 주변 저온 입자는 청록으로 남는다"
|
||||
/>
|
||||
<figcaption>상면 · 랙 상부의 열기둥</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img
|
||||
src="./assets/sph-sideview.webp"
|
||||
width="691"
|
||||
height="437"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
alt="RFS 측면 뷰 — 랙을 통과하며 데워진 공기가 오른쪽으로 밀려 나가는 단면"
|
||||
/>
|
||||
<figcaption>측면 · 랙을 통과한 공기</figcaption>
|
||||
</figure>
|
||||
<div class="vis-toolbar">
|
||||
<div class="vis-tabs" role="group" aria-label="RFS 실물 파티클 뷰 선택">
|
||||
<button class="vis-tab" type="button" data-vis-target="top" aria-pressed="true">상면</button>
|
||||
<button class="vis-tab" type="button" data-vis-target="side" aria-pressed="false">측면</button>
|
||||
</div>
|
||||
<p class="vis-source mono">RFS 실물 렌더</p>
|
||||
</div>
|
||||
<div class="vis-frame">
|
||||
<figure data-vis-panel="top">
|
||||
<img
|
||||
src="./assets/sph-topview.webp"
|
||||
width="689"
|
||||
height="680"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
alt="RFS 상면 뷰 — 랙 상부에서 데워진 입자가 초록으로 퍼지고 주변 저온 입자는 청록으로 남는다"
|
||||
/>
|
||||
<figcaption>상면 · 랙 상부의 열기둥</figcaption>
|
||||
</figure>
|
||||
<figure data-vis-panel="side" hidden>
|
||||
<img
|
||||
src="./assets/sph-sideview.webp"
|
||||
width="691"
|
||||
height="437"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
alt="RFS 측면 뷰 — 랙을 통과하며 데워진 공기가 오른쪽으로 밀려 나가는 단면"
|
||||
/>
|
||||
<figcaption>측면 · 랙을 통과한 공기</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="temp-key" role="list" aria-label="파티클 온도 색상 범례">
|
||||
<span role="listitem"><i class="temp-cold" aria-hidden="true"></i>저온</span>
|
||||
<span role="listitem"><i class="temp-mid" aria-hidden="true"></i>중간</span>
|
||||
<span role="listitem"><i class="temp-hot" aria-hidden="true"></i>고온</span>
|
||||
</div>
|
||||
<p class="shots-note">
|
||||
두 장 모두 RFS 실물의 파티클 뷰다. 저온이 청록, 고온이 초록이다 —
|
||||
이 페이지 첫 화면의 색도 같은 규칙을 쓴다.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
@import "./tokens.css";
|
||||
|
||||
/* ==========================================================================
|
||||
RFS — 메인 스타일시트
|
||||
|
||||
|
|
@ -361,8 +359,48 @@ h3 {
|
|||
color: var(--band-ink-muted);
|
||||
}
|
||||
|
||||
.hero-controls {
|
||||
.scenario-strip {
|
||||
margin-top: var(--space-5);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
max-width: 760px;
|
||||
}
|
||||
|
||||
.scenario-label {
|
||||
margin-right: var(--space-2);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--step--1);
|
||||
color: var(--band-ink-faint);
|
||||
}
|
||||
|
||||
.scenario-btn,
|
||||
.vis-tab {
|
||||
min-height: 40px;
|
||||
padding-inline: var(--space-3);
|
||||
border: 1px solid var(--band-line-strong);
|
||||
background: transparent;
|
||||
color: var(--band-ink-muted);
|
||||
cursor: pointer;
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.scenario-btn:hover,
|
||||
.vis-tab:hover {
|
||||
color: var(--band-ink);
|
||||
border-color: var(--band-accent);
|
||||
}
|
||||
|
||||
.scenario-btn[aria-pressed="true"],
|
||||
.vis-tab[aria-pressed="true"] {
|
||||
color: var(--band-accent-ink);
|
||||
background: var(--band-accent);
|
||||
border-color: var(--band-accent);
|
||||
}
|
||||
|
||||
.hero-controls {
|
||||
margin-top: var(--space-4);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: var(--space-4);
|
||||
|
|
@ -389,6 +427,58 @@ input[type="range"] {
|
|||
background: transparent;
|
||||
}
|
||||
|
||||
.field-readout {
|
||||
margin-top: var(--space-5);
|
||||
max-width: 880px;
|
||||
border-block: 1px solid var(--band-line-strong);
|
||||
}
|
||||
|
||||
.field-readout-head {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
padding-block: var(--space-3);
|
||||
border-bottom: 1px solid var(--band-line);
|
||||
}
|
||||
|
||||
.field-readout-label,
|
||||
.field-readout-grid dt {
|
||||
font-size: var(--step--1);
|
||||
color: var(--band-ink-faint);
|
||||
}
|
||||
|
||||
.field-readout-state {
|
||||
color: var(--band-ink);
|
||||
}
|
||||
|
||||
.field-readout-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.field-readout-grid > div {
|
||||
min-width: 0;
|
||||
padding-block: var(--space-3);
|
||||
}
|
||||
|
||||
.field-readout-grid > div + div {
|
||||
padding-left: var(--space-3);
|
||||
border-left: 1px solid var(--band-line);
|
||||
}
|
||||
|
||||
.field-readout-grid dd {
|
||||
margin-top: var(--space-1);
|
||||
color: var(--band-ink-muted);
|
||||
font-size: var(--step--1);
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
|
||||
@media (min-width: 720px) {
|
||||
.field-readout-head {
|
||||
grid-template-columns: 9rem minmax(0, 1fr);
|
||||
align-items: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-note {
|
||||
margin-top: var(--space-5);
|
||||
padding-top: var(--space-3);
|
||||
|
|
@ -692,17 +782,54 @@ input[type="range"] {
|
|||
|
||||
.vis-shots {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
gap: var(--space-3);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.vis-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
border-bottom: 1px solid var(--band-line-strong);
|
||||
padding-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.vis-tabs {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vis-tab + .vis-tab {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.vis-source {
|
||||
color: var(--band-ink-faint);
|
||||
font-size: var(--step--1);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.vis-frame {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.vis-shots figure {
|
||||
display: grid;
|
||||
grid-template-rows: minmax(0, 1fr) auto;
|
||||
border: 1px solid var(--band-line);
|
||||
background: var(--band-surface-2);
|
||||
min-height: clamp(360px, 42vw, 540px);
|
||||
}
|
||||
|
||||
.vis-shots figure[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vis-shots img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.vis-shots figcaption {
|
||||
|
|
@ -713,6 +840,35 @@ input[type="range"] {
|
|||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.temp-key {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-3);
|
||||
color: var(--band-ink-muted);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.temp-key span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.temp-key i {
|
||||
width: 1.5rem;
|
||||
height: 2px;
|
||||
background: var(--temp-cold);
|
||||
}
|
||||
|
||||
.temp-key .temp-mid {
|
||||
background: var(--temp-mid);
|
||||
}
|
||||
|
||||
.temp-key .temp-hot {
|
||||
background: var(--temp-hot);
|
||||
}
|
||||
|
||||
.shots-note {
|
||||
font-size: var(--step--1);
|
||||
color: var(--band-ink-faint);
|
||||
|
|
@ -979,6 +1135,35 @@ kbd {
|
|||
font-size: 0.6875rem;
|
||||
}
|
||||
|
||||
.scenario-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.scenario-label {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.scenario-btn,
|
||||
.vis-tab {
|
||||
min-height: 44px;
|
||||
padding-inline: var(--space-2);
|
||||
}
|
||||
|
||||
.field-readout-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.field-readout-grid > div + div {
|
||||
padding-left: 0;
|
||||
border-left: 0;
|
||||
border-top: 1px solid var(--band-line);
|
||||
}
|
||||
|
||||
.vis-shots figure {
|
||||
min-height: 320px;
|
||||
}
|
||||
|
||||
.figures {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
|
@ -1014,6 +1199,8 @@ kbd {
|
|||
.site-head,
|
||||
.hero-stage,
|
||||
.hero-controls,
|
||||
.scenario-strip,
|
||||
.field-readout,
|
||||
.skip-link {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@
|
|||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap"
|
||||
/>
|
||||
<link rel="stylesheet" href="./styles/main.css" />
|
||||
<link rel="stylesheet" href="./styles/tokens.css?v=2" />
|
||||
<link rel="stylesheet" href="./styles/main.css?v=2" />
|
||||
<link
|
||||
rel="icon"
|
||||
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%2316181A'/%3E%3Cpath d='M6 20h20' stroke='%237FA8D9' stroke-width='2'/%3E%3Ccircle cx='13' cy='14' r='4.5' fill='none' stroke='%23F3F1EC' stroke-width='2'/%3E%3Cpath d='M13 9.5V7' stroke='%23F3F1EC' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"
|
||||
|
|
@ -97,7 +98,21 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">백도 복숭아</th>
|
||||
<th scope="row">
|
||||
<button
|
||||
class="board-select"
|
||||
type="button"
|
||||
data-market-row
|
||||
data-name="백도 복숭아"
|
||||
data-origin="경북 청도"
|
||||
data-grade="특 · 12.5 Bx"
|
||||
data-pack="4.5 kg"
|
||||
data-price="38,000원"
|
||||
data-delta="▲ 2,000원"
|
||||
data-auction="02:00 경매"
|
||||
aria-pressed="true"
|
||||
>백도 복숭아</button>
|
||||
</th>
|
||||
<td data-label="산지">경북 청도</td>
|
||||
<td data-label="등급" class="c-num mono">특</td>
|
||||
<td data-label="당도" class="c-num mono">12.5<span class="unit">Bx</span></td>
|
||||
|
|
@ -106,7 +121,21 @@
|
|||
<td data-label="전일 대비" class="c-num mono delta up">▲ 2,000</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">샤인머스캣</th>
|
||||
<th scope="row">
|
||||
<button
|
||||
class="board-select"
|
||||
type="button"
|
||||
data-market-row
|
||||
data-name="샤인머스캣"
|
||||
data-origin="경북 상주"
|
||||
data-grade="특 · 18.2 Bx"
|
||||
data-pack="2 kg"
|
||||
data-price="26,500원"
|
||||
data-delta="▼ 1,500원"
|
||||
data-auction="02:00 경매"
|
||||
aria-pressed="false"
|
||||
>샤인머스캣</button>
|
||||
</th>
|
||||
<td data-label="산지">경북 상주</td>
|
||||
<td data-label="등급" class="c-num mono">특</td>
|
||||
<td data-label="당도" class="c-num mono">18.2<span class="unit">Bx</span></td>
|
||||
|
|
@ -115,7 +144,21 @@
|
|||
<td data-label="전일 대비" class="c-num mono delta down">▼ 1,500</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">캠벨얼리</th>
|
||||
<th scope="row">
|
||||
<button
|
||||
class="board-select"
|
||||
type="button"
|
||||
data-market-row
|
||||
data-name="캠벨얼리"
|
||||
data-origin="충북 영동"
|
||||
data-grade="상 · 15.4 Bx"
|
||||
data-pack="5 kg"
|
||||
data-price="31,000원"
|
||||
data-delta="보합"
|
||||
data-auction="02:00 경매"
|
||||
aria-pressed="false"
|
||||
>캠벨얼리</button>
|
||||
</th>
|
||||
<td data-label="산지">충북 영동</td>
|
||||
<td data-label="등급" class="c-num mono">상</td>
|
||||
<td data-label="당도" class="c-num mono">15.4<span class="unit">Bx</span></td>
|
||||
|
|
@ -124,7 +167,21 @@
|
|||
<td data-label="전일 대비" class="c-num mono delta flat">보합</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">홍로 사과</th>
|
||||
<th scope="row">
|
||||
<button
|
||||
class="board-select"
|
||||
type="button"
|
||||
data-market-row
|
||||
data-name="홍로 사과"
|
||||
data-origin="경북 영주"
|
||||
data-grade="특 · 14.1 Bx"
|
||||
data-pack="5 kg"
|
||||
data-price="44,000원"
|
||||
data-delta="▲ 3,500원"
|
||||
data-auction="08:30 경매"
|
||||
aria-pressed="false"
|
||||
>홍로 사과</button>
|
||||
</th>
|
||||
<td data-label="산지">경북 영주</td>
|
||||
<td data-label="등급" class="c-num mono">특</td>
|
||||
<td data-label="당도" class="c-num mono">14.1<span class="unit">Bx</span></td>
|
||||
|
|
@ -133,7 +190,21 @@
|
|||
<td data-label="전일 대비" class="c-num mono delta up">▲ 3,500</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">원황 배</th>
|
||||
<th scope="row">
|
||||
<button
|
||||
class="board-select"
|
||||
type="button"
|
||||
data-market-row
|
||||
data-name="원황 배"
|
||||
data-origin="전남 나주"
|
||||
data-grade="상 · 12.8 Bx"
|
||||
data-pack="7.5 kg"
|
||||
data-price="52,000원"
|
||||
data-delta="▼ 2,000원"
|
||||
data-auction="08:30 경매"
|
||||
aria-pressed="false"
|
||||
>원황 배</button>
|
||||
</th>
|
||||
<td data-label="산지">전남 나주</td>
|
||||
<td data-label="등급" class="c-num mono">상</td>
|
||||
<td data-label="당도" class="c-num mono">12.8<span class="unit">Bx</span></td>
|
||||
|
|
@ -142,7 +213,21 @@
|
|||
<td data-label="전일 대비" class="c-num mono delta down">▼ 2,000</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">무화과</th>
|
||||
<th scope="row">
|
||||
<button
|
||||
class="board-select"
|
||||
type="button"
|
||||
data-market-row
|
||||
data-name="무화과"
|
||||
data-origin="전남 영암"
|
||||
data-grade="특 · 당도 표기 대상 아님"
|
||||
data-pack="1.2 kg"
|
||||
data-price="24,000원"
|
||||
data-delta="▲ 1,000원"
|
||||
data-auction="02:00 경매"
|
||||
aria-pressed="false"
|
||||
>무화과</button>
|
||||
</th>
|
||||
<td data-label="산지">전남 영암</td>
|
||||
<td data-label="등급" class="c-num mono">특</td>
|
||||
<td data-label="당도" class="c-num mono blank" title="당도 표시 대상 품목이 아닙니다">—</td>
|
||||
|
|
@ -153,6 +238,37 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="lot-inspector" aria-live="polite" aria-atomic="true">
|
||||
<div class="lot-inspector-head">
|
||||
<p class="mono">선택 로트 읽기</p>
|
||||
<p class="mono" id="lotAuction">02:00 경매</p>
|
||||
</div>
|
||||
<div class="lot-inspector-body">
|
||||
<div class="lot-title">
|
||||
<p id="lotName">백도 복숭아</p>
|
||||
<p id="lotOrigin">경북 청도</p>
|
||||
</div>
|
||||
<dl class="lot-spec">
|
||||
<div>
|
||||
<dt>등급 · 당도</dt>
|
||||
<dd id="lotGrade">특 · 12.5 Bx</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>규격</dt>
|
||||
<dd id="lotPack">4.5 kg</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>낙찰가</dt>
|
||||
<dd id="lotPrice">38,000원</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>전일 대비</dt>
|
||||
<dd id="lotDelta">▲ 2,000원</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="board-notes">
|
||||
<li>
|
||||
사과와 배는 같은 날 <span class="mono">08:30</span> 경매입니다. 나머지 넷은
|
||||
|
|
@ -276,6 +392,18 @@
|
|||
판에 오릅니다.
|
||||
</p>
|
||||
|
||||
<div class="season-control">
|
||||
<div class="season-buttons" role="group" aria-label="제철 달 선택">
|
||||
<button type="button" data-season-dir="-1" aria-label="이전 달">이전 달</button>
|
||||
<button type="button" data-season-dir="1" aria-label="다음 달">다음 달</button>
|
||||
</div>
|
||||
<p class="season-status mono" aria-live="polite" aria-atomic="true">
|
||||
<output id="seasonMonth">8월</output>
|
||||
<span aria-hidden="true">·</span>
|
||||
<span id="seasonCount">제철 품목을 읽는 중입니다</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="season-scroll">
|
||||
<table class="season">
|
||||
<caption class="sr-only">품목별 제철 달력 — 가로축은 1월부터 12월</caption>
|
||||
|
|
@ -507,9 +635,47 @@
|
|||
<dd>품목당 상자 <span class="mono">3</span> 개부터</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<a class="btn" href="mailto:order@saebyeok-cheonggwa.example?subject=%EB%82%A9%ED%92%88%20%EB%AC%B8%EC%9D%98">
|
||||
납품 조건 문의
|
||||
</a>
|
||||
|
||||
<section class="order-draft" aria-labelledby="orderDraftHeading">
|
||||
<div class="order-draft-head">
|
||||
<h3 id="orderDraftHeading">발주 메모 만들기</h3>
|
||||
<p class="mono">쇼케이스 예시</p>
|
||||
</div>
|
||||
<div class="order-controls">
|
||||
<label>
|
||||
<span>품목</span>
|
||||
<select id="orderItem">
|
||||
<option value="백도 복숭아" data-price="38000" data-pack="4.5 kg">백도 복숭아 · 특 · 4.5 kg</option>
|
||||
<option value="샤인머스캣" data-price="26500" data-pack="2 kg">샤인머스캣 · 특 · 2 kg</option>
|
||||
<option value="캠벨얼리" data-price="31000" data-pack="5 kg">캠벨얼리 · 상 · 5 kg</option>
|
||||
<option value="홍로 사과" data-price="44000" data-pack="5 kg">홍로 사과 · 특 · 5 kg</option>
|
||||
<option value="원황 배" data-price="52000" data-pack="7.5 kg">원황 배 · 상 · 7.5 kg</option>
|
||||
<option value="무화과" data-price="24000" data-pack="1.2 kg">무화과 · 특 · 1.2 kg</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>상자 수</span>
|
||||
<input id="orderQty" type="number" min="3" max="50" step="1" value="3" inputmode="numeric" />
|
||||
</label>
|
||||
</div>
|
||||
<dl class="order-summary">
|
||||
<div>
|
||||
<dt>규격</dt>
|
||||
<dd class="mono" id="orderPack">4.5 kg × 3상자</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>예시 낙찰가 합계</dt>
|
||||
<dd class="mono" id="orderTotal">114,000원</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<p class="order-fine">
|
||||
위 합계는 판에 적힌 예시 낙찰가만 더합니다. 선별·포장·배송과 실제 납품가는 포함하지 않습니다.
|
||||
</p>
|
||||
<div class="order-actions">
|
||||
<button class="btn" id="copyOrder" type="button">발주 메모 복사</button>
|
||||
</div>
|
||||
<p class="order-status" id="orderStatus" role="status"></p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -559,14 +725,168 @@
|
|||
</footer>
|
||||
|
||||
<script>
|
||||
/* 이번 달 칸에 표시를 남긴다. 달이 바뀌면 저절로 따라가야 하므로 이것만 스크립트다. */
|
||||
(() => {
|
||||
const m = new Date().getMonth() + 1;
|
||||
document.querySelectorAll(`.season [data-m="${m}"]`).forEach((el) => {
|
||||
el.classList.add("is-now");
|
||||
const marketTime = new Date();
|
||||
const dateParts = Object.fromEntries(
|
||||
new Intl.DateTimeFormat("en-CA", {
|
||||
timeZone: "Asia/Seoul",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
})
|
||||
.formatToParts(marketTime)
|
||||
.filter((part) => part.type !== "literal")
|
||||
.map((part) => [part.type, part.value]),
|
||||
);
|
||||
const currentMonth = Number(dateParts.month);
|
||||
const stamp = document.querySelector(".hero-stamp time");
|
||||
if (stamp) {
|
||||
stamp.dateTime = `${dateParts.year}-${dateParts.month}-${dateParts.day}T02:00+09:00`;
|
||||
stamp.textContent = `${dateParts.month}월 ${dateParts.day}일 02:00`;
|
||||
}
|
||||
|
||||
/* 판의 한 행을 고르면 같은 값으로 로트 판독기가 바뀐다. */
|
||||
const marketRows = [...document.querySelectorAll("[data-market-row]")];
|
||||
const lotFields = {
|
||||
auction: document.getElementById("lotAuction"),
|
||||
name: document.getElementById("lotName"),
|
||||
origin: document.getElementById("lotOrigin"),
|
||||
grade: document.getElementById("lotGrade"),
|
||||
pack: document.getElementById("lotPack"),
|
||||
price: document.getElementById("lotPrice"),
|
||||
delta: document.getElementById("lotDelta"),
|
||||
};
|
||||
|
||||
function showLot(button) {
|
||||
for (const row of marketRows) row.setAttribute("aria-pressed", String(row === button));
|
||||
for (const [key, field] of Object.entries(lotFields)) {
|
||||
if (field) field.textContent = button.dataset[key] || "";
|
||||
}
|
||||
if (orderItem) orderItem.value = button.dataset.name || orderItem.value;
|
||||
updateOrder();
|
||||
}
|
||||
|
||||
for (const row of marketRows) row.addEventListener("click", () => showLot(row));
|
||||
|
||||
/* 제철 띠는 이번 달에서 시작하지만 앞뒤 달도 직접 읽을 수 있다. */
|
||||
let selectedMonth = currentMonth;
|
||||
const season = document.querySelector(".season");
|
||||
const seasonScroll = document.querySelector(".season-scroll");
|
||||
const seasonMonth = document.getElementById("seasonMonth");
|
||||
const seasonCount = document.getElementById("seasonCount");
|
||||
|
||||
function showMonth(month, moveViewport) {
|
||||
selectedMonth = month;
|
||||
season?.querySelectorAll(".is-focus").forEach((cell) => cell.classList.remove("is-focus"));
|
||||
season?.querySelectorAll("[aria-current='date']").forEach((cell) => cell.removeAttribute("aria-current"));
|
||||
season?.querySelectorAll(`[data-m="${month}"]`).forEach((cell) => cell.classList.add("is-focus"));
|
||||
|
||||
const head = season?.querySelector(`thead [data-m="${month}"]`);
|
||||
if (month === currentMonth) head?.setAttribute("aria-current", "date");
|
||||
|
||||
const items = [...(season?.querySelectorAll("tbody tr") || [])]
|
||||
.filter((row) => row.querySelector(`td.on[data-m="${month}"]`))
|
||||
.map((row) => row.querySelector("th")?.textContent.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
if (seasonMonth) seasonMonth.textContent = `${month}월`;
|
||||
if (seasonCount) seasonCount.textContent = `${items.length}품목 · ${items.join(" · ")}`;
|
||||
|
||||
if (moveViewport && head && seasonScroll) {
|
||||
const left = Math.max(0, head.offsetLeft - seasonScroll.clientWidth / 2 + head.clientWidth / 2);
|
||||
seasonScroll.scrollTo({ left, behavior: "smooth" });
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll("[data-season-dir]").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const next = ((selectedMonth - 1 + Number(button.dataset.seasonDir) + 12) % 12) + 1;
|
||||
showMonth(next, true);
|
||||
});
|
||||
});
|
||||
const head = document.querySelector(`.season thead [data-m="${m}"]`);
|
||||
if (head) head.setAttribute("aria-current", "date");
|
||||
showMonth(currentMonth, false);
|
||||
|
||||
/* 낙찰가 예시로 발주 메모를 만든다. 납품가처럼 보이지 않게 항목을 분리한다. */
|
||||
const orderItem = document.getElementById("orderItem");
|
||||
const orderQty = document.getElementById("orderQty");
|
||||
const orderPack = document.getElementById("orderPack");
|
||||
const orderTotal = document.getElementById("orderTotal");
|
||||
const orderStatus = document.getElementById("orderStatus");
|
||||
|
||||
function orderValues() {
|
||||
const option = orderItem?.selectedOptions[0];
|
||||
const quantity = Math.min(50, Math.max(3, Number(orderQty?.value) || 3));
|
||||
const price = Number(option?.dataset.price) || 0;
|
||||
return {
|
||||
name: option?.value || "",
|
||||
pack: option?.dataset.pack || "",
|
||||
quantity,
|
||||
total: price * quantity,
|
||||
};
|
||||
}
|
||||
|
||||
function updateOrder() {
|
||||
const value = orderValues();
|
||||
if (orderPack) orderPack.textContent = `${value.pack} × ${value.quantity}상자`;
|
||||
if (orderTotal) orderTotal.textContent = `${value.total.toLocaleString("ko-KR")}원`;
|
||||
if (orderStatus) orderStatus.textContent = "";
|
||||
}
|
||||
|
||||
orderItem?.addEventListener("change", updateOrder);
|
||||
orderQty?.addEventListener("input", updateOrder);
|
||||
orderQty?.addEventListener("change", () => {
|
||||
if (orderQty) orderQty.value = String(orderValues().quantity);
|
||||
updateOrder();
|
||||
});
|
||||
|
||||
async function copyText(text) {
|
||||
if (navigator.clipboard?.writeText) {
|
||||
try {
|
||||
await Promise.race([
|
||||
navigator.clipboard.writeText(text),
|
||||
new Promise((_, reject) => window.setTimeout(() => reject(new Error("clipboard timeout")), 1200)),
|
||||
]);
|
||||
return true;
|
||||
} catch {
|
||||
// 권한 대화상자가 없거나 응답하지 않는 브라우저는 아래 선택 복사로 전환합니다.
|
||||
}
|
||||
}
|
||||
|
||||
const field = document.createElement("textarea");
|
||||
field.value = text;
|
||||
field.setAttribute("readonly", "");
|
||||
field.style.position = "fixed";
|
||||
field.style.inset = "0 auto auto -9999px";
|
||||
document.body.append(field);
|
||||
field.select();
|
||||
const copied = document.execCommand("copy");
|
||||
field.remove();
|
||||
return copied;
|
||||
}
|
||||
|
||||
document.getElementById("copyOrder")?.addEventListener("click", async () => {
|
||||
const value = orderValues();
|
||||
const memo = [
|
||||
"새벽청과 납품 문의",
|
||||
`품목: ${value.name}`,
|
||||
`규격: ${value.pack}`,
|
||||
`수량: ${value.quantity}상자`,
|
||||
`예시 낙찰가 합계: ${value.total.toLocaleString("ko-KR")}원`,
|
||||
"실제 납품가는 선별·포장·배송 조건을 확인한 뒤 정합니다.",
|
||||
].join("\n");
|
||||
try {
|
||||
const copied = await copyText(memo);
|
||||
if (orderStatus) {
|
||||
orderStatus.textContent = copied
|
||||
? "발주 메모를 복사했습니다."
|
||||
: "복사하지 못했습니다. 브라우저의 클립보드 권한을 확인해 주세요.";
|
||||
}
|
||||
} catch {
|
||||
if (orderStatus) orderStatus.textContent = "복사하지 못했습니다. 브라우저의 클립보드 권한을 확인해 주세요.";
|
||||
}
|
||||
});
|
||||
|
||||
updateOrder();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
@import "./tokens.css";
|
||||
|
||||
/* ==========================================================================
|
||||
새벽청과 — 메인 스타일시트
|
||||
|
||||
|
|
@ -42,6 +40,14 @@ img {
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
|
@ -305,6 +311,25 @@ h3 {
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.board-select {
|
||||
min-height: 40px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.board tbody tr:has(.board-select[aria-pressed="true"]) {
|
||||
background: color-mix(in srgb, var(--band-accent-wash) 72%, transparent);
|
||||
box-shadow: inset 2px 0 var(--band-accent);
|
||||
}
|
||||
|
||||
.board-select[aria-pressed="true"] {
|
||||
color: var(--band-accent);
|
||||
}
|
||||
|
||||
.board td {
|
||||
color: var(--band-ink-muted);
|
||||
}
|
||||
|
|
@ -340,6 +365,67 @@ h3 {
|
|||
color: var(--band-ink-faint);
|
||||
}
|
||||
|
||||
.lot-inspector {
|
||||
margin-top: var(--space-4);
|
||||
border-block: 1px solid var(--band-line-strong);
|
||||
}
|
||||
|
||||
.lot-inspector-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
padding-block: var(--space-2);
|
||||
border-bottom: 1px solid var(--band-line);
|
||||
color: var(--band-ink-faint);
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.lot-inspector-head p:last-child {
|
||||
color: var(--band-accent);
|
||||
}
|
||||
|
||||
.lot-inspector-body {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(13rem, 1.4fr) minmax(0, 3fr);
|
||||
gap: var(--space-5);
|
||||
padding-block: var(--space-4);
|
||||
}
|
||||
|
||||
.lot-title p:first-child {
|
||||
font-size: var(--step-2);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lot-title p:last-child {
|
||||
margin-top: var(--space-1);
|
||||
color: var(--band-ink-muted);
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.lot-spec {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.lot-spec > div {
|
||||
min-width: 0;
|
||||
padding-left: var(--space-3);
|
||||
border-left: 1px solid var(--band-line);
|
||||
}
|
||||
|
||||
.lot-spec dt {
|
||||
color: var(--band-ink-faint);
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.lot-spec dd {
|
||||
margin-top: var(--space-2);
|
||||
color: var(--band-ink);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--step--1);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.board-notes {
|
||||
margin-top: var(--space-5);
|
||||
list-style: none;
|
||||
|
|
@ -497,10 +583,53 @@ h3 {
|
|||
|
||||
/* ── 제철 띠 ────────────────────────────────────────────────────── */
|
||||
.season-scroll {
|
||||
margin-top: var(--space-6);
|
||||
margin-top: var(--space-3);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.season-control {
|
||||
margin-top: var(--space-5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-4);
|
||||
padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--band-line-strong);
|
||||
}
|
||||
|
||||
.season-buttons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.season-buttons button {
|
||||
min-height: 40px;
|
||||
padding-inline: var(--space-3);
|
||||
border: 1px solid var(--band-line-strong);
|
||||
background: transparent;
|
||||
color: var(--band-ink-muted);
|
||||
cursor: pointer;
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.season-buttons button + button {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.season-buttons button:hover {
|
||||
color: var(--band-ink);
|
||||
border-color: var(--band-accent);
|
||||
}
|
||||
|
||||
.season-status {
|
||||
color: var(--band-ink-muted);
|
||||
font-size: var(--step--1);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.season-status output {
|
||||
color: var(--band-accent);
|
||||
}
|
||||
|
||||
.season {
|
||||
width: 100%;
|
||||
min-width: 34rem;
|
||||
|
|
@ -541,11 +670,11 @@ h3 {
|
|||
|
||||
/* 이번 달 — 배경 대신 테두리로 표시한다. 칸이 찬 것과 이번 달이 겹쳐도
|
||||
둘 다 읽혀야 하기 때문이다 */
|
||||
.season .is-now {
|
||||
.season .is-focus {
|
||||
border-inline: 2px solid var(--band-accent);
|
||||
}
|
||||
|
||||
.season thead .is-now {
|
||||
.season thead .is-focus {
|
||||
color: var(--band-accent);
|
||||
border-top: 2px solid var(--band-accent);
|
||||
}
|
||||
|
|
@ -677,6 +806,99 @@ h3 {
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
.order-draft {
|
||||
margin-top: var(--space-6);
|
||||
padding-block: var(--space-4);
|
||||
border-block: 1px solid var(--band-line-strong);
|
||||
}
|
||||
|
||||
.order-draft-head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.order-draft-head h3 {
|
||||
font-size: var(--step-2);
|
||||
}
|
||||
|
||||
.order-draft-head p {
|
||||
color: var(--band-ink-faint);
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.order-controls {
|
||||
margin-top: var(--space-4);
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 3fr) minmax(8rem, 1fr);
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.order-controls label {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
color: var(--band-ink-faint);
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.order-controls select,
|
||||
.order-controls input {
|
||||
min-width: 0;
|
||||
min-height: 44px;
|
||||
padding-inline: var(--space-3);
|
||||
border: 1px solid var(--band-line-strong);
|
||||
background: var(--band-surface-2);
|
||||
color: var(--band-ink);
|
||||
}
|
||||
|
||||
.order-summary {
|
||||
margin-top: var(--space-4);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
border-top: 1px solid var(--band-line);
|
||||
}
|
||||
|
||||
.order-summary > div {
|
||||
padding-block: var(--space-3);
|
||||
border-bottom: 1px solid var(--band-line);
|
||||
}
|
||||
|
||||
.order-summary > div + div {
|
||||
padding-left: var(--space-3);
|
||||
border-left: 1px solid var(--band-line);
|
||||
}
|
||||
|
||||
.order-summary dt,
|
||||
.order-fine,
|
||||
.order-status {
|
||||
color: var(--band-ink-faint);
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.order-summary dd {
|
||||
margin-top: var(--space-1);
|
||||
color: var(--band-ink);
|
||||
}
|
||||
|
||||
.order-fine {
|
||||
margin-top: var(--space-3);
|
||||
line-height: var(--leading-loose);
|
||||
}
|
||||
|
||||
.order-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.order-status {
|
||||
min-height: 1.7em;
|
||||
margin-top: var(--space-3);
|
||||
color: var(--band-ink-muted);
|
||||
}
|
||||
|
||||
.contact-fine {
|
||||
margin-top: var(--space-4);
|
||||
font-size: var(--step--1);
|
||||
|
|
@ -687,14 +909,16 @@ h3 {
|
|||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
min-height: 44px;
|
||||
margin-top: var(--space-5);
|
||||
padding: var(--space-3) var(--space-5);
|
||||
border: 0;
|
||||
background: var(--band-accent);
|
||||
color: var(--band-accent-ink);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
transition: background-color var(--dur-quick) var(--ease);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
|
|
@ -772,6 +996,10 @@ h3 {
|
|||
border: 0;
|
||||
}
|
||||
|
||||
.board-select {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.board td {
|
||||
padding: var(--space-1) 0;
|
||||
border: 0;
|
||||
|
|
@ -787,6 +1015,37 @@ h3 {
|
|||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
.lot-inspector-body,
|
||||
.lot-spec,
|
||||
.order-controls,
|
||||
.order-summary {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.lot-spec > div {
|
||||
padding: var(--space-2) 0;
|
||||
border-left: 0;
|
||||
border-top: 1px solid var(--band-line);
|
||||
}
|
||||
|
||||
.season-control {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.season-buttons button {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.season-status {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.order-summary > div + div {
|
||||
padding-left: 0;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.today-card-wide {
|
||||
display: block;
|
||||
}
|
||||
|
|
@ -812,6 +1071,10 @@ h3 {
|
|||
.site-head,
|
||||
.skip-link,
|
||||
.btn,
|
||||
.season-control,
|
||||
.order-controls,
|
||||
.order-actions,
|
||||
.order-status,
|
||||
.hero-bg {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue