fixed overlap
This commit is contained in:
@@ -176,6 +176,27 @@ function inferHorizontalPitch(positions) {
|
|||||||
return Number.isFinite(pitch) ? pitch : 0;
|
return Number.isFinite(pitch) ? pitch : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function inferVerticalPitch(positions) {
|
||||||
|
const epsilon = 1e-3;
|
||||||
|
const cols = new Map();
|
||||||
|
for (const pos of positions) {
|
||||||
|
const key = pos[0].toFixed(4);
|
||||||
|
if (!cols.has(key)) cols.set(key, []);
|
||||||
|
cols.get(key).push(pos[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let pitch = Infinity;
|
||||||
|
for (const colYs of cols.values()) {
|
||||||
|
colYs.sort((a, b) => a - b);
|
||||||
|
for (let index = 1; index < colYs.length; index++) {
|
||||||
|
const delta = colYs[index] - colYs[index - 1];
|
||||||
|
if (delta > epsilon) pitch = Math.min(pitch, delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number.isFinite(pitch) ? pitch : 0;
|
||||||
|
}
|
||||||
|
|
||||||
function computeBoundaryRoundoverFeatures(cellIndices, positions, padRadius) {
|
function computeBoundaryRoundoverFeatures(cellIndices, positions, padRadius) {
|
||||||
if (cellIndices.length < 2) {
|
if (cellIndices.length < 2) {
|
||||||
return { extraPads: [], extraSegments: [] };
|
return { extraPads: [], extraSegments: [] };
|
||||||
@@ -249,7 +270,11 @@ function computeBoundaryRoundoverFeatures(cellIndices, positions, padRadius) {
|
|||||||
return { extraPads, extraSegments };
|
return { extraPads, extraSegments };
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeEdgeOverlapFeatures(cellIndices, positions, layoutType, overlapLength) {
|
function computeEdgeOverlapFeatures(cellIndices, positions, layoutType, overlapLength, cellRadius, spacing) {
|
||||||
|
if (layoutType === 'vertical') {
|
||||||
|
return computeEdgeOverlapFeaturesVertical(cellIndices, positions, overlapLength, cellRadius, spacing);
|
||||||
|
}
|
||||||
|
|
||||||
if (cellIndices.length < 2) {
|
if (cellIndices.length < 2) {
|
||||||
return { extraPads: [], extraSegments: [] };
|
return { extraPads: [], extraSegments: [] };
|
||||||
}
|
}
|
||||||
@@ -374,6 +399,204 @@ function computeEdgeOverlapFeatures(cellIndices, positions, layoutType, overlapL
|
|||||||
return { extraPads, extraSegments };
|
return { extraPads, extraSegments };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function computeBoundaryRoundoverFeaturesVertical(cellIndices, positions, padRadius) {
|
||||||
|
if (cellIndices.length < 2) {
|
||||||
|
return { extraPads: [], extraSegments: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const selected = cellIndices
|
||||||
|
.map((index) => ({ index, pos: positions[index] }))
|
||||||
|
.filter((entry) => Array.isArray(entry.pos) && entry.pos.length >= 2);
|
||||||
|
if (selected.length !== cellIndices.length) {
|
||||||
|
return { extraPads: [], extraSegments: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const epsilon = 1e-3;
|
||||||
|
const verticalPitch = inferVerticalPitch(positions);
|
||||||
|
if (verticalPitch <= epsilon) {
|
||||||
|
return { extraPads: [], extraSegments: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const yTolerance = Math.max(0.5, verticalPitch * 0.25);
|
||||||
|
const xTolerance = 1e-3;
|
||||||
|
const hasVerticalNeighbor = (entry, direction) => positions.some((pos) => {
|
||||||
|
if (Math.abs(pos[0] - entry.pos[0]) > xTolerance) return false;
|
||||||
|
const deltaY = pos[1] - entry.pos[1];
|
||||||
|
if (direction === 'top' && deltaY >= -epsilon) return false;
|
||||||
|
if (direction === 'bottom' && deltaY <= epsilon) return false;
|
||||||
|
return Math.abs(Math.abs(deltaY) - verticalPitch) <= yTolerance;
|
||||||
|
});
|
||||||
|
|
||||||
|
const cols = new Map();
|
||||||
|
for (const entry of selected) {
|
||||||
|
const key = entry.pos[0].toFixed(4);
|
||||||
|
if (!cols.has(key)) cols.set(key, []);
|
||||||
|
cols.get(key).push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
const topBoundary = [];
|
||||||
|
const bottomBoundary = [];
|
||||||
|
for (const colEntries of cols.values()) {
|
||||||
|
colEntries.sort((a, b) => a.pos[1] - b.pos[1]);
|
||||||
|
topBoundary.push(colEntries[0]);
|
||||||
|
bottomBoundary.push(colEntries[colEntries.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const topExposed = topBoundary.filter((entry) => !hasVerticalNeighbor(entry, 'top'));
|
||||||
|
const bottomExposed = bottomBoundary.filter((entry) => !hasVerticalNeighbor(entry, 'bottom'));
|
||||||
|
|
||||||
|
const extraPads = [];
|
||||||
|
const extraSegments = [];
|
||||||
|
const fillRadius = Math.max(0.2, padRadius * 0.55);
|
||||||
|
const outwardShift = Math.max(0.2, padRadius * 0.22);
|
||||||
|
|
||||||
|
const addSideRoundovers = (entries, side) => {
|
||||||
|
const direction = side === 'top' ? -1 : 1;
|
||||||
|
const ordered = entries.slice().sort((a, b) => a.pos[0] - b.pos[0]);
|
||||||
|
for (let index = 0; index < ordered.length - 1; index++) {
|
||||||
|
const left = ordered[index];
|
||||||
|
const right = ordered[index + 1];
|
||||||
|
const fillKey = `boundary_round_${side}_${index}`;
|
||||||
|
const fillPos = [
|
||||||
|
(left.pos[0] + right.pos[0]) / 2,
|
||||||
|
(left.pos[1] + right.pos[1]) / 2 + direction * outwardShift,
|
||||||
|
];
|
||||||
|
extraPads.push({ key: fillKey, pos: fillPos, radius: fillRadius });
|
||||||
|
extraSegments.push({ from: fillPos, to: left.pos, fromKey: fillKey, toKey: `c${left.index}`, radius: fillRadius });
|
||||||
|
extraSegments.push({ from: fillPos, to: right.pos, fromKey: fillKey, toKey: `c${right.index}`, radius: fillRadius });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addSideRoundovers(topExposed, 'top');
|
||||||
|
addSideRoundovers(bottomExposed, 'bottom');
|
||||||
|
|
||||||
|
return { extraPads, extraSegments };
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeEdgeOverlapFeaturesVertical(cellIndices, positions, overlapLength, cellRadius, spacing) {
|
||||||
|
if (cellIndices.length < 2) {
|
||||||
|
return { extraPads: [], extraSegments: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const selected = cellIndices
|
||||||
|
.map((index) => ({ index, pos: positions[index] }))
|
||||||
|
.filter((entry) => Array.isArray(entry.pos) && entry.pos.length >= 2);
|
||||||
|
if (selected.length !== cellIndices.length) {
|
||||||
|
return { extraPads: [], extraSegments: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measure the X pitch between adjacent columns in the full layout.
|
||||||
|
const uniqueColXs = [...new Set(positions.map(p => Math.round(p[0] * 10)))]
|
||||||
|
.sort((a, b) => a - b);
|
||||||
|
let colPitch = Infinity;
|
||||||
|
for (let i = 1; i < uniqueColXs.length; i++) {
|
||||||
|
const d = (uniqueColXs[i] - uniqueColXs[i - 1]) / 10;
|
||||||
|
if (d > 1e-3) colPitch = Math.min(colPitch, d);
|
||||||
|
}
|
||||||
|
if (!Number.isFinite(colPitch) || colPitch <= 1e-3) {
|
||||||
|
return { extraPads: [], extraSegments: [] };
|
||||||
|
}
|
||||||
|
const colTolerance = colPitch * 0.3;
|
||||||
|
|
||||||
|
// Group selected cells by column (keyed by rounded X*10).
|
||||||
|
const colMap = new Map();
|
||||||
|
for (const entry of selected) {
|
||||||
|
const key = Math.round(entry.pos[0] * 10);
|
||||||
|
if (!colMap.has(key)) colMap.set(key, []);
|
||||||
|
colMap.get(key).push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedColKeys = [...colMap.keys()].sort((a, b) => a - b);
|
||||||
|
const leftColX = sortedColKeys[0] / 10;
|
||||||
|
const rightColX = sortedColKeys[sortedColKeys.length - 1] / 10;
|
||||||
|
|
||||||
|
// Count how many boundary-column cells have no full-layout column neighbor.
|
||||||
|
const leftColCells = colMap.get(sortedColKeys[0]);
|
||||||
|
const rightColCells = colMap.get(sortedColKeys[sortedColKeys.length - 1]);
|
||||||
|
|
||||||
|
const countExposed = (colCells, side) => colCells.filter((entry) => {
|
||||||
|
const neighborX = side === 'left' ? leftColX - colPitch : rightColX + colPitch;
|
||||||
|
return !positions.some((p) => Math.abs(p[0] - neighborX) < colTolerance);
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
const leftExposedCount = countExposed(leftColCells, 'left');
|
||||||
|
const rightExposedCount = countExposed(rightColCells, 'right');
|
||||||
|
|
||||||
|
// Skip if the busbar is only a single column — a single-column spine that
|
||||||
|
// runs straight through all pads adds no useful material above what the
|
||||||
|
// pad-and-edge geometry already provides.
|
||||||
|
// (We still proceed when there are multiple columns.)
|
||||||
|
let chosenSide, boundaryCells, boundaryColX;
|
||||||
|
if (leftExposedCount > rightExposedCount) {
|
||||||
|
chosenSide = 'left';
|
||||||
|
boundaryCells = leftColCells;
|
||||||
|
boundaryColX = leftColX;
|
||||||
|
} else if (rightExposedCount > leftExposedCount) {
|
||||||
|
chosenSide = 'right';
|
||||||
|
boundaryCells = rightColCells;
|
||||||
|
boundaryColX = rightColX;
|
||||||
|
} else if (leftExposedCount > 0) {
|
||||||
|
// Tie: prefer left.
|
||||||
|
chosenSide = 'left';
|
||||||
|
boundaryCells = leftColCells;
|
||||||
|
boundaryColX = leftColX;
|
||||||
|
} else {
|
||||||
|
return { extraPads: [], extraSegments: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const extension = Number.isFinite(Number(overlapLength)) && Number(overlapLength) > 0
|
||||||
|
? Number(overlapLength)
|
||||||
|
: 10;
|
||||||
|
|
||||||
|
// Place the spine relative to the pack boundary (not the cell centre) so the
|
||||||
|
// overlap tab is clearly visible outside the pack rectangle.
|
||||||
|
const packLeft = Math.min(...positions.map(p => p[0])) - cellRadius - spacing;
|
||||||
|
const packRight = Math.max(...positions.map(p => p[0])) + cellRadius + spacing;
|
||||||
|
const spineX = chosenSide === 'left'
|
||||||
|
? packLeft - extension
|
||||||
|
: packRight + extension;
|
||||||
|
|
||||||
|
const extraPads = [];
|
||||||
|
const extraSegments = [];
|
||||||
|
const sortedBoundaryCells = boundaryCells.slice().sort((a, b) => a.pos[1] - b.pos[1]);
|
||||||
|
|
||||||
|
const overlapPads = sortedBoundaryCells.map((entry, index) => {
|
||||||
|
const key = `edge_overlap_${index}`;
|
||||||
|
const overlapPos = [spineX, entry.pos[1]];
|
||||||
|
extraPads.push({ key, pos: overlapPos });
|
||||||
|
extraSegments.push({ from: overlapPos, to: entry.pos, fromKey: key, toKey: `c${entry.index}` });
|
||||||
|
return { key, pos: overlapPos };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect adjacent spine pads with a vertical segment and add fill pads.
|
||||||
|
for (let index = 0; index < overlapPads.length - 1; index++) {
|
||||||
|
const topPad = overlapPads[index];
|
||||||
|
const bottomPad = overlapPads[index + 1];
|
||||||
|
const topCell = sortedBoundaryCells[index];
|
||||||
|
const bottomCell = sortedBoundaryCells[index + 1];
|
||||||
|
|
||||||
|
extraSegments.push({
|
||||||
|
from: topPad.pos,
|
||||||
|
to: bottomPad.pos,
|
||||||
|
fromKey: topPad.key,
|
||||||
|
toKey: bottomPad.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fillKey = `edge_overlap_fill_${index}`;
|
||||||
|
const fillPos = [
|
||||||
|
(topPad.pos[0] + bottomPad.pos[0] + topCell.pos[0] + bottomCell.pos[0]) / 4,
|
||||||
|
(topPad.pos[1] + bottomPad.pos[1] + topCell.pos[1] + bottomCell.pos[1]) / 4,
|
||||||
|
];
|
||||||
|
extraPads.push({ key: fillKey, pos: fillPos });
|
||||||
|
extraSegments.push({ from: fillPos, to: topPad.pos, fromKey: fillKey, toKey: topPad.key });
|
||||||
|
extraSegments.push({ from: fillPos, to: bottomPad.pos, fromKey: fillKey, toKey: bottomPad.key });
|
||||||
|
extraSegments.push({ from: fillPos, to: topCell.pos, fromKey: fillKey, toKey: `c${topCell.index}` });
|
||||||
|
extraSegments.push({ from: fillPos, to: bottomCell.pos, fromKey: fillKey, toKey: `c${bottomCell.index}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
return { extraPads, extraSegments };
|
||||||
|
}
|
||||||
|
|
||||||
function computeCellCutouts(cellIndices, positions, cellCutoutEnabled) {
|
function computeCellCutouts(cellIndices, positions, cellCutoutEnabled) {
|
||||||
if (cellCutoutEnabled !== true) return [];
|
if (cellCutoutEnabled !== true) return [];
|
||||||
|
|
||||||
@@ -439,9 +662,11 @@ export function computeBusbarGeometry(cellIndices, positions, cellRadius, padRad
|
|||||||
});
|
});
|
||||||
|
|
||||||
const overlapFeatures = overlapEnabled
|
const overlapFeatures = overlapEnabled
|
||||||
? computeEdgeOverlapFeatures(cellIndices, positions, layoutType, overlapSize)
|
? computeEdgeOverlapFeatures(cellIndices, positions, layoutType, overlapSize, cellRadius, spacing)
|
||||||
: { extraPads: [], extraSegments: [] };
|
: { extraPads: [], extraSegments: [] };
|
||||||
const roundoverFeatures = computeBoundaryRoundoverFeatures(cellIndices, positions, padRadius);
|
const roundoverFeatures = layoutType === 'vertical'
|
||||||
|
? computeBoundaryRoundoverFeaturesVertical(cellIndices, positions, padRadius)
|
||||||
|
: computeBoundaryRoundoverFeatures(cellIndices, positions, padRadius);
|
||||||
const cutouts = computeCellCutouts(cellIndices, positions, cellCutoutEnabled);
|
const cutouts = computeCellCutouts(cellIndices, positions, cellCutoutEnabled);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
17
src/main.js
17
src/main.js
@@ -19,7 +19,6 @@ function scaleCanvasById(id) {
|
|||||||
const canvas = document.getElementById(id);
|
const canvas = document.getElementById(id);
|
||||||
if (!canvas) return;
|
if (!canvas) return;
|
||||||
const dpr = window.devicePixelRatio || 1;
|
const dpr = window.devicePixelRatio || 1;
|
||||||
// Let CSS (width:100%, height:720px) control the display size.
|
|
||||||
// Only update the drawing buffer to match the current CSS-rendered size × DPR.
|
// Only update the drawing buffer to match the current CSS-rendered size × DPR.
|
||||||
const rect = canvas.getBoundingClientRect();
|
const rect = canvas.getBoundingClientRect();
|
||||||
if (rect.width === 0 || rect.height === 0) return;
|
if (rect.width === 0 || rect.height === 0) return;
|
||||||
@@ -672,6 +671,22 @@ async function initializeApp() {
|
|||||||
|
|
||||||
await initOC();
|
await initOC();
|
||||||
|
|
||||||
|
// Keep canvas drawing buffers in sync whenever their CSS size changes
|
||||||
|
// (e.g. window resize, flex layout settling, face-filter toggles).
|
||||||
|
const canvasResizeObserver = new ResizeObserver(() => {
|
||||||
|
scaleCanvasForDPI();
|
||||||
|
updatePreview(false);
|
||||||
|
});
|
||||||
|
const previewCanvas = document.getElementById('preview');
|
||||||
|
const previewBottomCanvas = document.getElementById('preview-bottom');
|
||||||
|
if (previewCanvas) canvasResizeObserver.observe(previewCanvas);
|
||||||
|
if (previewBottomCanvas) canvasResizeObserver.observe(previewBottomCanvas);
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
scaleCanvasForDPI();
|
||||||
|
updatePreview(false);
|
||||||
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Force a fresh render after both OC init and config load are complete.
|
// Force a fresh render after both OC init and config load are complete.
|
||||||
// This ensures viewTransform and geometries are in sync with loaded busbars.
|
// This ensures viewTransform and geometries are in sync with loaded busbars.
|
||||||
|
|||||||
@@ -10,26 +10,29 @@ body {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
color: #e2e8f0;
|
color: #e2e8f0;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
padding: 48px 24px;
|
padding: 12px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 1600px;
|
max-width: 1600px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
transition: max-width 0.3s ease, padding 0.3s ease;
|
transition: max-width 0.3s ease, padding 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header */
|
/* Header */
|
||||||
.header-row {
|
.header-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 20px;
|
gap: 12px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2.5em;
|
font-size: 1.6em;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@@ -45,8 +48,8 @@ h1:hover {
|
|||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
font-size: 1em;
|
font-size: 0.88em;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 6px;
|
||||||
transition: color 0.3s ease;
|
transition: color 0.3s ease;
|
||||||
animation: fadeInUp 0.6s ease-out 0.1s both;
|
animation: fadeInUp 0.6s ease-out 0.1s both;
|
||||||
}
|
}
|
||||||
@@ -100,8 +103,8 @@ h1:hover {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin: 0 auto 18px;
|
margin: 0 auto 8px;
|
||||||
padding: 10px 18px;
|
padding: 6px 14px;
|
||||||
max-width: 700px;
|
max-width: 700px;
|
||||||
background: rgba(234, 179, 8, 0.1);
|
background: rgba(234, 179, 8, 0.1);
|
||||||
border: 1px solid rgba(234, 179, 8, 0.45);
|
border: 1px solid rgba(234, 179, 8, 0.45);
|
||||||
@@ -163,6 +166,9 @@ h1:hover {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 460px 1fr;
|
grid-template-columns: 460px 1fr;
|
||||||
gap: 28px;
|
gap: 28px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
align-items: stretch;
|
||||||
transition: grid-template-columns 0.4s cubic-bezier(0.4, 0, 0.2, 1), gap 0.3s ease;
|
transition: grid-template-columns 0.4s cubic-bezier(0.4, 0, 0.2, 1), gap 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,6 +197,8 @@ h1:hover {
|
|||||||
border: 1px solid rgba(100, 149, 237, 0.1);
|
border: 1px solid rgba(100, 149, 237, 0.1);
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
padding: 20px 20px 24px;
|
padding: 20px 20px 24px;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-height: 0;
|
||||||
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,6 +408,9 @@ h1:hover {
|
|||||||
|
|
||||||
.preview-container {
|
.preview-container {
|
||||||
padding: 28px;
|
padding: 28px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section:hover {
|
.section:hover {
|
||||||
@@ -763,12 +774,15 @@ input[type="checkbox"]:focus {
|
|||||||
.previews-row {
|
.previews-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
align-items: flex-start;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-face-wrap {
|
.preview-face-wrap {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
@@ -791,13 +805,14 @@ input[type="checkbox"]:focus {
|
|||||||
#preview,
|
#preview,
|
||||||
#preview-bottom {
|
#preview-bottom {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 720px;
|
height: min(calc(100vh - 280px), 580px);
|
||||||
|
min-height: 380px;
|
||||||
border: 1px solid rgba(100, 149, 237, 0.2);
|
border: 1px solid rgba(100, 149, 237, 0.2);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: #1e293b;
|
background: #1e293b;
|
||||||
display: block;
|
display: block;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview:hover,
|
#preview:hover,
|
||||||
|
|||||||
Reference in New Issue
Block a user