body {
    font-family: Arial, Helvetica, sans-serif;
    max-width: 1000px;
    margin: 40px auto;
    padding: 0 20px;
    color: #222;
}

h1 {
    font-size: 32px;
}

h2 {
    margin-top: 30px;
}

p {
    line-height: 1.6;
}

#chart {
    margin-top: 30px;
}

/* Lab navigation */

.lab-nav {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 30px 0;
    padding-bottom: 15px;
    border-bottom: 1px solid #ddd;
}

.lab-nav a {
    padding: 8px 14px;
    text-decoration: none;
    background: #f1f1f1;
    color: #333;
    border-radius: 5px;
}

.lab-nav a:hover {
    background: #333;
    color: white;
}

.lab-nav a.active {
    background: steelblue;
    color: white;
}

/* Bar chart */

.bar {
    fill: steelblue;
}

.bar:hover {
    fill: #1f4e79;
}

.bar-value {
    font-size: 13px;
    font-weight: bold;
    fill: #333;
    text-anchor: middle;
}

.bar-name {
    font-size: 13px;
    fill: #555;
    text-anchor: middle;
}

.chart-title {
    font-size: 20px;
    font-weight: bold;
    fill: #222;
}

/* Lab list on the home page */

.lab-list {
    list-style: none;
    padding: 0;
}

.lab-list li {
    padding: 10px 0;
    border-bottom: 1px solid #eee;
}

.lab-list li.pending {
    color: #888;
}

.lab-list a {
    color: steelblue;
    font-weight: bold;
    text-decoration: none;
}

.lab-list a:hover {
    text-decoration: underline;
}

.lab-list span {
    display: block;
    color: #555;
    font-size: 14px;
    margin-top: 4px;
}

/* ---------- Lab 2: tooltip, legends and small-multiple panels ---------- */

.tooltip {
    position: absolute;
    background: #fcfcfb;
    border: 1px solid #c3c2b7;
    padding: 8px 10px;
    border-radius: 4px;
    pointer-events: none;
    opacity: 0;
    font-size: 13px;
    line-height: 1.5;
    color: #0b0b0b;
    box-shadow: 0 2px 8px rgba(11, 11, 11, 0.12);
    transition: opacity 0.12s;
}

/* Axes and legends stay recessive against the marks. */
.axis text {
    fill: #52514e;
}

.axis line,
.axis path {
    stroke: #c3c2b7;
}

.axis-label {
    fill: #52514e;
    font-size: 14px;
}

.legend-title {
    fill: #0b0b0b;
    font-size: 13px;
    font-weight: bold;
}

.legend-item text {
    fill: #52514e;
    font-size: 13px;
}

.viz-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 28px;
    margin: 20px 0 24px;
}

.legend-block {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.legend-label {
    font-size: 13px;
    font-weight: bold;
    color: #0b0b0b;
}

.ramp {
    display: flex;
}

.ramp-step {
    width: 34px;
    height: 12px;
    border-radius: 2px;
    margin-right: 2px;
}

.ramp-ends,
.dev-name {
    font-size: 12px;
    color: #52514e;
}

.dev-items {
    display: flex;
    gap: 18px;
}

.dev-item {
    font-size: 12px;
    color: #2a78d6;
    letter-spacing: 1px;
}

.dev-name {
    margin-left: 6px;
    letter-spacing: normal;
}

.panel-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
    gap: 10px 24px;
}

.panel svg {
    max-width: 100%;
    height: auto;
}

.panel-title {
    fill: #0b0b0b;
    font-size: 14px;
    font-weight: bold;
}

.baseline {
    stroke: #c3c2b7;
    stroke-width: 1;
}

.city-name {
    fill: #0b0b0b;
    font-size: 13px;
}

.city-pips {
    fill: #2a78d6;
    font-size: 11px;
    letter-spacing: 1px;
}

.city-value {
    fill: #52514e;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.city-row:hover .city-bar {
    stroke: #0b0b0b;
    stroke-width: 1.5;
}

.row-hit {
    cursor: pointer;
}

.table-view {
    margin-top: 24px;
}

.table-view summary {
    cursor: pointer;
    color: steelblue;
    font-size: 14px;
}

#justification p {
    max-width: 68ch;
}

/* ---------- Lab 3: dataset facts and the sortable table ---------- */

.dataset-facts {
    margin: 20px 0 30px;
    padding: 16px 20px;
    background: #f9f9f7;
    border: 1px solid #e1e0d9;
    border-radius: 6px;
    line-height: 1.6;
}

.dataset-facts dt {
    font-weight: bold;
    color: #0b0b0b;
    margin-top: 10px;
}

.dataset-facts dt:first-child {
    margin-top: 0;
}

.dataset-facts dd {
    margin: 2px 0 0;
    color: #52514e;
}

.link-list {
    line-height: 1.9;
}

.link-list a,
.dataset-facts a {
    color: steelblue;
}

.table-controls {
    display: flex;
    align-items: center;
    gap: 14px;
    margin: 16px 0 10px;
}

#search {
    padding: 7px 10px;
    border: 1px solid #c3c2b7;
    border-radius: 4px;
    font-size: 14px;
    min-width: 240px;
}

.row-status {
    font-size: 13px;
    color: #52514e;
}

.table-scroll {
    max-height: 560px;
    overflow: auto;
    border: 1px solid #e1e0d9;
    border-radius: 6px;
}

.sortable {
    width: 100%;
    margin: 0;
    border-collapse: separate;
    border-spacing: 0;
}

.sortable thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: #f1f1f1;
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}

.sortable thead th:hover,
.sortable thead th:focus {
    background: #e4e4e2;
}

.sortable thead th.sorted {
    color: #184f95;
}

.sortable td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.sortable tbody tr:nth-child(even) {
    background: #fafaf8;
}

