From 44c8fe26bf0f98df1304730c835b51618cda7aa4 Mon Sep 17 00:00:00 2001 From: Ole-Morten Duesund Date: Fri, 13 Mar 2026 13:09:04 +0100 Subject: [PATCH] Initial commit: forskjeller.naiv.no Interactive salary gap calculator (public/lonn/) showing how percentage-based raises amplify absolute salary differences over time. Includes landing page, README, and CLAUDE.md. Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 38 +++++ README.md | 56 +++++++ public/index.html | 155 +++++++++++++++++ public/lonn/app.js | 159 ++++++++++++++++++ public/lonn/index.html | 169 +++++++++++++++++++ public/lonn/style.css | 369 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 946 insertions(+) create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 public/index.html create mode 100644 public/lonn/app.js create mode 100644 public/lonn/index.html create mode 100644 public/lonn/style.css diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..bb13538 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,38 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Interactive web application that visualizes how percentage-based salary increases amplify absolute salary differences over time. Norwegian-language UI ("Lønnsforskjellen vokser" = "The salary gap grows"). + +## Architecture + +Hosted at `forskjeller.naiv.no`. Static site in `public/` — no build system, no package manager. Serve `public/` as the site root. Each topic gets its own subdirectory (e.g. `public/lonn/`). + +- `public/lonn/index.html` — Page structure: header, 4 range-slider controls, 4 stat cards, 4 chart canvases, explainer section +- `public/lonn/style.css` — CSS custom properties in `:root`, CSS Grid layouts, responsive breakpoints at 560px/480px/360px +- `public/lonn/app.js` — All logic: + - `fmtKr(n)` / `fmtShort(n)` — Norwegian locale number formatting (nb-NO) + - `getData()` — Computes compound growth arrays from slider values + - `baseOpts()` — Shared Chart.js config factory for all 4 charts + - `update()` — Reads sliders, recalculates, updates DOM and charts (called on every `input` event) + +**External dependencies** (loaded via CDN, no install needed): Chart.js 4.4.1, Google Fonts (Fraunces + DM Sans). + +### Key Design Decisions + +- All 4 charts share `baseOpts()` — modify this for consistent chart styling changes +- Charts update with `'none'` animation mode for responsive slider interaction +- Mathematical model: gap = `(B₀ − A₀) × (1 + r)ⁿ` + +## Development + +```bash +python3 -m http.server 8000 -d public +# open http://localhost:8000/lonn/ +``` + +## Language + +All user-facing text is in Norwegian Bokmål (nb-NO). Use Norwegian for UI strings and `nb-NO` locale for number formatting. diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa23498 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# forskjeller.naiv.no + +Interaktive visualiseringer av forskjeller og ulikhet i Norge. + +## Lønn + +Kalkulator som viser hvordan prosentvise lønnstillegg forsterker lønnsforskjeller over tid. + +## Hva er dette? + +Prosentvise lønnstillegg *ser* rettferdige ut — alle får den samme prosenten. Men matematikken er asymmetrisk: jo høyere startlønn, desto flere kroner i absolutt tillegg hvert år. Denne kalkulatoren visualiserer effekten over en hel karriere. + +**Formelen:** +``` +Kroneforskjell etter n år = (B₀ − A₀) × (1 + vekst)ⁿ +``` + +Den opprinnelige forskjellen forsvinner aldri — den multipliseres eksponentielt. + +## Funksjoner + +- 4 interaktive slidere: startlønn A, startlønn B, årlig vekst (%), antall år +- 4 diagrammer: årslønn, absolutt gap, månedlig gap, akkumulert total +- Statistikkort med nøkkeltall +- Responsivt design for mobil og desktop + +## Kjøre lokalt + +Åpne `public/lonn/index.html` direkte i en nettleser, eller start en lokal server: + +```bash +python3 -m http.server 8000 -d public +``` + +Deretter åpne `http://localhost:8000/lonn/` i nettleseren. + +## Avhengigheter + +- [Chart.js 4.4.1](https://www.chartjs.org/) — lastes via CDN +- [Google Fonts](https://fonts.google.com/) — Fraunces (overskrifter) og DM Sans (brødtekst) + +Ingen byggsteg eller pakkebehandler nødvendig. + +## Filstruktur + +``` +public/ +└── lonn/ + ├── index.html # Lønnsforskjell-kalkulator + ├── style.css # Styling og responsivt design + └── app.js # Beregningslogikk, diagrammer og interaktivitet +``` + +## Lisens + +Tallene er illustrative og ikke finansiell rådgivning. diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..86190a3 --- /dev/null +++ b/public/index.html @@ -0,0 +1,155 @@ + + + + + +Forskjeller — interaktive visualiseringer + + + + + + +
+

