{{ store.state.validationError }}
@@ -122,6 +131,13 @@ const offendsProgramEffect = offends('programEffect') container-name: controls; } +.ctl-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + h3 { font-size: 13px; font-weight: 600; @@ -130,6 +146,35 @@ h3 { color: var(--ink-4); } +.reroll { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 12px; + font-weight: 600; + color: var(--ink-3); + background: var(--bg); + border: 1px solid var(--border); + border-radius: 999px; + padding: 4px 11px; + cursor: pointer; +} + +.reroll:hover { + border-color: var(--ink-4); + color: var(--ink); +} + +.reroll svg.ic { + width: 13px; + height: 13px; + fill: none; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; +} + .group + .group { margin-top: 22px; border-top: 1px solid var(--border-soft); diff --git a/web/src/components/PanelGrid.vue b/web/src/components/PanelGrid.vue index 5f20267..3e2ad17 100644 --- a/web/src/components/PanelGrid.vue +++ b/web/src/components/PanelGrid.vue @@ -40,7 +40,12 @@ function syncRowHeight() { const element = gridElement.value if (!element) return if (oneScreen.matches) { - const rowHeight = Math.max(150, Math.floor((element.clientHeight - ROW_GAP_PX) / 2)) + // clientHeight includes the container's padding (which gives the card + // shadows room); subtract it so two rows fit the content area exactly. + const style = getComputedStyle(element) + const padV = parseFloat(style.paddingTop) + parseFloat(style.paddingBottom) + const available = element.clientHeight - padV + const rowHeight = Math.max(150, Math.floor((available - ROW_GAP_PX) / 2)) element.style.setProperty('--row-h', `${rowHeight}px`) } else { element.style.removeProperty('--row-h') @@ -166,9 +171,10 @@ onBeforeUnmount(() => { @media (min-width: 761px) { .panelgrid { overflow-y: auto; - /* Reserve room so the scrollbar (overlay or classic) never sits over the - right column of graphs when a 5th scenario makes the grid scroll. */ - padding-right: 14px; + /* Room so the card shadows are not clipped at the scroll edges; the right + also leaves the scrollbar gutter. syncRowHeight subtracts this padding + so the 2x2 still fits exactly. */ + padding: 4px 14px 14px 6px; scrollbar-gutter: stable; } } diff --git a/web/src/composables/useLayout.ts b/web/src/composables/useLayout.ts index 9954f40..b1424cb 100644 --- a/web/src/composables/useLayout.ts +++ b/web/src/composables/useLayout.ts @@ -16,8 +16,8 @@ export interface LayoutPoint { y: number } -export const NETWORK_VIEW_WIDTH = 380 -export const NETWORK_VIEW_HEIGHT = 230 +export const NETWORK_VIEW_WIDTH = 340 +export const NETWORK_VIEW_HEIGHT = 270 const LAYOUT_PADDING = 14 const LAYOUT_TICKS = 300 @@ -38,29 +38,31 @@ export function layoutForGraph(config: Config, edges: number[][]): LayoutPoint[] const random = mulberry32(Number(config.graphSeed) >>> 0) const nodes: LayoutNode[] = Array.from({ length: config.numStudents }, (_, index) => { - // Seeded start positions in a wide disc; the forces do the rest. + // Seeded start positions in a balanced disc; the forces do the rest. const angle = random() * Math.PI * 2 const radius = Math.sqrt(random()) - return { index, x: Math.cos(angle) * radius * 150, y: Math.sin(angle) * radius * 60 } + return { index, x: Math.cos(angle) * radius * 120, y: Math.sin(angle) * radius * 90 } }) const links: SimulationLinkDatum