.sortable tbody tr:hover {
    background: #eef4fb;
}

.site-note {
    margin-top: 40px;
    color: #888;
    font-size: 13px;
}

/* Data tables */

.data-table {
    border-collapse: collapse;
    margin-bottom: 20px;
    min-width: 240px;
}

.data-table th,
.data-table td {
    border: 1px solid #ddd;
    padding: 6px 16px;
    text-align: left;
}

.data-table th {
    background: #f1f1f1;
}

.back-link {
    display: inline-block;
    margin-top: 30px;
    color: steelblue;
}

/* ---------- Final project: proposal page ---------- */

.question-list {
    max-width: 68ch;
}

.question-list li {
    margin-bottom: 8px;
}

.caveat {
    max-width: 68ch;
    margin: 20px 0 30px;
    padding: 14px 18px;
    background: #fdf6f3;
    border-left: 3px solid #9e3f26;
    border-radius: 0 4px 4px 0;
    font-size: 15px;
}

.data-table .num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.timeline td {
    vertical-align: top;
    font-size: 14px;
}

.timeline td:first-child {
    font-weight: bold;
}

/* Sketches for the five proposed views */

.sketch-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 24px;
    margin: 24px 0 30px;
}

.sketch {
    margin: 0;
    padding: 14px;
    border: 1px solid #e1e0d9;
    border-radius: 6px;
    background: #fdfdfc;
}

.sketch svg {
    width: 100%;
    height: auto;
    display: block;
}

.sketch figcaption {
    margin-top: 10px;
    font-size: 14px;
    line-height: 1.5;
    color: #52514e;
}

.sketch figcaption strong {
    display: block;
    color: #0b0b0b;
    font-size: 15px;
}

.sketch .idiom {
    display: block;
    margin: 2px 0 6px;
    font-size: 12px;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: #2a78d6;
}

/* ---------- Lab 4: sentiment charts and the model-confidence panel ---------- */

/*
 * Sentiment is a diverging scale, so it is drawn with two poles and a neutral
 * midpoint rather than three unrelated hues. The grey midpoint sits below 3:1
 * against the page, which is why every chart using it also ships direct labels
 * and a table view.
 */
.viz-root {
    --negative: #e34948;
    --neutral: #c3c2b7;
    --positive: #2a78d6;
    --surface: #ffffff;
    --ink: #0b0b0b;
    --ink-soft: #52514e;
}

.viz-figure {
    margin: 22px 0 10px;
}

.viz-figure svg {
    width: 100%;
    height: auto;
    display: block;
    overflow: visible;
}

.viz-caption {
    max-width: 68ch;
    margin: 4px 0 26px;
    font-size: 14px;
    line-height: 1.6;
    color: #52514e;
}

/* Legend: a swatch beside text, never coloured text. */
.key {
    display: flex;
    flex-wrap: wrap;
    gap: 18px;
    margin: 0 0 6px;
    padding: 0;
    list-style: none;
    font-size: 13px;
    color: #52514e;
}

.key li {
    display: flex;
    align-items: center;
    gap: 7px;
}

.key .swatch {
    width: 13px;
    height: 13px;
    border-radius: 3px;
    flex: none;
}