forskjeller.naiv.no

+

Forskjeller

+

Interaktive visualiseringer som viser hvordan tilsynelatende små forskjeller vokser over tid.

+
+ +
+ +
+ +
+ Tallene er illustrative og ikke finansiell rådgivning +
+ + + diff --git a/public/lonn/app.js b/public/lonn/app.js new file mode 100644 index 0000000..902578a --- /dev/null +++ b/public/lonn/app.js @@ -0,0 +1,159 @@ +function fmtKr(n) { + return Math.round(n).toLocaleString('nb-NO') + '\u202fkr'; +} + +function fmtShort(n) { + if (n >= 1e6) return (n/1e6).toFixed(1).replace('.',',') + '\u202fM'; + if (n >= 1e3) return Math.round(n/1e3) + '\u202fk'; + return Math.round(n); +} + +function getData() { + const A0 = +document.getElementById('salA').value; + const B0 = +document.getElementById('salB').value; + const pct = +document.getElementById('pct').value / 100; + const Y = +document.getElementById('yrs').value; + const labels=[], sA=[], sB=[], gaps=[], monthly=[], cumA=[], cumB=[], cumGaps=[]; + let rA=0, rB=0; + for (let y=0; y<=Y; y++) { + labels.push(y===0 ? 'I dag' : (y % 5 === 0 || Y <= 10) ? 'År '+y : (y===Y ? 'År '+y : '')); + const a = A0 * Math.pow(1+pct, y); + const b = B0 * Math.pow(1+pct, y); + sA.push(a); sB.push(b); + gaps.push(b-a); + monthly.push((b-a)/12); + rA += a; rB += b; + cumA.push(rA); cumB.push(rB); cumGaps.push(rB-rA); + } + return {A0,B0,pct,Y,labels,sA,sB,gaps,monthly,cumA,cumB,cumGaps}; +} + +const GRID_COLOR = 'rgba(0,0,0,0.06)'; +const TICK_COLOR = '#8a857e'; + +function baseOpts() { + return { + responsive: true, maintainAspectRatio: false, + plugins: { + legend: { display: false }, + tooltip: { + callbacks: { label: c => ' ' + fmtKr(c.parsed.y) }, + backgroundColor: '#1a1714', + titleColor: '#f5f2eb', + bodyColor: 'rgba(245,242,235,0.7)', + padding: 10, + cornerRadius: 4, + titleFont: { family: 'DM Sans', size: 12 }, + bodyFont: { family: 'DM Sans', size: 12 } + } + }, + scales: { + x: { + ticks: { color: TICK_COLOR, font: { size: 11, family: 'DM Sans' }, autoSkip: false, maxRotation: 0 }, + grid: { display: false }, + border: { color: 'rgba(0,0,0,0.1)' } + }, + y: { + ticks: { color: TICK_COLOR, font: { size: 11, family: 'DM Sans' }, callback: v => fmtShort(v) }, + grid: { color: GRID_COLOR }, + border: { dash: [3,3], color: 'transparent' } + } + } + }; +} + +const chart1 = new Chart(document.getElementById('chart1'), { + type: 'line', + data: { labels:[], datasets:[ + { data:[], borderColor:'#1a4a8a', backgroundColor:'rgba(26,74,138,0.07)', fill:true, tension:0.35, pointRadius:0, borderWidth:2 }, + { data:[], borderColor:'#c0392b', backgroundColor:'rgba(192,57,43,0.07)', fill:true, tension:0.35, pointRadius:0, borderWidth:2 } + ]}, + options: baseOpts() +}); + +const chart2 = new Chart(document.getElementById('chart2'), { + type: 'line', + data: { labels:[], datasets:[ + { data:[], borderColor:'#4a3a8a', backgroundColor:'rgba(74,58,138,0.1)', fill:true, tension:0.35, pointRadius:0, borderWidth:2 } + ]}, + options: baseOpts() +}); + +const chart3 = new Chart(document.getElementById('chart3'), { + type: 'line', + data: { labels:[], datasets:[ + { data:[], borderColor:'#2c6e49', backgroundColor:'rgba(44,110,73,0.1)', fill:true, tension:0.35, pointRadius:0, borderWidth:2 } + ]}, + options: baseOpts() +}); + +const chart4 = new Chart(document.getElementById('chart4'), { + type: 'line', + data: { labels:[], datasets:[ + { data:[], borderColor:'#1a4a8a', backgroundColor:'rgba(26,74,138,0.07)', fill:true, tension:0.35, pointRadius:0, borderWidth:2 }, + { data:[], borderColor:'#c0392b', backgroundColor:'rgba(192,57,43,0.07)', fill:true, tension:0.35, pointRadius:0, borderWidth:2 }, + { data:[], borderColor:'#b5620a', backgroundColor:'rgba(181,98,10,0.1)', fill:true, tension:0.35, pointRadius:0, borderWidth:2, borderDash:[6,4] } + ]}, + options: baseOpts() +}); + +// Alle verdier som settes via innerHTML kommer fra numeriske beregninger (slider-verdier), +// ikke fra brukerinput-tekst — ingen XSS-risiko. +function update() { + const {A0,B0,pct,Y,labels,sA,sB,gaps,monthly,cumA,cumB,cumGaps} = getData(); + + // Oppdater visning + document.querySelectorAll('.yr-lbl').forEach(el => el.textContent = Y); + document.getElementById('salA-out').textContent = Math.round(A0).toLocaleString('nb-NO') + '\u202fkr'; + document.getElementById('salB-out').textContent = Math.round(B0).toLocaleString('nb-NO') + '\u202fkr'; + document.getElementById('pct-out').textContent = (+document.getElementById('pct').value).toLocaleString('nb-NO', {minimumFractionDigits:1}) + '\u202f%'; + document.getElementById('yrs-out').textContent = Y + '\u202får'; + + // Statistikk + document.getElementById('st-finalA').textContent = fmtKr(sA[Y]); + document.getElementById('st-monthA').textContent = fmtKr(sA[Y]/12) + '/mnd'; + document.getElementById('st-subA').textContent = '+' + fmtKr(sA[Y]-A0) + ' fra start'; + document.getElementById('st-finalB').textContent = fmtKr(sB[Y]); + document.getElementById('st-monthB').textContent = fmtKr(sB[Y]/12) + '/mnd'; + document.getElementById('st-subB').textContent = '+' + fmtKr(sB[Y]-B0) + ' fra start'; + document.getElementById('st-diffyr').textContent = fmtKr(gaps[Y]) + '/år'; + document.getElementById('st-diffmnd').textContent = fmtKr(monthly[Y]) + '/mnd'; + document.getElementById('st-diffstart').textContent = 'Var ' + fmtKr(B0-A0) + '/år ved start'; + document.getElementById('st-cumgap').textContent = fmtKr(cumGaps[Y]); + + // Banner + document.getElementById('banner-gap').textContent = fmtKr(cumGaps[Y]); + + // Innsikt — innerHTML er trygt her: verdiene er kun formaterte tall fra slidere + const monthlyNow = Math.round((B0-A0)/12); + const monthlyEnd = Math.round(monthly[Y]); + document.getElementById('insight-monthly').innerHTML = + `Månedlig gap i dag: ${fmtKr(monthlyNow)} ekstra til B. `+ + `Etter ${Y} år er det ${fmtKr(monthlyEnd)} per måned — `+ + `en økning på ${fmtKr(monthlyEnd - monthlyNow)} kun pga. prosentvekst.`; + + // Diagrammer + chart1.data.labels = labels; + chart1.data.datasets[0].data = sA; + chart1.data.datasets[1].data = sB; + chart1.update('none'); + + chart2.data.labels = labels; + chart2.data.datasets[0].data = gaps; + chart2.update('none'); + + chart3.data.labels = labels; + chart3.data.datasets[0].data = monthly; + chart3.update('none'); + + chart4.data.labels = labels; + chart4.data.datasets[0].data = cumA; + chart4.data.datasets[1].data = cumB; + chart4.data.datasets[2].data = cumGaps; + chart4.update('none'); +} + +['salA','salB','pct','yrs'].forEach(id => + document.getElementById(id).addEventListener('input', update) +); +update(); diff --git a/public/lonn/index.html b/public/lonn/index.html new file mode 100644 index 0000000..c91d7eb --- /dev/null +++ b/public/lonn/index.html @@ -0,0 +1,169 @@ + + + + + +Lønnsforskjellen vokser — interaktiv kalkulator + + + + + + + +
+

