/* SatsChart — Cascade bar chart for KS2 SATs results */ .container { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.75rem; padding-top: 0.25rem; } /* ── Individual subject column ── */ .subjectChart { position: relative; } .subjectName { font-size: 0.65rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-muted); margin-bottom: 0.75rem; } .chartArea { position: relative; padding-bottom: 0.25rem; } /* ── Gridlines ── */ .gridlines { position: absolute; top: 0; left: 0; right: 0; bottom: 20px; pointer-events: none; } .gridline { position: absolute; top: 0; height: 100%; width: 1px; background: var(--bg-secondary); } /* ── Per-bar national average marker ── Each bar compares against its own benchmark (expected vs higher standard / greater depth), so the marker sits on the individual bar's track rather than as one line spanning both bars. */ /* * Knockout, not a colour. * * This marker was var(--brand) — the same value as .barExpected and * .barExceeding, so wherever it crossed a bar it measured 1.00:1 and was not * rendered distinguishably at all. It scored 5.47:1 only against the empty * track, which means it was visible precisely when a school was BELOW the * national average and vanished for every school at or above it. * * No single colour fixes this, because the marker's position is data-driven: * it can land on the bar, on the empty track, or straddle the boundary. So it * is drawn as a knockout — a light core carrying a dark edge. On the teal bar * the core reads; on the pale track the edge reads. Both tokens flip with the * theme, so the pairing holds in dark mode too. * * The edge is box-shadow rather than border so it costs no layout width and * cannot shift the 50% translate. */ .natTick { position: absolute; top: -3px; bottom: -3px; width: 3px; transform: translateX(-50%); background: var(--bg-card); box-shadow: 0 0 0 1px var(--text-primary); border-radius: 2px; z-index: 4; pointer-events: none; } .natTick::before { content: ''; position: absolute; top: -4px; left: 50%; transform: translateX(-50%); width: 6px; height: 6px; border-radius: 50%; background: var(--bg-card); box-shadow: 0 0 0 1px var(--text-primary); } .barHeaderRight { display: flex; align-items: baseline; gap: 0.4rem; } .natLabel { font-size: 0.55rem; font-weight: 700; color: var(--brand-strong); text-transform: uppercase; letter-spacing: 0.03em; } /* ── Bar rows ── */ .barGroup { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 0.35rem; padding-top: 0.9rem; } .barRow { display: flex; flex-direction: column; gap: 0.18rem; } .barHeader { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; } .barTrack { width: 100%; height: 14px; position: relative; } .bar { height: 100%; border-radius: 4px; position: relative; transition: width 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94); } /* * Expected and Exceeding were both var(--brand) — one colour for two series, * distinguished only by which row you were looking at, while the legend * claimed two. * * They are a sequential pair, not two categories: "exceeding" is a subset of * the same cohort at a harder bar. So they take two steps of the same hue * rather than two different hues, with the harder measure the more intense * step. --brand-stronger is darker than --brand in the light theme and lighter * in the dark one, which is the right direction in both: further from the * ground. */ .barExpected { background: var(--brand); } .barExceeding { background: var(--brand-stronger); } .barLabel { font-family: var(--font-display); font-size: 0.78rem; font-weight: 700; color: var(--text-primary); white-space: nowrap; position: relative; z-index: 3; } .barLabelSuffix { font-weight: 500; color: var(--text-muted); font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.06em; } /* ── Ruler ── */ .ruler { position: relative; height: 14px; margin-top: 0.3rem; border-top: 1px solid var(--border); } .rulerTick { position: absolute; top: 0; width: 1px; height: 3px; background: var(--border); } .rulerLabel { position: absolute; top: 5px; font-size: 0.5rem; font-weight: 500; color: var(--text-muted); transform: translateX(-50%); letter-spacing: 0.01em; opacity: 0.7; } /* Anchor first/last labels to edges */ .rulerLabelFirst { transform: translateX(0); } .rulerLabelLast { transform: translateX(-100%); } /* ── Legend ── */ .legend { margin-top: 1rem; display: flex; gap: 1.25rem; justify-content: center; } .legendItem { display: flex; align-items: center; gap: 0.35rem; font-size: 0.65rem; font-weight: 500; color: var(--text-muted); } .legendSwatch { width: 8px; height: 8px; border-radius: 3px; flex-shrink: 0; } /* ── Responsive ── */ @media (max-width: 640px) { .container { grid-template-columns: 1fr; gap: 1.5rem; } .legend { flex-wrap: wrap; gap: 0.75rem; } }