.swatch.negative { background: #e34948; }
.swatch.neutral  { background: #c3c2b7; }
.swatch.positive { background: #2a78d6; }

/* Chart type */

.viz-root .row-label {
    fill: #0b0b0b;
    font-size: 13px;
}

.viz-root .row-meta,
.viz-root .tick-label {
    fill: #52514e;
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}

.viz-root .seg-label {
    font-size: 11px;
    font-weight: bold;
    font-variant-numeric: tabular-nums;
    pointer-events: none;
}

.viz-root .zero-line {
    stroke: #52514e;
    stroke-width: 1;
}

.viz-root .grid-line {
    stroke: #e1e0d9;
    stroke-width: 1;
}

.viz-root .bar-hit {
    cursor: default;
}

.viz-root .bar-hit:hover .segment {
    opacity: 0.82;
}

/* Table view — the relief for the low-contrast neutral, and the text
   alternative to the chart. */

.table-view[open] summary {
    margin-bottom: 12px;
}

/* The shared .data-table has no numeric alignment of its own, so the routes
   table brings its own rather than depending on another page's styles. */
#routes-table .num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* Model-confidence panel */

.uncertain-list {
    list-style: none;
    padding: 0;
    margin: 18px 0 0;
}

.uncertain-list li {
    padding: 12px 0;
    border-bottom: 1px solid #eee;
}

.uncertain-list .tweet {
    margin: 0 0 8px;
    font-size: 14px;
    line-height: 1.55;
    max-width: 74ch;
}

.prob-bar {
    display: flex;
    height: 10px;
    width: 260px;
    max-width: 100%;
    border-radius: 3px;
    overflow: hidden;
    gap: 2px;
}

.prob-bar span {
    display: block;
    height: 100%;
}

.prob-figures {
    margin-top: 5px;
    font-size: 12px;
    color: #52514e;
    font-variant-numeric: tabular-nums;
}

.verdict {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 7px;
    border-radius: 3px;
    background: #f1f1f1;
    font-size: 12px;
    color: #0b0b0b;
}

/* Characteristic terms from the TF-IDF matrix */

.term-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 20px;
    margin: 20px 0 10px;
}

.term-block h4 {
    margin: 0 0 8px;
    font-size: 14px;
    display: flex;
    align-items: center;
    gap: 7px;
}

.term-block ol {
    margin: 0;
    padding-left: 20px;
    font-size: 14px;
    line-height: 1.7;
    color: #52514e;
}

.loading {
    color: #888;
    font-size: 14px;
}

.method {
    max-width: 68ch;
}

.method summary {
    cursor: pointer;
    color: steelblue;
    font-size: 14px;
    margin-bottom: 10px;
}

/* ---------- Lab 5: network diagram and adjacency matrix ---------- */

/*
 * Two figures, one colour scale each: district in the node-link view, route
 * type in the matrix. District's five hues are validated for all-pairs
 * separation, but two of them (yellow, magenta) sit below 3:1 on white — the
 * printed station numbers and the table view are the relief for that, so
 * neither may be removed.
 */

.viz-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 14px;
    margin: 22px 0 18px;
}

.control-label {
    font-size: 13px;
    font-weight: bold;
    color: #0b0b0b;
}

.button-set {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.button-set button,
.ghost-button {
    padding: 7px 13px;
    border: 1px solid #e1e0d9;
    border-radius: 5px;
    background: #f1f1f1;
    color: #333;
    font: inherit;
    font-size: 13px;
    cursor: pointer;
}

.button-set button:hover,
.ghost-button:hover {
    background: #e4e4e2;
}

.button-set button.active {
    background: steelblue;
    border-color: steelblue;
    color: white;
}

.ghost-button {
    background: none;
    color: #52514e;
}

/* Legend rows — a swatch beside text, never coloured text. The district and
   route-type rows are buttons because they double as filters. */

.legend-rows {
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.legend-swatch-row {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 2px 6px 2px 3px;
    border: 1px solid transparent;
    border-radius: 4px;
    background: none;
    font: inherit;
    font-size: 13px;
    color: #52514e;
    text-align: left;
    cursor: pointer;
}

.legend-swatch-row.static {
    cursor: default;
    padding-left: 3px;
}

.legend-swatch-row:not(.static):hover {
    background: #f1f1f1;
}

.legend-swatch-row.pinned {
    border-color: #c3c2b7;
    background: #eef4fb;
    color: #0b0b0b;
    font-weight: bold;
}

.legend-swatch-row svg {
    flex: none;
}

.legend-tick {
    font-size: 11px;
    fill: #52514e;
    font-variant-numeric: tabular-nums;
}

/* Node-link view */

.net-node {
    cursor: grab;
}

.net-node:active {
    cursor: grabbing;
}

.net-link {
    pointer-events: stroke;
}

/* The station number is the direct label that lets a reader identify one node,
   and the halo is what keeps it legible where it crosses a link. */
.net-label {
    font-size: 9px;
    font-variant-numeric: tabular-nums;
    fill: #0b0b0b;
    stroke: #ffffff;
    stroke-width: 3px;
    paint-order: stroke;
    pointer-events: none;
}

/* The strip that holds the stations with no connections, so "unconnected" is
   something the picture states rather than something the reader has to spot. */
.tray-rule {
    stroke: #c3c2b7;
    stroke-dasharray: 4 4;
}

.tray-label {
    font-size: 12px;
    fill: #898781;
}

/* Adjacency matrix */

.matrix-name {
    font-size: 8.5px;
    fill: #52514e;
}

.matrix-row.lit .matrix-name,
.matrix-col.lit .matrix-name {
    fill: #0b0b0b;
    font-weight: bold;
}

.matrix-row.lit .matrix-glyph,
.matrix-col.lit .matrix-glyph {
    fill: #0b0b0b;
}

.matrix-crosshair .wash {
    fill: rgba(11, 11, 11, 0.06);
    pointer-events: none;
}

.group-name {
    font-size: 12px;
    font-weight: bold;
    fill: #0b0b0b;
}

/* Written sections */

.prose p,
.prose-lead {
    max-width: 68ch;
}

.prose-lead {
    color: #52514e;
}

.findings {
    display: flex;
    flex-direction: column;
    gap: 18px;
    margin: 24px 0 30px;
}

.finding {
    padding: 16px 20px 6px;
    border: 1px solid #e1e0d9;
    border-radius: 6px;
    background: #fdfdfc;
}

.finding h3 {
    margin: 0 0 10px;
    font-size: 16px;
}

.finding-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 12px;
}

.finding-tag {
    padding: 2px 9px;
    border-radius: 3px;
    background: #f1f1f1;
    font-size: 12px;
    color: #0b0b0b;
}

.finding-tag.view {
    background: #eef4fb;
    color: #184f95;
}

.finding-encoding {
    max-width: 68ch;
    margin: 0 0 12px;
    font-size: 14px;
    color: #52514e;
}

.finding-body p {
    max-width: 68ch;
    font-size: 15px;
}

/* ---------- Lab 6: GDP treemaps and the practice tree ---------- */

/*
 * GDP status is the only data colour in the two assignment treemaps
 * (blue / grey / red, diverging around "Unchanged"); the hierarchy is carried
 * by two neutral frame tones, so structure never competes with status.
 */

.gdp-panel h3 {
    margin: 30px 0 4px;
    font-size: 17px;
}

.gdp-panel h3 code {
    font-size: 13px;
    font-weight: normal;
    color: #52514e;
}

.viz-controls.compact {
    margin: 8px 0 0;
    min-height: 32px;
}

.crumb {
    font-size: 13px;
    color: #52514e;
}

.legend-note {
    font-size: 12px;
    color: #898781;
}

.legend-rows.horizontal {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 4px 18px;
}

.status-chip,
.frame-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 20px;
    height: 16px;
    border-radius: 2px;
    font-size: 10px;
}

.frame-chip {
    border: 1px solid #c3c2b7;
}

.gdp-frame,
.gdp-leaf {
    cursor: zoom-in;
}

.zoomed .gdp-frame,
.zoomed .gdp-leaf {
    cursor: zoom-out;
}

.gdp-leaf rect {
    stroke: #ffffff;
    stroke-width: 0;
}

.gdp-leaf:hover rect,
.gdp-leaf.linked rect {
    stroke: #0b0b0b;
    stroke-width: 2.5;
}

.frame-label {
    pointer-events: none;
}

.frame-label.continent {
    font-size: 13px;
    font-weight: bold;
    fill: #ffffff;
    letter-spacing: 0.2px;
}

.frame-label.area {
    font-size: 11px;
    font-weight: bold;
    fill: #0b0b0b;
}

.leaf-name,
.leaf-value {
    pointer-events: none;
}

.leaf-name {
    font-size: 12px;
    font-weight: bold;
}

.leaf-value {
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}

.group-axis .bracket,
.group-axis .leader {
    fill: none;
    stroke: #898781;
    stroke-width: 1;
}

.group-axis .axis-name {
    font-size: 12px;
    fill: #0b0b0b;
}

.tip-path {
    color: #52514e;
    font-size: 12px;
}

.tip-swatch {
    display: inline-block;
    width: 10px;
    height: 10px;
    margin-right: 3px;
    border-radius: 2px;
    vertical-align: -1px;
}

.gdp-table th.num,
.gdp-table td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* Practice tree */

#tree .link {
    fill: none;
    stroke: #c3c2b7;
    stroke-width: 1.5;
}