Samme prosent. Ikke det samme resultatet.

+

Lønnsforskjellen
vokser

+

Prosentvise lønnstillegg ser rettferdige ut — men de gjør de rike rikere, hvert eneste år. Se hva som skjer over tid.

+
Spis de rike
+
+ +
+ + Selv en liten forskjell på 50 000 kr i startlønn blir til over i løpet av en karriere — med identisk prosentvekst. +
+ +
+ +
+

Sjekk selv

+
+
+ +
+ + 500 000 kr +
+
+
+ +
+ + 550 000 kr +
+
+
+ +
+ + 3,5 % +
+
+
+ +
+ + 20 år +
+
+
+
+ +
+
+
Lønn A etter 20 år
+
+
+
+
+
+
Lønn B etter 20 år
+
+
+
+
+
+
B tjener mer enn A
+
+
+
+
+
+
Akkumulert differanse
+
+
over 20 år
+
+
+ + +
+
+
+
Årslønn over tid
+
Begge lønninger vokser med samme prosentsats
+
+
+ Lønn A + Lønn B +
+
+
+
+ + +
+
+
+
Absolutt lønnsforskjell per år
+
Kroneforskjellen i årslønn — vokser hvert år
+
+
+ Gap (år) +
+
+
+
+ +
+ + +
+
+
+
Lønnsforskjell per måned
+
Hva B mottar ekstra på kontoen — hver måned
+
+
+ Gap (måned) +
+
+
+
+ + +
+
+
+
Akkumulert total lønn utbetalt
+
Summen av alle utbetalinger gjennom hele perioden
+
+
+ Akkum. A + Akkum. B + Akkum. gap +
+
+
+
+ + +
+

