Fixed the edge tabs overhang
This commit is contained in:
@@ -157,6 +157,10 @@
|
||||
<label>Tab Length (mm)</label>
|
||||
<input type="number" id="tabLength" value="10.0" min="1" step="0.5">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Edge Cut Width (mm)</label>
|
||||
<input type="number" id="tabWidth" value="4.0" min="1" step="0.5">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" id="tabOverlapSideGroup" style="display:none;">
|
||||
<label>Tab Overlap Side</label>
|
||||
@@ -200,6 +204,11 @@
|
||||
<button class="btn-secondary" id="addTopBusbarBtn">+ Top Busbar</button>
|
||||
<button class="btn-secondary" id="addBottomBusbarBtn">+ Bottom Busbar</button>
|
||||
</div>
|
||||
<div class="panel-divider" aria-hidden="true"></div>
|
||||
<div class="order-header">Copper Sheet Calculator</div>
|
||||
<div id="orderContent">
|
||||
<p class="order-placeholder">Generate a preview first to see sheet requirements.</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
8
node_modules/.vite/deps/_metadata.json
generated
vendored
8
node_modules/.vite/deps/_metadata.json
generated
vendored
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"hash": "c96d213e",
|
||||
"configHash": "c79dcb49",
|
||||
"hash": "8727c2cc",
|
||||
"configHash": "cddbe005",
|
||||
"lockfileHash": "6e69140d",
|
||||
"browserHash": "2de19426",
|
||||
"browserHash": "80e0ce3b",
|
||||
"optimized": {
|
||||
"jszip": {
|
||||
"src": "../../jszip/dist/jszip.min.js",
|
||||
"file": "jszip.js",
|
||||
"fileHash": "d21eabfe",
|
||||
"fileHash": "d6368be4",
|
||||
"needsInterop": true
|
||||
}
|
||||
},
|
||||
|
||||
3
node_modules/.vite/deps_temp_6e38d81b/package.json
generated
vendored
3
node_modules/.vite/deps_temp_6e38d81b/package.json
generated
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
78
src/app.js
78
src/app.js
@@ -23,6 +23,35 @@ export function getLastBusbarGeometries() {
|
||||
return lastComputedGeometries;
|
||||
}
|
||||
|
||||
let _orderUpdateCallback = null;
|
||||
export function setOrderUpdateCallback(fn) {
|
||||
_orderUpdateCallback = fn;
|
||||
}
|
||||
|
||||
let lastPreviewState = null;
|
||||
|
||||
export function refreshOrderFromLastState() {
|
||||
if (!_orderUpdateCallback || !lastPreviewState) return;
|
||||
const { positions, cellSize, spacing, seriesCount } = lastPreviewState;
|
||||
const cellRadius = cellSize / 2;
|
||||
const busbarsNeeded = seriesCount + 1;
|
||||
|
||||
const busbarSheets = busbarStore.list.map(bb => {
|
||||
if (!bb.cellIndices || bb.cellIndices.length === 0) {
|
||||
return { name: bb.name, w: 0, h: 0, empty: true };
|
||||
}
|
||||
const pts = bb.cellIndices.map(i => positions[i]).filter(Boolean);
|
||||
if (pts.length === 0) return { name: bb.name, w: 0, h: 0, empty: true };
|
||||
const minX = Math.min(...pts.map(p => p[0])) - cellRadius - spacing;
|
||||
const maxX = Math.max(...pts.map(p => p[0])) + cellRadius + spacing;
|
||||
const minY = Math.min(...pts.map(p => p[1])) - cellRadius - spacing;
|
||||
const maxY = Math.max(...pts.map(p => p[1])) + cellRadius + spacing;
|
||||
return { name: bb.name, w: maxX - minX, h: maxY - minY, empty: false };
|
||||
});
|
||||
|
||||
_orderUpdateCallback({ busbarSheets, busbarsNeeded });
|
||||
}
|
||||
|
||||
function getEdgeTabCenters(positions, cellRadius, spacing, layoutType) {
|
||||
if (!Array.isArray(positions) || positions.length < 2) {
|
||||
return { top: [], bottom: [] };
|
||||
@@ -218,12 +247,7 @@ function attachEdgeTabsToNearestBusbars(busbars, geometries, positions, options)
|
||||
const anchorPoint = best.anchor.slice();
|
||||
const edgePoint = [best.tab.x, best.tab.y];
|
||||
const innerPoint = [best.tab.x, best.tab.y + inwardDirection * overlapLength];
|
||||
|
||||
const busbar = busbars[best.busbarIndex];
|
||||
const overlapOutward = Number(busbar.overlapSize) > 0 ? Number(busbar.overlapSize) : 0;
|
||||
const outerPoint = overlapOutward > 0
|
||||
? [best.tab.x, best.tab.y - inwardDirection * overlapOutward]
|
||||
: null;
|
||||
const outerPoint = [best.tab.x, best.tab.y - inwardDirection * overlapLength];
|
||||
|
||||
best.geometry.extraPads = Array.isArray(best.geometry.extraPads)
|
||||
? best.geometry.extraPads
|
||||
@@ -243,13 +267,11 @@ function attachEdgeTabsToNearestBusbars(busbars, geometries, positions, options)
|
||||
pos: innerPoint,
|
||||
radius: connectorRadius,
|
||||
});
|
||||
if (outerPoint) {
|
||||
best.geometry.extraPads.push({
|
||||
key: `bms_tab_outer_${tabOverlapSide}_${best.tabKey}`,
|
||||
pos: outerPoint,
|
||||
radius: connectorRadius,
|
||||
});
|
||||
}
|
||||
best.geometry.extraPads.push({
|
||||
key: `bms_tab_outer_${tabOverlapSide}_${best.tabKey}`,
|
||||
pos: outerPoint,
|
||||
radius: connectorRadius,
|
||||
});
|
||||
best.geometry.extraSegments = Array.isArray(best.geometry.extraSegments)
|
||||
? best.geometry.extraSegments
|
||||
: [];
|
||||
@@ -267,15 +289,13 @@ function attachEdgeTabsToNearestBusbars(busbars, geometries, positions, options)
|
||||
toKey: `bms_tab_inner_${tabOverlapSide}_${best.tabKey}`,
|
||||
radius: connectorRadius,
|
||||
});
|
||||
if (outerPoint) {
|
||||
best.geometry.extraSegments.push({
|
||||
from: edgePoint,
|
||||
to: outerPoint,
|
||||
fromKey: `bms_tab_edge_${tabOverlapSide}_${best.tabKey}`,
|
||||
toKey: `bms_tab_outer_${tabOverlapSide}_${best.tabKey}`,
|
||||
radius: connectorRadius,
|
||||
});
|
||||
}
|
||||
best.geometry.extraSegments.push({
|
||||
from: edgePoint,
|
||||
to: outerPoint,
|
||||
fromKey: `bms_tab_edge_${tabOverlapSide}_${best.tabKey}`,
|
||||
toKey: `bms_tab_outer_${tabOverlapSide}_${best.tabKey}`,
|
||||
radius: connectorRadius,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +504,7 @@ export function updatePreview(resetView = false) {
|
||||
enabled: document.getElementById('bmsHolesType')?.value === 'tabs',
|
||||
cellRadius,
|
||||
spacing,
|
||||
tabWidth: parseFloat(document.getElementById('height')?.value) || 10.0,
|
||||
tabWidth: (parseFloat(document.getElementById('tabWidth')?.value) || 4.0) - 1,
|
||||
tabOverlapSide: document.getElementById('tabOverlapSide')?.value || 'off',
|
||||
overlapLength: parseFloat(document.getElementById('height')?.value) || 10.0,
|
||||
layoutType,
|
||||
@@ -508,7 +528,11 @@ export function updatePreview(resetView = false) {
|
||||
const actualHeight = maxY - minY + cellSize + spacing * 2;
|
||||
|
||||
if (positions.length >= 2) {
|
||||
stats.textContent = `${positions.length} cells • ${actualWidth.toFixed(0)}×${actualHeight.toFixed(0)} mm`;
|
||||
const areaCm2 = (actualWidth * actualHeight / 100).toFixed(0);
|
||||
stats.textContent = `${positions.length} cells • ${actualWidth.toFixed(0)}×${actualHeight.toFixed(0)} mm • ${areaCm2} cm²`;
|
||||
const s = Math.max(1, Math.round(parseFloat(document.getElementById('series')?.value) || 1));
|
||||
lastPreviewState = { positions, cellSize, spacing, seriesCount: s };
|
||||
refreshOrderFromLastState();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Preview error:', error);
|
||||
@@ -732,7 +756,7 @@ export async function generateLayout() {
|
||||
}
|
||||
}
|
||||
|
||||
const tabWidth = parseFloat(document.getElementById('height').value) || 10.0;
|
||||
const edgeCutWidth = parseFloat(document.getElementById('tabWidth')?.value) || 4.0;
|
||||
const tabLength = parseFloat(document.getElementById('tabLength')?.value) || 10.0;
|
||||
const tabOverlapSide = document.getElementById('tabOverlapSide')?.value || 'off';
|
||||
|
||||
@@ -740,7 +764,7 @@ export async function generateLayout() {
|
||||
cellSize, spacing, height, terminalDiameter, terminalDepth,
|
||||
coverThickness, roundedCorners, bmsHoles, ledgeWidth,
|
||||
filletBms, circleHoleOffset, useTabs, useFullCircles, bmsHoleDiameter,
|
||||
tabWidth, tabLength, tabDepth: 1.0, tabOverlapSide, layoutType,
|
||||
tabWidth: edgeCutWidth, tabLength, tabDepth: 1.0, tabOverlapSide, layoutType,
|
||||
};
|
||||
|
||||
const holderShape = create3DModel(positions, config);
|
||||
@@ -778,7 +802,7 @@ export async function generateLayout() {
|
||||
enabled: bmsHolesType === 'tabs',
|
||||
cellRadius,
|
||||
spacing,
|
||||
tabWidth,
|
||||
tabWidth: edgeCutWidth - 1,
|
||||
tabOverlapSide,
|
||||
overlapLength: height,
|
||||
layoutType,
|
||||
|
||||
@@ -309,6 +309,21 @@ function computeEdgeOverlapFeatures(cellIndices, positions, layoutType, overlapL
|
||||
return { extraPads: [], extraSegments: [] };
|
||||
}
|
||||
|
||||
// Skip overlap if it would create a hard 90° bend at every boundary cell.
|
||||
// This happens when no boundary cell has any busbar neighbour in the same row
|
||||
// (i.e. the busbar only spans one column and all its internal connections are
|
||||
// vertical). In that case the horizontal arm is perpendicular to every
|
||||
// connection and would overlap adjacent busbars.
|
||||
const hasSameRowBusbarNeighbour = boundaryEntries.some((entry) =>
|
||||
selected.some((other) =>
|
||||
other.index !== entry.index &&
|
||||
Math.abs(other.pos[1] - entry.pos[1]) <= yTolerance
|
||||
)
|
||||
);
|
||||
if (!hasSameRowBusbarNeighbour) {
|
||||
return { extraPads: [], extraSegments: [] };
|
||||
}
|
||||
|
||||
const extension = Number.isFinite(Number(overlapLength)) && Number(overlapLength) > 0
|
||||
? Number(overlapLength)
|
||||
: 10;
|
||||
|
||||
@@ -178,6 +178,54 @@ export function drawBusbarsOverlay(busbars, geometries, positions, cellSize, pad
|
||||
}
|
||||
}
|
||||
|
||||
// 3b. Fill four-cell square interstice (grid layout).
|
||||
// For every diagonal pair (in triAdj but not adjCaps) that has exactly
|
||||
// 2 common direct (capsule) neighbours, those 4 cells form a 2×2 square.
|
||||
// Fill a circle at the centroid whose radius spans the gap.
|
||||
{
|
||||
const directSet = new Set(adjCaps.map(([a, b]) => `${Math.min(a,b)}_${Math.max(a,b)}`));
|
||||
const visited4 = new Set();
|
||||
off.fillStyle = opaqueColor;
|
||||
|
||||
for (let a = 0; a < nCells; a++) {
|
||||
for (const b of triAdj[a]) {
|
||||
if (b <= a) continue;
|
||||
if (directSet.has(`${Math.min(a,b)}_${Math.max(a,b)}`)) continue; // skip direct neighbours
|
||||
|
||||
// a–b is diagonal; find their common direct neighbours
|
||||
const common = [];
|
||||
for (const c of triAdj[a]) {
|
||||
if (c === b) continue;
|
||||
if (!directSet.has(`${Math.min(a,c)}_${Math.max(a,c)}`)) continue;
|
||||
if (!triAdj[b].has(c)) continue;
|
||||
if (!directSet.has(`${Math.min(b,c)}_${Math.max(b,c)}`)) continue;
|
||||
common.push(c);
|
||||
}
|
||||
if (common.length < 2) continue;
|
||||
|
||||
const [c, d] = common.sort((x, y) => x - y);
|
||||
const quadKey = [a, b, c, d].sort((x, y) => x - y).join('_');
|
||||
if (visited4.has(quadKey)) continue;
|
||||
visited4.add(quadKey);
|
||||
|
||||
const pa = positions[cellIndices[a]];
|
||||
const pb = positions[cellIndices[b]];
|
||||
const pc = positions[cellIndices[c]];
|
||||
const pd = positions[cellIndices[d]];
|
||||
if (!pa || !pb || !pc || !pd) continue;
|
||||
|
||||
const qcx = (pa[0] + pb[0] + pc[0] + pd[0]) / 4;
|
||||
const qcy = (pa[1] + pb[1] + pc[1] + pd[1]) / 4;
|
||||
const distToCell = Math.hypot(pa[0] - qcx, pa[1] - qcy);
|
||||
const fillR = Math.max(0.5, distToCell - padRadius);
|
||||
|
||||
off.beginPath();
|
||||
off.arc(toScreenX(qcx), toScreenY(qcy), fillR * t.scale, 0, Math.PI * 2);
|
||||
off.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Obstacle-avoidance detour waypoints (spanning-tree edges with bends).
|
||||
off.strokeStyle = opaqueColor;
|
||||
off.lineWidth = 2 * padRadiusScreen();
|
||||
|
||||
@@ -2,10 +2,11 @@ import { canvasState } from './state.js';
|
||||
import { initOC } from './oc.js';
|
||||
import { toggleBmsDiameter, initCustomSelects, showStatus } from './ui.js';
|
||||
import { drawPreview } from './preview.js';
|
||||
import { updatePreview, generateLayout, redrawBusbarOverlay, downloadSingleBusbar, downloadAllBusbarsZip } from './app.js';
|
||||
import { updatePreview, generateLayout, redrawBusbarOverlay, downloadSingleBusbar, downloadAllBusbarsZip, setOrderUpdateCallback, refreshOrderFromLastState } from './app.js';
|
||||
import { busbarStore } from './busbars.js';
|
||||
import { initBusbarUI, renderBusbarList } from './busbar-ui.js';
|
||||
import { captureConfig, encodeConfigToHash, decodeHashToConfig } from './url-config.js';
|
||||
import { renderOrderSection } from './order.js';
|
||||
|
||||
const CLICK_PIXEL_THRESHOLD = 4;
|
||||
const URL_SYNC_DEBOUNCE_MS = 250;
|
||||
@@ -627,6 +628,8 @@ function wireSidebarTabs() {
|
||||
async function initializeApp() {
|
||||
scaleCanvasForDPI();
|
||||
initCustomSelects();
|
||||
setOrderUpdateCallback(renderOrderSection);
|
||||
busbarStore.subscribe(() => refreshOrderFromLastState());
|
||||
wireSidebarTabs();
|
||||
wirePackMode();
|
||||
|
||||
|
||||
124
src/order.js
Normal file
124
src/order.js
Normal file
@@ -0,0 +1,124 @@
|
||||
const WIDTHS = [50, 100, 150, 200, 300];
|
||||
const LENGTHS = [300, 1000, 1500];
|
||||
|
||||
function calcCuts(sheetW, sheetL, busbarW, busbarH) {
|
||||
if (busbarW <= 0 || busbarH <= 0) return 0;
|
||||
const orientA = Math.floor(sheetW / busbarW) * Math.floor(sheetL / busbarH);
|
||||
const orientB = Math.floor(sheetW / busbarH) * Math.floor(sheetL / busbarW);
|
||||
return Math.max(orientA, orientB);
|
||||
}
|
||||
|
||||
export function renderOrderSection({ busbarSheets, busbarsNeeded }) {
|
||||
const container = document.getElementById('orderContent');
|
||||
if (!container) return;
|
||||
|
||||
const defined = busbarSheets.length;
|
||||
const nonEmpty = busbarSheets.filter(b => !b.empty);
|
||||
const emptyOnes = busbarSheets.filter(b => b.empty);
|
||||
const missing = busbarsNeeded - defined; // negative = too many defined, 0 = exact, positive = missing
|
||||
|
||||
// ── Warnings ─────────────────────────────────────────────────────────────
|
||||
let warnings = '';
|
||||
if (defined === 0) {
|
||||
warnings += `<div class="order-warning">No busbars defined. Add busbars in the list above and assign cells by clicking them in the preview.</div>`;
|
||||
} else {
|
||||
if (missing > 0) {
|
||||
warnings += `<div class="order-warning">${missing} busbar${missing > 1 ? 's' : ''} missing — need ${busbarsNeeded}, have ${defined}.</div>`;
|
||||
}
|
||||
if (emptyOnes.length > 0) {
|
||||
const names = emptyOnes.map(b => `<strong>${escHtml(b.name)}</strong>`).join(', ');
|
||||
warnings += `<div class="order-warning">${names} ${emptyOnes.length === 1 ? 'has' : 'have'} no cells assigned — click cells in the preview to assign them.</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Nothing to calculate ─────────────────────────────────────────────────
|
||||
if (nonEmpty.length === 0) {
|
||||
container.innerHTML = warnings +
|
||||
`<p class="order-placeholder">Assign cells to busbars to calculate sheet requirements.</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Max sheet dimensions (largest busbar drives the order size) ───────────
|
||||
const maxW = Math.max(...nonEmpty.map(b => b.w));
|
||||
const maxH = Math.max(...nonEmpty.map(b => b.h));
|
||||
const totalSheets = busbarsNeeded; // what we actually need to order
|
||||
const totalAreaCm2 = (maxW * maxH * totalSheets / 100).toFixed(1);
|
||||
const singleAreaCm2 = (maxW * maxH / 100).toFixed(1);
|
||||
|
||||
// ── Per-busbar size breakdown (only when sizes differ) ────────────────────
|
||||
const sizesVary = nonEmpty.some(b => Math.abs(b.w - maxW) > 0.5 || Math.abs(b.h - maxH) > 0.5);
|
||||
let perBusbarHtml = '';
|
||||
if (sizesVary) {
|
||||
perBusbarHtml = `<div class="order-busbar-sizes">`;
|
||||
for (const b of nonEmpty) {
|
||||
perBusbarHtml += `<div class="order-busbar-size-row">
|
||||
<span class="order-label">${escHtml(b.name)}</span>
|
||||
<span class="order-value">${b.w.toFixed(0)} × ${b.h.toFixed(0)} mm <span class="order-muted">(${(b.w * b.h / 100).toFixed(1)} cm²)</span></span>
|
||||
</div>`;
|
||||
}
|
||||
perBusbarHtml += `</div>`;
|
||||
}
|
||||
|
||||
// ── Sheet table ───────────────────────────────────────────────────────────
|
||||
const rows = [];
|
||||
for (const w of WIDTHS) {
|
||||
for (const l of LENGTHS) {
|
||||
const cuts = calcCuts(w, l, maxW, maxH);
|
||||
const sheets = cuts > 0 ? Math.ceil(totalSheets / cuts) : null;
|
||||
rows.push({ w, l, cuts, sheets });
|
||||
}
|
||||
}
|
||||
const fittingSheets = rows.filter(r => r.sheets !== null).map(r => r.sheets);
|
||||
const bestSheets = fittingSheets.length > 0 ? Math.min(...fittingSheets) : null;
|
||||
|
||||
let tableHtml = `
|
||||
<table class="order-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Sheet (W×L mm)</th>
|
||||
<th>Cuts / sheet</th>
|
||||
<th>Sheets to buy</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
for (const { w, l, cuts, sheets } of rows) {
|
||||
const noFit = cuts === 0;
|
||||
const isBest = !noFit && sheets === bestSheets;
|
||||
const cls = noFit ? 'order-row-nofit' : (isBest ? 'order-row-best' : '');
|
||||
tableHtml += `
|
||||
<tr class="${cls}">
|
||||
<td>${w} × ${l}</td>
|
||||
<td>${noFit ? '—' : cuts}</td>
|
||||
<td>${noFit ? '—' : sheets}</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
tableHtml += `</tbody></table>`;
|
||||
|
||||
// ── Summary ───────────────────────────────────────────────────────────────
|
||||
const summaryHtml = `
|
||||
<div class="order-summary">
|
||||
<div class="order-summary-row">
|
||||
<span class="order-label">Largest busbar</span>
|
||||
<span class="order-value">${maxW.toFixed(0)} × ${maxH.toFixed(0)} mm</span>
|
||||
</div>
|
||||
<div class="order-summary-row">
|
||||
<span class="order-label">Sheets needed</span>
|
||||
<span class="order-value">${totalSheets}</span>
|
||||
</div>
|
||||
<div class="order-summary-row">
|
||||
<span class="order-label">Total copper area</span>
|
||||
<span class="order-value">${totalAreaCm2} cm² <span class="order-muted">(${singleAreaCm2} cm² each)</span></span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.innerHTML = warnings + summaryHtml + perBusbarHtml + tableHtml;
|
||||
}
|
||||
|
||||
function escHtml(s) {
|
||||
return String(s).replace(/[&<>"']/g, ch => (
|
||||
{ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ch]
|
||||
));
|
||||
}
|
||||
@@ -384,7 +384,7 @@ export function drawPreview(positions, cellSize) {
|
||||
ctx.strokeStyle = 'rgba(255, 193, 7, 0.9)';
|
||||
ctx.lineWidth = 1.5 / zoom;
|
||||
|
||||
const tabWidthMm = parseFloat(document.getElementById('height').value) || 10.0;
|
||||
const tabWidthMm = parseFloat(document.getElementById('tabWidth')?.value) || 4.0;
|
||||
const tabWidth = tabWidthMm * scale;
|
||||
const tabHeight = 1.0 * scale;
|
||||
|
||||
|
||||
137
styles/main.css
137
styles/main.css
@@ -1198,6 +1198,143 @@ input[type="checkbox"]:focus {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Copper Sheet Order Calculator ─────────────────────────────────────── */
|
||||
|
||||
.order-header {
|
||||
font-size: 0.72em;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: #64748b;
|
||||
padding: 4px 0 10px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.order-placeholder {
|
||||
font-size: 0.85em;
|
||||
color: #475569;
|
||||
padding: 4px 0 8px;
|
||||
}
|
||||
|
||||
.order-summary {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(100, 149, 237, 0.1);
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.order-summary-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.order-label {
|
||||
color: #64748b;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.order-value {
|
||||
color: #e2e8f0;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.order-muted {
|
||||
color: #64748b;
|
||||
font-weight: 400;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.order-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.83em;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.order-table th {
|
||||
text-align: left;
|
||||
padding: 5px 8px;
|
||||
color: #64748b;
|
||||
font-weight: 600;
|
||||
font-size: 0.9em;
|
||||
border-bottom: 1px solid rgba(100, 149, 237, 0.15);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.order-table td {
|
||||
padding: 5px 8px;
|
||||
color: #cbd5e1;
|
||||
border-bottom: 1px solid rgba(100, 149, 237, 0.06);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.order-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.order-row-nofit td {
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.order-row-best td {
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.order-row-best td:last-child {
|
||||
color: #6ee7b7;
|
||||
}
|
||||
|
||||
.order-table tr:not(.order-row-nofit):not(.order-row-best):hover td {
|
||||
background: rgba(100, 149, 237, 0.07);
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.order-warning {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 7px;
|
||||
font-size: 0.82em;
|
||||
color: #fbbf24;
|
||||
background: rgba(251, 191, 36, 0.08);
|
||||
border: 1px solid rgba(251, 191, 36, 0.25);
|
||||
border-radius: 6px;
|
||||
padding: 7px 10px;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.order-warning strong {
|
||||
color: #fde68a;
|
||||
}
|
||||
|
||||
.order-busbar-sizes {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid rgba(100, 149, 237, 0.08);
|
||||
border-radius: 6px;
|
||||
padding: 7px 12px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.order-busbar-size-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
font-size: 0.82em;
|
||||
}
|
||||
|
||||
/* Axis diagram — resolves width/depth confusion by showing which input maps to which
|
||||
on-screen dimension. Mirrors the canvas orientation: X horizontal, Y vertical, Z depth. */
|
||||
.axis-diagram {
|
||||
|
||||
Reference in New Issue
Block a user