#tree .node text {
    font-size: 12px;
    fill: #0b0b0b;
    stroke: #ffffff;
    stroke-width: 3px;
    paint-order: stroke;
}

#tree .node.branch {
    cursor: pointer;
}

/* Practice treemap: zoomed cells slide outside the view and must be cut off. */
.viz-figure svg.clipped {
    overflow: hidden;
}

#treemap .cell {
    cursor: zoom-in;
}

/* ---------- Lab 7: temporal network and weather line chart ---------- */

/*
 * Region is the only categorical colour in the network (blue / orange / aqua,
 * the palette's first three slots, validated all-pairs). Aqua measures 2.74:1
 * on the page, below 3:1, so the relief rule applies: every node carries a
 * printed company name and the page ships the relationship table. Neither the
 * labels nor the table may be removed. Sector rides on shape and transaction
 * type on dash pattern, so no second hue scale competes with region.
 */

/* Tooltip internals — value leads, label follows. */

.tip-head {
    font-weight: bold;
    color: #0b0b0b;
    margin-bottom: 4px;
}

.tip-row {
    white-space: nowrap;
}

.tip-row strong {
    font-variant-numeric: tabular-nums;
}

.tip-key {
    color: #52514e;
}

.tip-note {
    margin-top: 5px;
    padding-top: 4px;
    border-top: 1px solid #e1e0d9;
    font-size: 12px;
    color: #898781;
}

/* Grid lines outside the .viz-root scope Lab 4 introduced. The selector has to
   out-specify the `.axis line` rule above, which would otherwise paint them at
   tick-mark weight. */

.axis line.grid-line,
.grid-line {
    stroke: #e1e0d9;
    stroke-width: 1;
}

/* Controls */

.viz-controls .transport {
    display: flex;
    gap: 6px;
}

.transport button.active {
    background: steelblue;
    border-color: steelblue;
    color: white;
}

input[type="range"] {
    flex: 1 1 240px;
    min-width: 200px;
    accent-color: steelblue;
}

.check-row {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    color: #52514e;
}

.date-field {
    padding: 6px 8px;
    border: 1px solid #c3c2b7;
    border-radius: 4px;
    font: inherit;
    font-size: 13px;
}

select.date-field {
    padding-right: 4px;
}

.readout {
    font-size: 13px;
    color: #52514e;
    font-variant-numeric: tabular-nums;
}

/* Day summary — a row of stat tiles above the animation, so the numbers are
   readable at a specific day rather than only remembered from a frame. */

.day-summary {
    margin: 20px 0 6px;
}

.stat-rows {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.stat-row {
    flex: 1 1 150px;
    padding: 9px 12px 10px;
    border: 1px solid #e1e0d9;
    border-radius: 6px;
    background: #fdfdfc;
}

.stat-row .stat-key {
    display: block;
    font-size: 12px;
    color: #52514e;
}

.stat-row .stat-val {
    display: block;
    margin-top: 2px;
    font-size: 19px;
    font-weight: bold;
    color: #0b0b0b;
    font-variant-numeric: tabular-nums;
}

.stat-side {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 26px;
    margin-top: 10px;
    align-items: baseline;
}

.stat-note {
    margin: 0;
    font-size: 13px;
    line-height: 1.5;
}

.note-key {
    color: #52514e;
}

.stat-note strong {
    font-variant-numeric: tabular-nums;
}

.stat-changes {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    width: 100%;
}

.change {
    margin: 0;
    font-size: 13px;
}

.change-tag {
    display: inline-block;
    padding: 1px 7px;
    border-radius: 3px;
    background: #0b0b0b;
    color: #ffffff;
    font-size: 11px;
    letter-spacing: 0.3px;
}

.change.closes .change-tag {
    background: none;
    border: 1px solid #0b0b0b;
    color: #0b0b0b;
}

.no-change {
    margin: 0;
    font-size: 13px;
    color: #898781;
}

/* Network */

.region-bands line {
    stroke: #e1e0d9;
    stroke-dasharray: 3 5;
}

.region-bands text {
    font-size: 11px;
    font-weight: bold;
    letter-spacing: 1.2px;
}

/* One group per cluster, at a single opacity so the overlapping circles and
   fat strokes inside it flatten instead of compounding into darker patches. */
.hulls .hull {
    opacity: 0.075;
    pointer-events: none;
}

.hulls .hull circle {
    fill: #0b0b0b;
}

.hulls .hull line {
    stroke: #0b0b0b;
    stroke-width: 30px;
    stroke-linecap: round;
}

.net-link .hit {
    stroke: transparent;
    cursor: pointer;
}

.net-link .wire {
    pointer-events: none;
}

.net-link .event {
    pointer-events: none;
}

.net-link:hover .wire {
    stroke: #0b0b0b !important;
    stroke-opacity: 1 !important;
}

.net-link.dim {
    opacity: 0.18;
}

#network .net-node {
    cursor: grab;
    outline: none;
}