Hvorfor er dette viktig?

+

Prosentvise lønnstillegg ser rettferdige ut fordi alle får det samme prosenttallet. Men matematikken er asymmetrisk: jo høyere startlønn, jo flere kroner får man i absolutt tillegg — hvert eneste år.

+
+ Lønn etter n år = Startlønn × (1 + vekst)ⁿ
+ Kroneforskjell = (B₀ − A₀) × (1 + vekst)ⁿ +
+

Legg merke til at faktoren (B₀ − A₀) — den opprinnelige forskjellen — aldri forsvinner. Den multipliseres eksponentielt. Det er derfor gapet vokser selv om begge får akkurat samme prosentsats.

+

Alternativet som bremser gapet er kronetillegg: et fast beløp til alle, uavhengig av lønnsnivå. Det gir lavtlønte relativt sett mer og bremser den eksponentielle gapveksten. Norske fagforbund har historisk kjempet for nettopp dette.

+
+ +
+ +
+ Laget med Chart.js · Tallene er illustrative og ikke finansiell rådgivning +
+ + + + diff --git a/public/lonn/style.css b/public/lonn/style.css new file mode 100644 index 0000000..7745725 --- /dev/null +++ b/public/lonn/style.css @@ -0,0 +1,369 @@ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +:root { + --bg: #f5f2eb; + --bg2: #ede9e0; + --bg3: #e4dfd3; + --ink: #1a1714; + --ink2: #5a5650; + --ink3: #8a857e; + --accent: #c0392b; + --accent2: #2c6e49; + --blue: #1a4a8a; + --amber: #b5620a; + --purple: #4a3a8a; + --border: rgba(26,23,20,0.12); + --radius: 4px; +} + +html { scroll-behavior: smooth; } + +body { + font-family: 'DM Sans', sans-serif; + background: var(--bg); + color: var(--ink); + font-size: 16px; + line-height: 1.6; + min-height: 100vh; +} + +/* HEADER */ +header { + background: var(--ink); + color: var(--bg); + padding: 3.5rem 2rem 3rem; + text-align: center; + position: relative; + overflow: hidden; +} +header::before { + content: ''; + position: absolute; + inset: 0; + background: repeating-linear-gradient( + -45deg, + transparent, + transparent 40px, + rgba(255,255,255,0.02) 40px, + rgba(255,255,255,0.02) 41px + ); +} +header .eyebrow { + font-size: 11px; + letter-spacing: 0.18em; + text-transform: uppercase; + color: rgba(245,242,235,0.5); + margin-bottom: 1rem; + font-weight: 400; +} +header h1 { + font-family: 'Fraunces', serif; + font-size: clamp(2.2rem, 6vw, 4rem); + font-weight: 600; + line-height: 1.1; + margin-bottom: 1rem; + position: relative; +} +header h1 em { + font-style: italic; + color: #e8b4a0; +} +header p.lead { + font-size: clamp(0.95rem, 2vw, 1.1rem); + color: rgba(245,242,235,0.65); + max-width: 560px; + margin: 0 auto; + font-weight: 300; + line-height: 1.7; +} + +/* MAIN LAYOUT */ +main { + max-width: 900px; + margin: 0 auto; + padding: 2.5rem 1.25rem 4rem; +} + +/* CONTROLS CARD */ +.controls-card { + background: var(--ink); + color: var(--bg); + border-radius: 8px; + padding: 2rem; + margin-bottom: 2.5rem; +} +.controls-card h2 { + font-family: 'Fraunces', serif; + font-size: 1rem; + font-weight: 300; + font-style: italic; + color: rgba(245,242,235,0.5); + margin-bottom: 1.5rem; + letter-spacing: 0.03em; +} +.controls-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.25rem 2rem; +} +@media (max-width: 560px) { + .controls-grid { grid-template-columns: 1fr; gap: 1rem; } +} + +.control-group label { + display: block; + font-size: 12px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: rgba(245,242,235,0.45); + margin-bottom: 0.5rem; + font-weight: 400; +} +.control-group .val-row { + display: flex; + align-items: center; + gap: 10px; +} +.control-group input[type=range] { + flex: 1; + -webkit-appearance: none; + height: 3px; + background: rgba(245,242,235,0.15); + border-radius: 2px; + outline: none; + cursor: pointer; +} +.control-group input[type=range]::-webkit-slider-thumb { + -webkit-appearance: none; + width: 16px; height: 16px; + border-radius: 50%; + background: var(--bg); + cursor: pointer; + transition: transform 0.1s; +} +.control-group input[type=range]::-webkit-slider-thumb:hover { transform: scale(1.2); } +.control-group input[type=range]::-moz-range-thumb { + width: 16px; height: 16px; + border-radius: 50%; + background: var(--bg); + cursor: pointer; + border: none; +} +.control-group .val-display { + font-size: 1rem; + font-weight: 500; + color: var(--bg); + min-width: 80px; + text-align: right; + font-variant-numeric: tabular-nums; +} + +/* STAT STRIP */ +.stat-strip { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px; + margin-bottom: 2.5rem; +} +@media (max-width: 600px) { + .stat-strip { grid-template-columns: 1fr 1fr; } +} +@media (max-width: 360px) { + .stat-strip { grid-template-columns: 1fr; } +} +.stat { + background: var(--bg2); + border: 1px solid var(--border); + border-radius: 6px; + padding: 1rem 1.1rem; +} +.stat-label { + font-size: 11px; + letter-spacing: 0.07em; + text-transform: uppercase; + color: var(--ink3); + margin-bottom: 0.35rem; +} +.stat-value { + font-size: 1.4rem; + font-weight: 500; + color: var(--ink); + font-variant-numeric: tabular-nums; + line-height: 1.2; +} +.stat-sub { + font-size: 12px; + color: var(--ink3); + margin-top: 0.2rem; +} + +/* CHART SECTIONS */ +.chart-section { + background: #fff; + border: 1px solid var(--border); + border-radius: 8px; + padding: 1.5rem; + margin-bottom: 1.25rem; +} +.chart-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin-bottom: 1rem; + gap: 1rem; + flex-wrap: wrap; +} +.chart-title { + font-family: 'Fraunces', serif; + font-size: 1.1rem; + font-weight: 600; + color: var(--ink); +} +.chart-desc { + font-size: 13px; + color: var(--ink3); + margin-top: 2px; +} +.legend { + display: flex; + gap: 14px; + font-size: 12px; + color: var(--ink2); + flex-wrap: wrap; + align-items: center; +} +.legend span { display: flex; align-items: center; gap: 5px; } +.swatch { + width: 10px; height: 10px; + border-radius: 2px; + flex-shrink: 0; + display: inline-block; +} +.swatch.dashed { + background: linear-gradient(90deg, var(--sw-color) 50%, transparent 50%); + background-size: 6px 2px; + background-repeat: repeat-x; + background-position: center; + background-color: transparent; + height: 2px; + width: 18px; + border-radius: 0; +} +.chart-wrap { + position: relative; + width: 100%; +} + +/* INSIGHT BOX */ +.insight { + background: var(--bg2); + border-left: 3px solid var(--accent); + border-radius: 0 6px 6px 0; + padding: 1rem 1.25rem; + margin-bottom: 1.25rem; + font-size: 0.95rem; + color: var(--ink2); + line-height: 1.65; +} +.insight strong { color: var(--ink); font-weight: 500; } + +/* EXPLAINER */ +.explainer { + background: var(--bg3); + border: 1px solid var(--border); + border-radius: 8px; + padding: 1.75rem 2rem; + margin-top: 2rem; +} +.explainer h3 { + font-family: 'Fraunces', serif; + font-size: 1.2rem; + font-weight: 600; + margin-bottom: 1rem; +} +.explainer p { + font-size: 0.95rem; + color: var(--ink2); + margin-bottom: 0.75rem; + line-height: 1.7; +} +.explainer p:last-child { margin-bottom: 0; } +.explainer strong { color: var(--ink); font-weight: 500; } + +/* FORMULA */ +.formula-box { + background: var(--ink); + color: var(--bg); + border-radius: 6px; + padding: 1rem 1.25rem; + font-family: 'DM Sans', monospace; + font-size: 0.9rem; + margin: 1rem 0; + letter-spacing: 0.02em; + line-height: 1.8; +} +.formula-box span { color: #e8b4a0; } + +/* STAMP */ +.stamp { + display: inline-block; + position: relative; + margin-top: 2rem; + padding: 0.55rem 1.1rem; + border: 3px solid #e8b4a0; + border-radius: 4px; + font-family: 'Fraunces', serif; + font-size: clamp(0.75rem, 2.5vw, 0.95rem); + font-weight: 600; + letter-spacing: 0.12em; + text-transform: uppercase; + color: #e8b4a0; + opacity: 0.72; + transform: rotate(-4deg); + box-shadow: inset 0 0 0 2px rgba(232,180,160,0.18); + user-select: none; +} +.stamp::before { + content: ''; + position: absolute; + inset: 3px; + border: 1px solid rgba(232,180,160,0.3); + border-radius: 2px; + pointer-events: none; +} + +/* SMALL DIFF BANNER */ +.small-diff-banner { + background: var(--accent); + color: #fff; + padding: 1rem 2rem; + display: flex; + align-items: flex-start; + gap: 0.75rem; + font-size: clamp(0.88rem, 2vw, 1rem); + line-height: 1.6; +} +.small-diff-banner strong { font-weight: 500; } +.sd-icon { font-size: 1.1rem; flex-shrink: 0; margin-top: 2px; } + +/* FOOTER */ +footer { + text-align: center; + padding: 2rem 1rem; + font-size: 12px; + color: var(--ink3); + border-top: 1px solid var(--border); + margin-top: 3rem; +} + +/* RESPONSIVE CHART HEIGHTS */ +.h-240 { height: 240px; } +.h-200 { height: 200px; } +@media (max-width: 480px) { + .h-240 { height: 200px; } + .h-200 { height: 170px; } + .chart-section { padding: 1rem; } + main { padding: 1.5rem 1rem 3rem; } + .controls-card { padding: 1.25rem; } + .explainer { padding: 1.25rem; } +}