/* Public pages: the code entry with the ISP teaser (/), the ISP page, the legal pages and the hidden test (/<CODE>).
   The look borrows from the ISP's own hardware: a rack-grey enclosure, ports, link and activity LEDs,
   and the T568B cable colours as accents. */

@font-face {
  font-family: "Atkinson Next";
  src: url("/fonts/next-latin.woff2") format("woff2");
  font-weight: 200 800;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Atkinson Next";
  src: url("/fonts/next-latin-ext.woff2") format("woff2");
  font-weight: 200 800;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Atkinson Mono";
  src: url("/fonts/mono-latin.woff2") format("woff2");
  font-weight: 200 800;
  font-display: swap;
}

:root {
  --rack: #e3e7e4;
  --panel: #f6f7f5;
  --rule: #c9d0cb;
  --ink: #18293a;
  --muted: #4f5d68;
  --port: #26323b;
  --mark: #26323b;
  --hole: #111a20;
  --port-ink: #eef2f0;
  --blue: #2458b8;
  --blue-ink: #ffffff;
  --blue-soft: #dbe4f4;
  --orange: #e0681c;
  --green: #2e8f47;
  --brown: #8a5a2b;
  --link: #2bc44f;
  --activity: #f2a516;
  --led-off: #3b4953;
  --bad: #b8322a;
  --bad-led: #f0463a;
  --sans: "Atkinson Next", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: "Atkinson Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
  :root {
    --rack: #151c21;
    --panel: #1d262c;
    --rule: #2f3b43;
    --ink: #e4eaec;
    --muted: #9aa8b0;
    --port: #232d34;
    --hole: #0b1014;
    --mark: #4a5a65;
    --blue: #6c95f0;
    --blue-ink: #0d1520;
    --blue-soft: #1f2c45;
    --orange: #f08a45;
    --green: #4fc06f;
    --brown: #c08a55;
    --led-off: #2a353d;
    --bad: #f07b72;
  }
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body { margin: 0; min-height: 100vh; background: var(--rack); color: var(--ink); font: 17px/1.55 var(--sans); }
a { color: var(--blue); text-underline-offset: 0.18em; }
h1, h2, h3 { margin: 0; line-height: 1.15; font-weight: 750; letter-spacing: -0.01em; text-wrap: balance; }
h1:focus { outline: none; }
p { margin: 0; }
.muted { color: var(--muted); }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

button, .button {
  display: inline-block;
  font: inherit;
  font-weight: 700;
  text-align: center;
  text-decoration: none;
  padding: 14px 20px;
  border: 0;
  border-radius: 6px;
  background: var(--blue);
  color: var(--blue-ink);
  cursor: pointer;
}
button:disabled { opacity: 0.45; cursor: not-allowed; }
:is(button, .button, a):focus-visible { outline: 3px solid var(--blue); outline-offset: 2px; }

/* Header: the wordmark and, on the landing page, two links. */
.site-head { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 24px; max-width: 1040px; margin: 0 auto; padding: 20px 24px; }
.brand { display: inline-flex; align-items: center; gap: 10px; color: var(--ink); font-size: 19px; font-weight: 750; letter-spacing: -0.01em; text-decoration: none; }
.brand small { color: var(--muted); font-size: inherit; font-weight: 500; }
/* An RJ45 port with its link LED lit. */
.mark { position: relative; width: 22px; height: 18px; background: var(--mark); clip-path: polygon(0 0, 100% 0, 100% 72%, 70% 72%, 70% 100%, 30% 100%, 30% 72%, 0 72%); border-radius: 2px; }
.mark::after { content: ""; position: absolute; top: 3px; left: 3px; width: 5px; height: 3px; border-radius: 1px; background: var(--link); }
.site-head nav { display: flex; gap: 20px; margin-left: auto; font-size: 16px; }
.site-head nav a { color: var(--muted); text-decoration: none; }
.site-head nav a:hover { color: var(--ink); text-decoration: underline; }
.site-head nav a[aria-current="page"] { color: var(--ink); text-decoration: underline 2px var(--orange); }

/* Landing: the code entry comes first, because every customer arrives here on the phone. */
.hero { max-width: 1040px; margin: 0 auto; padding: 40px 24px 72px; }
.hero h1 { font-size: clamp(30px, 6.4vw, 48px); max-width: 17ch; }
.hero .lead { margin-top: 14px; max-width: 44ch; font-size: 19px; color: var(--muted); }
/* As wide as the port plate: the button lines up under it, and a message wraps instead of widening the form. */
.code-form { display: grid; gap: 14px; width: fit-content; max-width: 100%; margin-top: 32px; }

/* The code as four switch ports. One real input sits over the ports; each character advances one port.
   Left LED: link, lit once the port holds a character. Right LED: activity, on the port that takes the next one. */
.plate { display: inline-block; padding: 14px 16px 16px; border-radius: 10px; background: var(--port); box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.08); }
.plate:focus-within { outline: 3px solid var(--blue); outline-offset: 3px; }
.cells { --cell: clamp(50px, 15vw, 84px); --gap: 12px; position: relative; display: grid; grid-template-columns: repeat(4, var(--cell)); gap: var(--gap); overflow: hidden; }
.cells span {
  position: relative;
  height: calc(var(--cell) * 1.1);
  border-radius: 3px;
  background: var(--hole);
  box-shadow: inset 0 4px 8px rgb(0 0 0 / 0.6);
  clip-path: polygon(0 0, 100% 0, 100% 80%, 68% 80%, 68% 100%, 32% 100%, 32% 80%, 0 80%);
}
.cells span::before, .cells span::after { content: ""; position: absolute; top: 6px; width: 9px; height: 5px; border-radius: 1px; background: var(--led-off); }
.cells span::before { left: 6px; }
.cells span::after { right: 6px; }
.cells input {
  position: absolute;
  top: 0;
  left: 0;
  height: 80%;
  /* Wider than the ports, so the fourth character never scrolls the field. */
  width: calc(4 * var(--cell) + 4 * var(--gap) + 40px);
  margin: 0;
  padding: 0 0 0 calc((var(--cell) - 1ch) / 2);
  appearance: none;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--port-ink);
  font: 600 calc(var(--cell) * 0.56) / 1 var(--mono);
  letter-spacing: calc(var(--cell) + var(--gap) - 1ch);
  text-transform: uppercase;
  caret-color: var(--activity);
  outline: none;
}
/* With landing.js: link LEDs for filled ports, the activity LED on the next port. */
.cells[data-len="1"] span:nth-child(-n + 1)::before,
.cells[data-len="2"] span:nth-child(-n + 2)::before,
.cells[data-len="3"] span:nth-child(-n + 3)::before,
.cells[data-len="4"] span::before { background: var(--link); box-shadow: 0 0 6px var(--link); }
.cells[data-len="0"]:focus-within span:nth-child(1)::after,
.cells[data-len="1"]:focus-within span:nth-child(2)::after,
.cells[data-len="2"]:focus-within span:nth-child(3)::after,
.cells[data-len="3"]:focus-within span:nth-child(4)::after { background: var(--activity); box-shadow: 0 0 6px var(--activity); }
.cells[data-len="4"] input { caret-color: transparent; }
/* A wrong code: the link LEDs turn red and the panel shakes once when the page arrives. */
.plate:has(#code[aria-invalid="true"]) { animation: shake 0.4s ease-in-out; }
.cells:has(#code[aria-invalid="true"]) span::before { background: var(--bad-led); box-shadow: 0 0 6px var(--bad-led); }
@keyframes shake { 20%, 60% { transform: translateX(-6px); } 40%, 80% { transform: translateX(6px); } }
/* A reserved slot for errors and hints, so the button does not jump: one line, two on a phone. */
.error { min-height: 1.5em; margin: 0; color: var(--bad); font-size: 15px; contain: inline-size; }

/* The ISP sections: the teaser on the landing page, and the bands of the ISP page. */
.isp { border-top: 1px solid var(--rule); background: var(--panel); }
.isp-inner { display: grid; gap: 40px; max-width: 1040px; margin: 0 auto; padding: 64px 24px 72px; }
.isp h2 { font-size: clamp(26px, 4.4vw, 36px); }
.isp .lead { max-width: 60ch; margin-top: 14px; font-size: 19px; color: var(--muted); }
.flow { display: grid; gap: 28px; align-items: start; }
@media (min-width: 860px) { .flow { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 48px; } }

.diagram { margin: 0; }
.diagram svg { display: block; width: 100%; max-width: 460px; height: auto; font-family: var(--sans); }
.diagram .node rect { fill: var(--panel); stroke: var(--rule); stroke-width: 1.5; }
.diagram .node text { fill: var(--ink); font-size: 14px; font-weight: 700; }
.diagram .node .sub { fill: var(--muted); font-size: 11.5px; font-weight: 400; }
.diagram .responder rect { fill: var(--port); stroke: var(--port); }
.diagram .responder text { fill: var(--port-ink); }
.diagram .responder .sub { fill: #aab6bd; }
.diagram .responder .led { fill: var(--link); }
.diagram .net { fill: none; stroke: var(--muted); stroke-width: 1.2; stroke-dasharray: 3 4; }
.diagram .net-label { fill: var(--muted); font-size: 11.5px; }
.diagram .edge { fill: none; stroke: var(--muted); stroke-width: 2; }
.diagram .edge.phone { stroke-dasharray: 5 5; }
.diagram .cable { stroke: var(--orange); stroke-width: 4; }
.diagram .edge-label { fill: var(--ink); font-size: 12.5px; }
.diagram .arrow { fill: var(--muted); }
.diagram .part { transition: opacity 0.3s; }
/* landing.js picks a step: its parts stay, the rest steps back. */
.diagram[data-step="1"] .part:not(.s1),
.diagram[data-step="2"] .part:not(.s2),
.diagram[data-step="3"] .part:not(.s3) { opacity: 0.18; }
.diagram[data-step="3"] .cable { stroke-dasharray: 8 6; animation: flow 0.9s linear infinite; }
@keyframes flow { to { stroke-dashoffset: -14; } }

.steps { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; counter-reset: step; }
.steps button {
  display: grid;
  grid-template-columns: 32px 1fr;
  gap: 2px 12px;
  width: 100%;
  padding: 14px 16px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: none;
  color: var(--ink);
  font-weight: 400;
  text-align: left;
}
.steps button::before {
  counter-increment: step;
  content: counter(step);
  grid-row: span 2;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--orange);
  color: #fff;
  font-weight: 750;
}
.steps li:nth-child(2) button::before { background: var(--green); }
.steps li:nth-child(3) button::before { background: var(--blue); color: var(--blue-ink); }
.steps b { font-size: 18px; font-weight: 750; }
.steps span { color: var(--muted); }
.steps button[aria-pressed="true"] { border-color: var(--rule); background: var(--rack); }
.steps-wrap { display: grid; gap: 14px; }
.isp h3 { margin-bottom: 14px; font-size: 21px; }
.play { justify-self: start; padding: 10px 16px; border: 1px solid var(--rule); background: none; color: var(--ink); font-weight: 600; }
.play[hidden] { display: none; }

@media (max-width: 520px) { .error { min-height: 3em; } }
.gets { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px 32px; margin: 0; padding: 0; list-style: none; }
.gets li { position: relative; padding-left: 22px; }
.gets li::before { content: ""; position: absolute; top: 0.62em; left: 2px; width: 9px; height: 5px; border-radius: 1px; background: var(--link); box-shadow: 0 0 6px var(--link); }
@media (min-width: 720px) { .gets { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.more { font-size: 19px; font-weight: 700; }
.cta { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 24px; }
.site-foot { display: grid; gap: 6px; max-width: 1040px; margin: 0 auto; padding: 24px; color: var(--muted); font-size: 14px; }
.legal-links { display: flex; flex-wrap: wrap; gap: 4px 20px; }

/* The ISP page: a hero on the rack grey, then bands that alternate panel and rack grey. */
.isp-page .hero h1 { max-width: 20ch; }
.eyebrow { margin-bottom: 12px; color: var(--muted); font-size: 15px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; }
.hero .cta { margin-top: 28px; }
.free { display: inline-flex; align-items: center; gap: 10px; font-weight: 700; }
.free .led { background: var(--link); box-shadow: 0 0 6px var(--link); }
.isp.alt { background: var(--rack); }
.isp.alt .steps button[aria-pressed="true"] { background: var(--panel); }

/* Why a public speed test falls short: one card per reason, topped with a cable colour. */
.why { display: grid; gap: 24px; margin: 0; padding: 0; list-style: none; }
.why li { display: grid; align-content: start; gap: 6px; padding-top: 16px; border-top: 4px solid var(--orange); }
.why li:nth-child(2) { border-color: var(--green); }
.why li:nth-child(3) { border-color: var(--blue); }
.why b { font-size: 19px; }
.why span { color: var(--muted); }
@media (min-width: 720px) { .why { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 32px; } }

.compare { display: grid; gap: 32px; align-items: start; }
@media (min-width: 860px) { .compare { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 48px; } }
.compare .gets { grid-template-columns: minmax(0, 1fr); }
/* What the client sees: the last screen of the test, with no number on it. */
.screen { display: grid; gap: 12px; max-width: 380px; margin: 0; }
.screen-card { display: grid; justify-items: start; gap: 8px; padding: 22px 20px; border: 1px solid var(--rule); border-radius: 12px; background: var(--rack); }
.screen-card .check { width: 44px; height: 44px; }
.screen-card b { font-size: 21px; }
.screen-card span, .screen figcaption { color: var(--muted); }

/* Client figure against server figure, as bars. */
.pairs { display: grid; gap: 16px; max-width: 560px; margin: 0; }
.pair { display: grid; gap: 8px; padding: 16px 18px; border: 1px solid var(--rule); border-radius: 10px; background: var(--panel); }
.pair.suspect { border-color: var(--bad); }
.bar-row { display: grid; grid-template-columns: 5.5em minmax(0, 1fr) 3ch; align-items: center; gap: 12px; font-size: 15px; }
.bar-row svg { display: block; width: 100%; height: 10px; }
.bar-row rect { fill: var(--rule); }
.bar-row .fill { fill: var(--blue); }
.bar-row.server .fill { fill: var(--green); }
.bar-row b { font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; }
.pair p { color: var(--muted); font-size: 15px; }
.warn { color: var(--bad); }
.pairs figcaption { color: var(--muted); font-size: 14px; }

/* Requirements as a spec sheet. */
.spec { margin: 0; border-top: 1px solid var(--rule); }
.spec div { display: grid; gap: 4px 24px; padding: 14px 0; border-bottom: 1px solid var(--rule); }
.spec dt { color: var(--muted); font-size: 14px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; }
.spec dd { margin: 0; }
@media (min-width: 720px) { .spec div { grid-template-columns: 11rem minmax(0, 1fr); } .spec dt { padding-top: 2px; } }
.operator { font-style: normal; line-height: 1.7; }

/* The privacy policy and the terms: lines short enough to read. */
.legal-text { max-width: 1040px; margin: 0 auto; padding: 24px 24px 64px; }
.legal-text > * { max-width: 70ch; }
.legal-text h1 { margin-bottom: 8px; font-size: clamp(28px, 5vw, 40px); }
.legal-text h2 { margin: 36px 0 10px; font-size: 21px; }
.legal-text :is(p, ul) + :is(p, ul) { margin-top: 10px; }
.legal-text ul { margin: 0; padding-left: 20px; }
.legal-text li + li { margin-top: 6px; }

/* The test page: one panel under the wordmark. */
body.test { display: grid; grid-template-rows: auto 1fr; }
.card { align-self: center; justify-self: center; width: calc(100% - 32px); max-width: 460px; margin: 0 16px 48px; padding: 28px 24px; border: 1px solid var(--rule); border-radius: 12px; background: var(--panel); }
.card h1 { font-size: 26px; margin-bottom: 10px; }
.card p { margin-bottom: 14px; color: var(--muted); }
.card .lead { color: var(--ink); font-weight: 700; }
.tips { margin: 0 0 22px; padding-left: 20px; color: var(--muted); font-size: 16px; }
.tips li + li { margin-top: 4px; }
fieldset { display: grid; gap: 8px; margin: 0 0 20px; padding: 0; border: 0; }
legend { margin-bottom: 8px; padding: 0; font-weight: 700; }
fieldset label { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border: 1px solid var(--rule); border-radius: 8px; cursor: pointer; }
fieldset label:has(input:checked) { border-color: var(--blue); background: var(--blue-soft); }
fieldset input { width: 18px; height: 18px; margin: 0; accent-color: var(--blue); }
fieldset label:has(input:focus-visible) { outline: 3px solid var(--blue); outline-offset: 2px; }
.card button { width: 100%; }

/* A switch drawn next to a visually hidden checkbox, so every browser keeps the native control. */
.switch { position: relative; display: flex; align-items: center; gap: 12px; margin: 0 0 16px; font-size: 16px; cursor: pointer; }
.switch input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.track { position: relative; flex: none; width: 38px; height: 22px; border-radius: 99px; background: var(--rule); transition: background-color 0.2s; }
.track::before { content: ""; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgb(0 0 0 / 0.3); transition: transform 0.2s; }
.switch input:checked + .track { background: var(--blue); }
.switch input:checked + .track::before { transform: translateX(16px); }
.switch input:focus-visible + .track { outline: 3px solid var(--blue); outline-offset: 2px; }

/* Running test. During the measurement only transform and opacity animate. */
.bar { position: relative; height: 10px; margin: 4px 0 6px; overflow: hidden; border-radius: 3px; background: var(--rule); }
.bar > div { height: 100%; background: var(--blue); transform: scaleX(0); transform-origin: left; transition: transform 0.3s ease-out; }
/* 24 segments: gaps in the panel colour over the track and the fill. */
.bar::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(to right, transparent 0 calc(100% / 24 - 2px), var(--panel) calc(100% / 24 - 2px) calc(100% / 24));
}
.card .eta { margin: 0 0 12px; font-size: 15px; text-align: right; font-variant-numeric: tabular-nums; }
.phases { display: grid; gap: 4px; margin: 0; padding: 0; list-style: none; }
.phases li { display: flex; align-items: center; gap: 12px; padding: 9px 14px; border-radius: 8px; color: var(--muted); }
.phases li[hidden] { display: none; }
.phases li.active { background: var(--rack); color: var(--ink); font-weight: 700; }
.phases li.done { color: var(--ink); }
/* Status LEDs: dark is pending, blinking amber is working (a constant tempo), green is done. */
.led { flex: none; width: 12px; height: 7px; border-radius: 1.5px; background: var(--rule); }
.active > .led { background: var(--activity); box-shadow: 0 0 8px var(--activity); animation: blink 1s ease-in-out infinite; }
.done > .led { background: var(--link); box-shadow: 0 0 6px var(--link); }
@keyframes blink { 50% { opacity: 0.25; } }

/* Done: the same check and burst for every run, in the four cable colours. */
.burst { position: relative; width: 56px; height: 56px; margin-bottom: 14px; }
.check { display: block; width: 56px; height: 56px; }
.check circle { fill: none; stroke: var(--green); stroke-width: 3; stroke-dasharray: 139; stroke-dashoffset: 139; transform: rotate(-90deg); transform-origin: center; transform-box: fill-box; animation: draw 0.5s ease-out forwards; }
.check path { fill: none; stroke: var(--green); stroke-width: 3.5; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 31; stroke-dashoffset: 31; animation: draw 0.3s ease-out 0.4s forwards; }
@keyframes draw { to { stroke-dashoffset: 0; } }
.burst i { position: absolute; top: 50%; left: 50%; width: 6px; height: 6px; margin: -3px; border-radius: 1px; background: var(--orange); opacity: 0; animation: spark 0.6s ease-out 0.45s; }
.burst i:nth-of-type(4n + 2) { background: var(--green); }
.burst i:nth-of-type(4n + 3) { background: var(--blue); }
.burst i:nth-of-type(4n) { background: var(--brown); }
.burst i:nth-of-type(1) { --turn: 0deg; }
.burst i:nth-of-type(2) { --turn: 45deg; }
.burst i:nth-of-type(3) { --turn: 90deg; }
.burst i:nth-of-type(4) { --turn: 135deg; }
.burst i:nth-of-type(5) { --turn: 180deg; }
.burst i:nth-of-type(6) { --turn: 225deg; }
.burst i:nth-of-type(7) { --turn: 270deg; }
.burst i:nth-of-type(8) { --turn: 315deg; }
@keyframes spark {
  from { opacity: 1; transform: rotate(var(--turn)) translateY(-28px) scale(1); }
  to { opacity: 0; transform: rotate(var(--turn)) translateY(-48px) scale(0.3); }
}

@media (prefers-reduced-motion: reduce) {
  .bar > div, .track, .track::before, .diagram .part { transition: none; }
  .active > .led, .diagram[data-step="3"] .cable, .plate:has(#code[aria-invalid="true"]) { animation: none; }
  .check circle, .check path { animation: none; stroke-dashoffset: 0; }
  .burst i { display: none; }
}