#network .net-node:active {
    cursor: grabbing;
}

#network .net-label {
    font-size: 11px;
    font-weight: bold;
}

#network .net-node.idle .net-label {
    fill: #898781;
    font-weight: normal;
}

#network .net-node:hover .glyph,
#network .net-node:focus .glyph {
    stroke: #0b0b0b;
    stroke-width: 2.5;
}

.day-stamp {
    font-size: 26px;
    font-weight: bold;
    fill: #0b0b0b;
    font-variant-numeric: tabular-nums;
}

.date-stamp {
    font-size: 13px;
    fill: #52514e;
    font-variant-numeric: tabular-nums;
}

/* Overview strips */

.strip-title {
    font-size: 12px;
    font-weight: bold;
    fill: #0b0b0b;
}

.day-bar {
    fill: #c9c8c2;
}

.day-bar.in-window {
    fill: #86b6ef;
}

.day-bar.current {
    fill: #184f95;
}

.day-cursor {
    fill: #0b0b0b;
    pointer-events: none;
}

.phase-mark line {
    stroke: #898781;
    stroke-dasharray: 2 4;
}

.phase-mark text {
    font-size: 11px;
    fill: #898781;
}

.scrub {
    fill: transparent;
    cursor: crosshair;
}

/* Relationship grid */

.rel-name {
    font-size: 10.5px;
    fill: #0b0b0b;
}

.rel-dash {
    stroke: #9a9892;
    stroke-width: 2;
}

.rel-band {
    fill: #f4f3ef;
}

.rel-cell {
    cursor: pointer;
}

.rel-row:hover .rel-band,
.rel-row.lit .rel-band {
    fill: #eef4fb;
}

.rel-row.lit .rel-name {
    font-weight: bold;
}

.rel-row:hover .rel-cell {
    stroke: #0b0b0b;
    stroke-width: 0.75;
}

.rel-cursor {
    fill: none;
    stroke: #0b0b0b;
    stroke-width: 1.5;
    pointer-events: none;
}

.legend-key {
    color: #0b0b0b;
}

/* Weather line chart */

#weather .city-line {
    stroke-linejoin: round;
    stroke-linecap: round;
}

.hairline {
    stroke: #898781;
    stroke-width: 1;
    stroke-dasharray: 3 3;
    pointer-events: none;
}

/* Direct labels at the line ends. The text wears the ink token and the short
   stroke beside it carries the series colour, so identity never rests on
   coloured text. */
.end-label text {
    font-size: 12px;
    font-weight: bold;
    fill: #0b0b0b;
}

.end-label line {
    stroke-width: 2.5;
    stroke-linecap: round;
}

#weather .marker {
    stroke: #ffffff;
    stroke-width: 2;
    pointer-events: none;
}

#weather .brush .selection {
    fill: #2a78d6;
    fill-opacity: 0.1;
    stroke: #184f95;
}

.grid .domain {
    display: none;
}

/* ---------- Lab 8: bulletin semantic map and topic x section matrix ---------- */

/*
 * Topic is the only categorical colour on the page (the eight documented
 * slots, in order). A scatter puts every pair of topics side by side, and
 * eight hues cannot all stay distinct under colour-vision deficiency, so every
 * topic also carries a direct label at its centre on the map, and the matrix
 * names every column in text.
 */

.l8-summary-grid {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    gap: 28px;
    align-items: start;
}

.l8-summary-grid h3,
.l8-panel h3 {
    margin: 0 0 6px;
    font-size: 15px;
}

.l8-search {
    flex: 1 1 220px;
    min-width: 180px;
    padding: 7px 10px;
    border: 1px solid #c3c2b7;
    border-radius: 4px;
    font: inherit;
    font-size: 14px;
}

.l8-select {
    max-width: 260px;
}

.l8-map-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 18px;
    align-items: start;
}

#semantic-map svg {
    border: 1px solid #e1e0d9;
    border-radius: 6px;
    background: #fcfcfb;
    cursor: grab;
    touch-action: none;
}

#semantic-map svg:active {
    cursor: grabbing;
}

.passage {
    stroke: #fcfcfb;
    stroke-width: 0.8;
    cursor: pointer;
    transition: opacity 0.15s;
}

.passage.dim {
    opacity: 0.07;
}

.passage.neighbour {
    stroke: #0b0b0b;
    stroke-width: 1.6;
}

.passage.selected {
    stroke: #0b0b0b;
    stroke-width: 2.5;
}

.neighbour-link {
    stroke: #0b0b0b;
    stroke-width: 1.2;
    stroke-dasharray: 3 2;
    pointer-events: none;
}

.topic-label {
    font-size: 12px;
    font-weight: bold;
    fill: #0b0b0b;
    stroke: #fcfcfb;
    stroke-width: 3.5px;
    stroke-linejoin: round;
    paint-order: stroke;
    pointer-events: none;
    text-anchor: middle;
}

.l8-panel {
    position: sticky;
    top: 12px;
    max-height: 640px;
    overflow-y: auto;
    padding: 14px 16px;
    border: 1px solid #e1e0d9;
    border-radius: 6px;
    background: #fdfdfc;
    font-size: 13px;
    line-height: 1.5;
}

.l8-panel .empty {
    color: #898781;
}

.l8-panel dl {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 2px 10px;
    margin: 0 0 10px;
}

.l8-panel dt {
    color: #52514e;
}

.l8-panel dd {
    margin: 0;
}

.l8-panel .passage-text {
    margin: 0 0 12px;
    padding: 8px 10px;
    border-left: 3px solid #c3c2b7;
    background: #fcfcfb;
    font-size: 13px;
}

.l8-panel mark {
    background: #fbe7a1;
}

.l8-panel h4 {
    margin: 12px 0 6px;
    font-size: 13px;
}

.nn-list {
    margin: 0;
    padding-left: 18px;
}

.nn-list li {
    margin-bottom: 7px;
    cursor: pointer;
}

.nn-list li:hover .nn-text {
    text-decoration: underline;
}

.nn-meta {
    display: block;
    color: #52514e;
    font-size: 12px;
}

.nn-other {
    padding: 0 5px;
    border-radius: 3px;
    background: #eef4fb;
    color: #184f95;
    font-size: 11px;
}

.dot {
    display: inline-block;
    width: 9px;
    height: 9px;
    margin-right: 5px;
    border-radius: 50%;
    vertical-align: baseline;
}

.l8-chip {
    display: none;
    align-items: center;
    gap: 6px;
    padding: 4px 8px;
    border: 1px solid #c3c2b7;
    border-radius: 4px;
    background: #eef4fb;
    font-size: 12px;
}

.l8-chip.on {
    display: inline-flex;
}

.l8-chip button {
    border: none;
    background: none;
    font: inherit;
    cursor: pointer;
    color: #52514e;
}

.l8-topic-legend {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 2px 8px;
    margin: 0 0 10px;
}

.matrix-row-label {
    font-size: 12.5px;
    fill: #0b0b0b;
}

.matrix-row-label.part {
    font-weight: bold;
    cursor: pointer;
}

.matrix-row-label.section {
    fill: #52514e;
}

.matrix-head {
    font-size: 11.5px;
    fill: #0b0b0b;
    text-anchor: middle;
}

.matrix-cell rect {
    stroke: #fcfcfb;
    stroke-width: 2;
    cursor: pointer;
}

.matrix-cell text {
    font-size: 11px;
    text-anchor: middle;
    pointer-events: none;
    font-variant-numeric: tabular-nums;
}

.matrix-cell.linked rect,
.matrix-cell.active rect {
    stroke: #0b0b0b;
    stroke-width: 2.5;
}

.matrix-total {
    font-size: 11.5px;
    fill: #52514e;
    font-variant-numeric: tabular-nums;
}

.l8-methods {
    margin: 10px 0 0;
}

.l8-word-count {
    font-size: 12px;
    color: #898781;
}

@media (max-width: 760px) {
    .l8-summary-grid,
    .l8-map-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    .l8-panel {
        position: static;
        max-height: none;
    }

    .l8-topic-legend {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* ---------- Lab 9: GDP choropleth and cartogram ---------- */

/*
 * GDP is the only data colour: a one-hue blue ramp (steps 150 → 700) on the
 * choropleth, one flat step (250) on the cartogram so area is its only GDP
 * channel. "No data" is a hatched neutral in both, never a ramp step, so a
 * missing country cannot be read as the smallest class.
 */

.l9-linked {
    position: relative;
}

/* The selection bar stays in view while the reader scrolls between the two
   maps, so the linked country is named even when the other map is off screen. */
.l9-selection {
    position: sticky;
    top: 0;
    z-index: 5;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 12px;
    margin: 24px 0 0;
    padding: 9px 12px;
    border: 1px solid #e1e0d9;
    border-radius: 6px;
    background: rgba(253, 253, 252, 0.96);
    box-shadow: 0 2px 6px rgba(11, 11, 11, 0.06);
}

.l9-readout {
    flex: 1 1 320px;
    font-size: 13px;
    color: #0b0b0b;
    font-variant-numeric: tabular-nums;
}

.l9-hint {
    color: #898781;
}

.l9-map svg {
    border: 1px solid #e1e0d9;
    border-radius: 6px;
    cursor: grab;
    touch-action: none;
    /* .viz-figure svg lets content spill; a zoomed map must stay in its frame. */
    overflow: hidden;
}

.l9-map svg:active {
    cursor: grabbing;
}

.l9-ocean {
    fill: #fcfcfb;
}

.l9-graticule {
    fill: none;
    stroke: #ecebe6;
    stroke-width: 0.5;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

.l9-map .country {
    stroke: #ffffff;
    stroke-width: 0.5;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
    cursor: pointer;
    transition: opacity 0.15s;
}

.l9-map .country:hover,
.l9-map .country.lit {
    stroke: #0b0b0b;
    stroke-width: 2;
}

.l9-map .country.faded {
    opacity: 0.45;
}

/* Choropleth legend: six class chips with counts, the limits beneath. */

.l9-legend {
    display: grid;
    grid-template-columns: auto;
    gap: 4px;
    margin: 14px 0 4px;
}

.l9-steps,
.l9-ticks {
    display: grid;
    grid-template-columns: repeat(6, 64px);
    gap: 2px;
}

.l9-step {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.l9-chip {
    display: inline-block;
    height: 14px;
    border-radius: 2px;
}

.l9-step .l9-chip {
    width: 100%;
}

.l9-count {
    font-size: 11px;
    color: #52514e;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

.l9-step.empty .l9-count {
    color: #898781;
    font-style: italic;
}

/* Limits sit under the boundaries between chips, so each label is shifted
   left by half its own width and the grid has one more column than chips. */
.l9-ticks {
    grid-template-columns: repeat(7, 64px);
    margin-left: -32px;
}

.l9-ticks span {
    font-size: 11px;
    color: #52514e;
    text-align: center;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.l9-nodata {
    display: flex;
    align-items: center;
    gap: 7px;
    margin-top: 4px;
    font-size: 12px;
    color: #52514e;
}

.l9-chip.hatched {
    width: 22px;
    border: 1px solid #e1e0d9;
    background: repeating-linear-gradient(45deg, #f1f0ec 0 2px, #c9c7bf 2px 3.5px);
}

/* Cartogram */

.l9-morph-end {
    font-size: 12px;
    color: #52514e;
}

#morph {
    flex: 0 1 220px;
    min-width: 140px;
}

.l9-area-note {
    max-width: 68ch;
    margin: 0 0 6px;
    padding: 8px 12px;
    border-left: 3px solid #2a78d6;
    background: #eef4fb;
    font-size: 14px;
}

.l9-area-note.off {
    opacity: 0.4;
}

.l9-labels text {
    fill: #0b0b0b;
    stroke: #ffffff;
    stroke-linejoin: round;
    paint-order: stroke;
    text-anchor: middle;
    pointer-events: none;
    font-weight: bold;
}

.l9-labels text.lit {
    font-size: 13px;
}

.l9-key-bg {
    fill: rgba(252, 252, 251, 0.92);
    stroke: #e1e0d9;
    rx: 4;
}

.l9-key-square {
    fill: #86b6ef;
    stroke: #184f95;
    stroke-width: 1;
}

.l9-key-text {
    font-size: 12px;
    fill: #0b0b0b;
}

/* Table view */

.l9-table {
    width: 100%;
    margin: 0;
    font-size: 13px;
}

.l9-table th {
    position: sticky;
    top: 0;
}

.l9-table th.num,
.l9-table td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.l9-table tbody tr {
    cursor: pointer;
}

.l9-table tbody tr:hover,
.l9-table tbody tr.lit {
    background: #eef4fb;
}

@media (max-width: 640px) {
    .l9-steps {
        grid-template-columns: repeat(6, 44px);
    }

    .l9-ticks {
        grid-template-columns: repeat(7, 44px);
        margin-left: -22px;
    }

    .l9-ticks span {
        font-size: 10px;
    }
}

.l9-locator {
    fill: none;
    stroke: #0b0b0b;
    stroke-width: 1.5;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

/* ---------- Lab 10: voice, gesture and gaze on the Lab 9 maps ---------- */

/*
 * Lab 10 reuses every Lab 9 style for the maps. What it adds is feedback:
 * three input panels, an action log, a toast that follows the reader down
 * the page, a corner dock that shows which sensors are on (with the camera
 * view for gestures), the gaze ring and the calibration screen.
 */

.l10-task-table,
.l10-command-table {
    width: 100%;
    margin: 0;
    font-size: 13px;
}

.l10-task-table td,
.l10-command-table td {
    vertical-align: top;
    line-height: 1.45;
}

.l10-command-table td:first-child {
    white-space: nowrap;
    font-weight: bold;
    color: #0b0b0b;
}

.l10-howto {
    display: grid;
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
    gap: 10px 28px;
}

.l10-howto h3 {
    margin: 14px 0 8px;
    font-size: 16px;
}

.l10-steps {
    margin: 0;
    padding-left: 20px;
    font-size: 14px;
    line-height: 1.55;
}

.l10-note {
    max-width: 75ch;
    font-size: 13px;
    color: #52514e;
}

.l10-privacy {
    max-width: 75ch;
    padding: 8px 12px;
    border-left: 3px solid #c3c2b7;
    background: #f6f6f3;
    font-size: 14px;
}

kbd {
    display: inline-block;
    min-width: 1.4em;
    padding: 0 5px;
    border: 1px solid #c3c2b7;
    border-bottom-width: 2px;
    border-radius: 4px;
    background: #fcfcfb;
    font: 12px/1.6 Arial, Helvetica, sans-serif;
    text-align: center;
}

/* Input panels */

.l10-panels {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 14px;
    margin: 14px 0;
}

.l10-panel {
    position: relative;
    padding: 12px 14px 18px;
    border: 1px solid #e1e0d9;
    border-radius: 6px;
    background: #fcfcfb;
}

.l10-panel h3 {
    margin: 0 0 10px;
    font-size: 16px;
}

.l10-panel-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}

.l10-start {
    padding: 7px 13px;
    border: 1px solid steelblue;
    border-radius: 5px;
    background: steelblue;
    color: white;
    font: inherit;
    font-size: 13px;
    cursor: pointer;
}

.l10-start:hover {
    background: #3a6d96;
}

.l10-start.on {
    border-color: #c0392b;
    background: #fff;
    color: #c0392b;
}

.l10-start:disabled,
.ghost-button:disabled,
.button-set button:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.l10-status {
    min-height: 2.9em;
    margin: 10px 0 6px;
    font-size: 13px;
    line-height: 1.45;
    color: #52514e;
}

.l10-status[data-mode="on"]::before {
    content: "";
    display: inline-block;
    width: 8px;
    height: 8px;
    margin-right: 6px;
    border-radius: 50%;
    background: #c0392b;
    vertical-align: 1px;
}

.l10-status[data-mode="error"] {
    color: #b03a2e;
}

.l10-status[data-mode="off"] {
    color: #898781;
    font-style: italic;
}

.l10-feedback {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 4px 10px;
    margin: 0;
    font-size: 13px;
}

.l10-feedback dt {
    color: #898781;
}

.l10-feedback dd {
    margin: 0;
    color: #0b0b0b;
    overflow-wrap: anywhere;
}

.l10-feedback dd.l10-fail {
    color: #b03a2e;
}

/* How long the current hand pose has been held, out of the time it needs. */
.l10-hold {
    position: absolute;
    left: 14px;
    right: 14px;
    bottom: 8px;
    height: 3px;
    border-radius: 2px;
    background: #ecebe6;
    overflow: hidden;
}

.l10-hold span {
    display: block;
    width: 0;
    height: 100%;
    background: #2a78d6;
}

/* Typed commands and the action log */

.l10-typed {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 10px;
    margin: 4px 0 12px;
}

.l10-typed input {
    flex: 1 1 260px;
    max-width: 420px;
}

.l10-log {
    margin: 8px 0 0;
    padding: 0;
    list-style: none;
    font-size: 13px;
}

.l10-log li {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 8px;
    padding: 5px 0;
    border-bottom: 1px solid #ecebe6;
}

.l10-log .l10-log-empty {
    color: #898781;
    font-style: italic;
}

.l10-time {
    color: #898781;
    font-variant-numeric: tabular-nums;
}

.l10-source {
    padding: 0 6px;
    border-radius: 3px;
    background: #eef4fb;
    color: #1c5cab;
    font-size: 12px;
    font-weight: bold;
    line-height: 1.6;
}

.l10-input {
    color: #0b0b0b;
}

.l10-arrow {
    color: #898781;
}

.l10-log li.l10-fail .l10-result {
    color: #b03a2e;
}

/* The Lab 9 selection bar gains a row of fallback controls above its readout. */

.l10-selection {
    flex-direction: column;
    align-items: stretch;
}

/* Lab 9's readout grows along a row; in this column it must not grow tall. */
.l10-selection .l9-readout {
    flex: none;
}

.l10-bar-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 10px;
}

.l10-bar-buttons button {
    padding: 6px 10px;
}

/* Outside the region shown, the cartogram fades its countries. The rule
   comes after Lab 9's .faded so it wins when both apply. */
.l9-map .country.l10-outside {
    opacity: 0.22;
}

/* Floating feedback: the toast (bottom right) and the sensor dock (bottom left). */

.l10-toast {
    position: fixed;
    right: 16px;
    bottom: 16px;
    z-index: 20;
    display: flex;
    flex-direction: column;
    gap: 3px;
    max-width: min(380px, calc(100vw - 32px));
    padding: 10px 14px;
    border: 1px solid #e1e0d9;
    border-left: 4px solid #2a78d6;
    border-radius: 6px;
    background: #fff;
    box-shadow: 0 6px 18px rgba(11, 11, 11, 0.12);
    font-size: 13px;
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 0.2s, transform 0.2s;
    pointer-events: none;
}

.l10-toast.show {
    opacity: 1;
    transform: none;
}

.l10-toast.l10-fail {
    border-left-color: #b03a2e;
}

.l10-toast .l10-source {
    align-self: flex-start;
}

.l10-toast .l10-input {
    font-weight: bold;
}

.l10-dock {
    position: fixed;
    left: 16px;
    bottom: 16px;
    z-index: 20;
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 8px;
    border: 1px solid #e1e0d9;
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.96);
    box-shadow: 0 6px 18px rgba(11, 11, 11, 0.12);
}

.l10-dock[hidden],
.l10-camera[hidden],
.l10-gaze-cursor[hidden],
.l10-calibration[hidden] {
    display: none;
}

.l10-indicators {
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: 12px;
    font-weight: bold;
    color: #c0392b;
}

.l10-indicators li::before {
    content: "";
    display: inline-block;
    width: 8px;
    height: 8px;
    margin-right: 6px;
    border-radius: 50%;
    background: #c0392b;
    animation: l10-pulse 1.6s ease-in-out infinite;
}

@keyframes l10-pulse {
    50% { opacity: 0.35; }
}

.l10-camera {
    position: relative;
    width: 224px;
    aspect-ratio: 4 / 3;
    overflow: hidden;
    border-radius: 4px;
    background: #0b0b0b;
}

/* Shown mirrored, so a hand moving right moves right on screen. The overlay
   is mirrored with it so the landmarks stay on the hand. */
.l10-camera video,
.l10-camera canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transform: scaleX(-1);
}

.l10-camera-label {
    position: absolute;
    left: 6px;
    right: 6px;
    bottom: 6px;
    padding: 2px 6px;
    border-radius: 3px;
    background: rgba(11, 11, 11, 0.6);
    color: #fff;
    font-size: 12px;
}

.l10-camera-label:empty {
    display: none;
}

/* The gaze estimate: a ring that fills clockwise as the dwell runs. */
.l10-gaze-cursor {
    --p: 0;
    position: fixed;
    z-index: 30;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: conic-gradient(rgba(42, 120, 214, 0.85) calc(var(--p) * 360deg), rgba(42, 120, 214, 0.18) 0);
    -webkit-mask: radial-gradient(circle, transparent 15px, #000 16px);
    mask: radial-gradient(circle, transparent 15px, #000 16px);
    transform: translate(-50%, -50%);
    pointer-events: none;
    transition: left 0.08s linear, top 0.08s linear;
}

/* Calibration screen. WebGazer's own camera preview sits above it, moved
   off the dots into the upper-left quarter. */
.l10-calibration {
    position: fixed;
    inset: 0;
    z-index: 1000;
    background: rgba(252, 252, 251, 0.97);
}

.l10-calibration-text {
    position: absolute;
    left: 72%;
    top: 28%;
    width: min(300px, 40vw);
    transform: translate(-50%, -50%);
    font-size: 14px;
}

.l10-calibration-text p {
    margin: 6px 0 10px;
}

.l10-dot {
    --done: 0;
    position: absolute;
    width: 26px;
    height: 26px;
    padding: 0;
    border: 2px solid #1c5cab;
    border-radius: 50%;
    background: conic-gradient(#2a78d6 calc(var(--done) * 360deg), #fff 0);
    transform: translate(-50%, -50%);
    cursor: crosshair;
}

.l10-dot.done {
    background: #2a78d6;
    border-color: #0d366b;
}

#webgazerVideoContainer {
    z-index: 1001 !important;
    top: 25% !important;
    left: 25% !important;
    transform: translate(-50%, -50%);
}

@media (max-width: 760px) {
    .l10-howto,
    .l10-panels {
        grid-template-columns: minmax(0, 1fr);
    }

    .l10-camera {
        width: 160px;
    }
}
