/*
 * Shared component styling.
 *
 * Both server-rendered services load this. Every colour here is a var(--token)
 * from tokens.css — a raw hex in this file would be a colour that no theme can
 * change, which is exactly what the token system exists to prevent.
 */

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: var(--font-size);
  line-height: var(--line-height);
}

:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

a {
  color: inherit;
}

/* A link inside running text is the accent; a link that is a whole card or a
   navigation entry inherits and is styled where it is used. */
.prose a,
.linked a,
a.link {
  color: var(--accent);
  text-underline-offset: 0.18em;
}

.prose a:hover,
.linked a:hover,
a.link:hover {
  color: var(--accent-deep);
}

/* --- the stripe: violet running into blue, decoration only ---
 *
 * The band is a plain gradient. The pulse cut out of it is a mask rather than
 * a drawing, so the colour stays the tokens' — the mask says only where the
 * band is, never what colour it is — and a browser that does not apply it
 * shows a solid band, which is still the right band.
 *
 * It is the one figurative thing on the page: a line that was flat and is not
 * any more.
 */
.stripe {
  height: 12px;
  background: linear-gradient(90deg, var(--stripe-from), var(--stripe-to));
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='12' viewBox='0 0 96 12'%3E%3Cpath d='M0 0h96v5H62l-5 7-8-12-6 9-4-4H0z'/%3E%3C/svg%3E") repeat-x left top / 96px 12px;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='12' viewBox='0 0 96 12'%3E%3Cpath d='M0 0h96v5H62l-5 7-8-12-6 9-4-4H0z'/%3E%3C/svg%3E") repeat-x left top / 96px 12px;
}

/* The same band at the foot of the page, the other way up. */
.stripe.foot {
  transform: scaleY(-1);
}

/* --- header --- */
.masthead {
  background: var(--band);
  color: var(--on-band);
}

.masthead-inner {
  max-width: var(--page);
  margin: 0 auto;
  padding: 1rem var(--gutter);
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: center;
  justify-content: space-between;
}

.masthead-identity {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

/* The default mark is drawn in currentColor, so it follows the palette
   without being an asset at all; an uploaded logo replaces it. */
.masthead-identity .logo {
  width: 40px;
  height: 40px;
  color: var(--on-band);
  flex: none;
}

.masthead-controls {
  display: flex;
  gap: 0.75rem;
  align-items: center;
}

/* The name carries the joke, so the type has to: three letters set light and
   four set heavy, in one word. */
.wordmark {
  font-size: 1.5rem;
  font-weight: 400;
  letter-spacing: var(--title-tracking);
  text-decoration: none;
}

.wordmark strong {
  font-weight: var(--title-weight);
}

.tagline {
  font-size: 0.875rem;
  opacity: 0.8;
}

.languages {
  display: flex;
  gap: 0.5rem;
  font-size: 0.8125rem;
}

.languages {
  margin: 0;
}

.languages button {
  font: inherit;
  color: inherit;
  background: transparent;
  cursor: pointer;
  padding: 0.125rem 0.5rem;
  border: 1px solid color-mix(in srgb, var(--on-band) 45%, transparent);
  border-radius: var(--radius);
}

.languages button[aria-pressed="true"] {
  background: var(--on-band);
  color: var(--band);
  border-color: var(--on-band);
}

/* --- theme toggle --- */
.theme-toggle {
  font: inherit;
  font-size: 1rem;
  line-height: 1;
  padding: 0.25rem 0.5rem;
  color: var(--on-band);
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--on-band) 45%, transparent);
  border-radius: var(--radius);
  cursor: pointer;
}

.theme-toggle:hover {
  background: color-mix(in srgb, var(--on-band) 15%, transparent);
}

/* --- navigation --- */
.nav {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.nav-inner {
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 var(--gutter);
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}

.nav a {
  padding: 0.75rem 0.875rem;
  text-decoration: none;
  color: var(--text-muted);
  border-bottom: 3px solid transparent;
  font-size: 0.9375rem;
}

.nav a:hover {
  color: var(--text);
}

.nav a[aria-current="page"] {
  color: var(--text);
  border-bottom-color: var(--accent);
  font-weight: 600;
}

/* Pushes what follows to the far end of the row: the account entries sit
   apart from the pages everybody reads. */
.nav-spacer {
  flex: 1;
}

/* Signing out is a form rather than a link — a change of state should not be
 * something a prefetcher can trigger — so its button has to be talked back
 * into looking like the links it sits among. */
.nav-form {
  display: contents;
}

/* Matched to `.nav a` above rather than left to a browser's button defaults:
 * it sits in the same row and has to read as one of the same things. */
.nav-button {
  padding: 0.75rem 0.875rem;
  font: inherit;
  font-size: 0.9375rem;
  color: var(--text-muted);
  background: none;
  border: 0;
  border-bottom: 3px solid transparent;
  cursor: pointer;
}

.nav-button:hover {
  color: var(--text);
}

/* --- layout --- */
main {
  max-width: var(--page);
  margin: 0 auto;
  padding: var(--space-lg) var(--gutter) 4rem;
}

.prose {
  max-width: var(--measure);
}

/* Somebody's own paragraphs: the line breaks they typed are kept rather than
   collapsed into one block they did not write. */
.body-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin: 1.5rem 0;
}

/* --- two columns, where a page has a second thing worth seeing at once --- */

/* A reading column and whatever belongs beside it. The first track is the
   measure and the second takes the rest, so the text keeps the line length it
   is set for however wide the page gets.
 *
 * It collapses to one column well before the columns would get too narrow to
 * read: two cramped columns are worse than one comfortable one.
 */
.split {
  display: grid;
  grid-template-columns: minmax(0, var(--measure)) minmax(0, 1fr);
  gap: var(--space-lg);
  align-items: start;
}

@media (max-width: 1100px) {
  .split {
    grid-template-columns: minmax(0, 1fr);
  }
}

.split > .prose {
  max-width: none;
}

/* The top of a column lines up with the top of what it sits beside, rather
   than carrying the gap a heading or a label needs when it follows a
   paragraph. */
.split-aside > h2:first-child,
.split-aside > label:first-child,
.split > .prose > label:first-child {
  margin-top: 0;
}

h1 {
  font-size: 2rem;
  line-height: 1.2;
  margin: 0 0 0.5rem;
  letter-spacing: var(--title-tracking);
}

h2 {
  font-size: 1.25rem;
  margin: 2.5rem 0 0.75rem;
}

h3 {
  font-size: 1rem;
  margin: 1.5rem 0 0.5rem;
}

.lede {
  font-size: 1.125rem;
  color: var(--text-muted);
  margin-top: 0;
}

.section-note {
  color: var(--text-muted);
  font-size: 0.9375rem;
}

/* A heading with something to do beside it: "Topics" and "new topic". */
.heading-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
  margin: 2.5rem 0 0.75rem;
}

.heading-row h1,
.heading-row h2 {
  margin: 0;
}

.heading-row .button {
  margin-top: 0;
}

/* Where a page sits: collective, then topic, then this. */
.crumbs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
  font-size: 0.875rem;
  color: var(--text-muted);
}

.crumbs li + li::before {
  content: "›";
  margin-right: 0.375rem;
}

/* --- two equal columns ---
 *
 * The landing page puts the calendar and the news side by side under the map.
 * Equal, because neither is the secondary one; stacked below a tablet's width,
 * where two columns of cards would each be too narrow to read.
 */
.columns {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-lg);
  align-items: start;
  margin-top: 1rem;
}

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

/* --- the counts above the map ---
 *
 * How many there are is the argument: one cut is a misfortune, four hundred on
 * one map is a policy. So the numbers are set as the largest type on the page
 * and are of everything, whatever the map below happens to be showing.
 */
.stats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 2.5rem;
  margin: 1.5rem 0 0;
  padding: 0;
  list-style: none;
}

.stat-value {
  display: block;
  font-size: 2rem;
  font-weight: var(--title-weight);
  line-height: 1.1;
  letter-spacing: var(--title-tracking);
  font-variant-numeric: tabular-nums;
}

.stat-label {
  font-size: 0.8125rem;
  color: var(--text-muted);
}

/* --- cards --- */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--grid-min), 1fr));
  gap: var(--space-md);
  margin-top: 1rem;
}

.card {
  background: var(--surface);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
  padding: var(--card-padding-y) var(--card-padding-x);
}

/* Every card in a grid the same height, with whatever sits at the foot of one
   pushed to the bottom, so two cards side by side end on the same line however
   much text is above. A listing that looks like a ransom note is one nobody
   scans. */
.grid > .card {
  display: flex;
  flex-direction: column;
}

.grid > .card > .card-foot {
  margin-top: auto;
}

.card-meta {
  font-size: 0.8125rem;
  color: var(--text-muted);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem 0.75rem;
  margin-bottom: 0.5rem;
}

.card-title {
  font-size: 1.0625rem;
  line-height: 1.35;
  margin: 0 0 0.375rem;
  font-weight: 700;
}

/* The title is the link, so it reads as a title that happens to be clickable:
   the colour of the words around it, and an underline only when somebody is
   pointing at it or has tabbed to it. */
.card-title a {
  text-decoration: none;
}

.card-title a:hover {
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-underline-offset: 0.2em;
}

.card-text {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.9375rem;
}

/* Says the text was cut, in the place where it was cut. */
.card-more {
  color: var(--text-muted);
  white-space: nowrap;
}

.card-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  padding-top: 0.75rem;
  margin-top: 0.875rem;
  border-top: 1px solid var(--border);
  font-size: 0.8125rem;
  color: var(--text-muted);
}

/* An unread notification. A left edge rather than a background: the card is
   already a surface, and a second one inside it flattens the type. */
.card.unread {
  border-left: 3px solid var(--accent);
}

/* --- the money ---
 *
 * The figure is why most people came: "what are they taking, and how much".
 * It is set large and in the accent, with a real minus sign in front of it,
 * because a cut is a subtraction and the page should say so.
 */
.amount {
  font-size: 1.5rem;
  font-weight: var(--title-weight);
  line-height: 1.15;
  letter-spacing: var(--title-tracking);
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  margin: 0 0 0.25rem;
}

.amount.large {
  font-size: 2.5rem;
}

/* --- tags: what kind of thing a card is --- */

/* A tag is a fact about a card, not a control. Outlined rather than filled: a
   listing with twelve filled tags in it reads as twelve warnings. */
.tag {
  display: inline-block;
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 0.0625rem 0.375rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text-muted);
  white-space: nowrap;
}

/* The three kinds of thing on the map and in a listing carry their marker's
   colour as a left edge, so a card and its pin are recognisably the same
   thing. */
.tag.topic {
  border-left: 3px solid var(--mark-topic);
}

.tag.action {
  border-left: 3px solid var(--mark-action);
}

.tag.collective {
  border-left: 3px solid var(--mark-collective);
}

/* --- pills: the state of a row --- */
.pill {
  display: inline-block;
  font-size: 0.75rem;
  padding: 0.05rem 0.375rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text-muted);
  white-space: nowrap;
}

/* Only for the answer that is good news — published, a synced passkey, the
   device you are holding. */
.pill.ok {
  color: var(--on-accent);
  background: var(--accent);
  border-color: var(--accent);
}

/* Called off. The one state a reader must not miss, so it is the one pill
   that is loud. */
.pill.alert {
  color: var(--error);
  border-color: var(--error);
  font-weight: 700;
}

/* An action that is not happening keeps its place in every listing and says
   so there: struck through, with the word beside it. */
.cancelled > h1,
.cancelled .card-title,
.cancelled .agenda-title {
  text-decoration: line-through;
  text-decoration-color: var(--error);
}

/* --- the feed: news, newest first --- */
.feed {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin: 1rem 0 0;
  padding: 0;
  list-style: none;
}

.feed .card {
  border-left: 3px solid var(--mark-topic);
}

/* --- the agenda: actions in the order they happen --- */
.agenda {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin: 1rem 0 0;
  padding: 0;
  list-style: none;
}

/* A day set off in a list of several: the date once, then what happens on it. */
.agenda-day {
  margin: 1.75rem 0 0.25rem;
  font-size: 0.8125rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}

.agenda-day:first-child {
  margin-top: 0.5rem;
}

.agenda-item {
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 1fr);
  gap: 0.875rem;
  align-items: start;
}

/* The date as an object: the day large, the month above it. It is what the eye
   finds first in a list of things to go to, before any title. */
.datebox {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0.25rem 0 0.375rem;
  border: 1px solid var(--border);
  border-top: 4px solid var(--mark-action);
  border-radius: var(--radius);
  background: var(--bg);
  line-height: 1.1;
  text-align: center;
}

.datebox-month {
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}

.datebox-day {
  font-size: 1.5rem;
  font-weight: var(--title-weight);
  font-variant-numeric: tabular-nums;
}

.agenda-title {
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.35;
  margin: 0 0 0.125rem;
}

.agenda-title a {
  text-decoration: none;
}

.agenda-title a:hover {
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-underline-offset: 0.2em;
}

/* --- the calendar: a month, a week to a row ---
 *
 * A grid of days for the people who think in weeks — "is anything on the
 * Saturday?" — beside the agenda, which is for the people who think in what is
 * next. Below a phone's width it is hidden and the agenda carries the page:
 * seven columns on a narrow screen are seven columns nobody can read.
 */
.calendar-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin: 1.5rem 0 0.75rem;
}

.calendar-bar h2 {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.calendar-bar .button {
  margin-top: 0;
}

.calendar {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  border-top: 1px solid var(--border);
  border-left: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
}

.calendar-weekday {
  padding: 0.375rem 0.5rem;
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  background: var(--bg);
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.calendar-day {
  min-height: 6.5rem;
  padding: 0.375rem 0.5rem 0.5rem;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  min-width: 0;
}

/* The days of the months either side, which fill out the first and last week.
   There, so the grid is a grid; quiet, so nobody mistakes them for this month. */
.calendar-day.outside {
  background: var(--bg);
  color: var(--text-muted);
}

.calendar-date {
  display: inline-block;
  min-width: 1.625rem;
  font-size: 0.8125rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: center;
  border-radius: 999px;
}

.calendar-day.today .calendar-date {
  background: var(--accent);
  color: var(--on-accent);
}

.calendar-entry {
  display: block;
  margin-top: 0.25rem;
  padding: 0.125rem 0.375rem;
  font-size: 0.75rem;
  line-height: 1.35;
  text-decoration: none;
  border-left: 3px solid var(--mark-action);
  background: color-mix(in srgb, var(--mark-action) 12%, transparent);
  border-radius: 0 3px 3px 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.calendar-entry:hover {
  background: color-mix(in srgb, var(--mark-action) 24%, transparent);
}

.calendar-entry.cancelled {
  text-decoration: line-through;
  border-left-color: var(--error);
  background: transparent;
  color: var(--text-muted);
}

@media (max-width: 760px) {
  .calendar {
    display: none;
  }
}

/* --- the organisations in a collective --- */
.members {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: var(--space-sm);
  margin: 1rem 0 0;
  padding: 0;
  list-style: none;
}

.member {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 1rem 0.75rem;
  text-align: center;
  background: var(--surface);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
}

a.member:hover {
  border-color: var(--accent);
}

.member-name {
  font-weight: 600;
  line-height: 1.3;
}

/* A logo is somebody else's artwork on our page: contained, never cropped, and
   on a light ground of its own in both modes, because most logos were drawn
   for white and a dark theme would otherwise swallow half of them. */
.logo-box {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 5.5rem;
  height: 5.5rem;
  padding: 0.5rem;
  background: var(--logo-ground);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  flex: none;
}

.logo-box img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.logo-box.small {
  width: 2.75rem;
  height: 2.75rem;
  padding: 0.25rem;
}

/* No logo uploaded: the first letter, set as a mark, rather than a broken
   image or a gap that makes the row look unfinished. */
.logo-box.initial {
  /* Our own letter, not somebody's artwork: it sits on the page's surface
     like everything else, where the accent it is drawn in is legible. */
  background: var(--surface);
  font-size: 2rem;
  font-weight: var(--title-weight);
  color: var(--accent);
  text-transform: uppercase;
}

.logo-box.small.initial {
  font-size: 1.125rem;
}

/* The head of a collective's or a topic's page: what it is, beside its mark. */
.profile {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  align-items: flex-start;
}

.profile-body {
  flex: 1;
  min-width: 16rem;
}

/* Follow, attend, the calendar file: the things a reader can do with a page,
   in one row under its heading. */
.actions-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.625rem 1rem;
  margin-top: 1rem;
}

.actions-row form {
  display: contents;
}

.actions-row .button {
  margin-top: 0;
}

/* A plain list of facts about a thing: when, where, who. */
.facts {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 0.375rem 1rem;
  margin: 1rem 0 0;
}

.facts dt {
  color: var(--text-muted);
  font-size: 0.875rem;
}

.facts dd {
  margin: 0;
}

/* --- forms and buttons --- */
label {
  display: block;
  font-weight: 600;
  margin: 1.25rem 0 0.375rem;
}

.hint {
  font-weight: 400;
  color: var(--text-muted);
  font-size: 0.875rem;
}

p.hint {
  margin: 0.375rem 0 0;
}

input[type="text"],
input[type="url"],
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="number"],
input[type="email"],
input[type="file"],
input[type="search"],
select,
textarea {
  width: 100%;
  padding: 0.625rem 0.75rem;
  font: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

textarea {
  min-height: 14rem;
  resize: vertical;
}

textarea.short {
  min-height: 5rem;
}

/* A rejected form and a saved one say so in the same place, above the fields,
   so the answer is where the person is already looking rather than wherever
   the browser happened to leave the page. */
.form-error,
.form-notice {
  padding: 0.75rem 1rem;
  border-radius: var(--radius);
  border: 1px solid;
  font-weight: 600;
}

.form-error {
  color: var(--error);
  border-color: var(--error);
}

.form-notice {
  color: var(--good);
  border-color: var(--good);
}

/* A date or a time field is a row of small targets — 23 / 09 / 2026 — so it is
   set larger than the text fields around it.
 *
 * **The popup it opens is not ours and cannot be made to match.** The calendar
 * is a platform control drawn outside the document. Keeping it is a decision
 * rather than a defeat: the native picker is keyboard- and screen-reader-
 * accessible on every platform for free, and on a phone — which is where most
 * people will announce a demonstration from — it is a great deal better than
 * anything this project would write.
 */
.datelike {
  font-size: 1.125rem;
  padding: 0.75rem;
  max-width: 20rem;
}

.datelike::-webkit-calendar-picker-indicator {
  width: 1.25rem;
  height: 1.25rem;
  cursor: pointer;
}

input[type="number"] {
  max-width: 16rem;
}

/* Fields that belong together, side by side while there is room. */
.field-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0 var(--space-md);
}

/* A group of fields with a name: where it happens, who may edit. */
.fieldset {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0 1rem 1rem;
  margin: 1.5rem 0 0;
  min-width: 0;
}

.fieldset legend {
  font-weight: 600;
  padding: 0 0.375rem;
}

/* The map an editor aims a pin on. Shorter than the public one: it is one
   field of a form, not the page. */
.fieldset .map,
.picker {
  height: 20rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-top: 0.75rem;
  background: var(--surface);
}

/* Destructive things live in a box of their own at the foot of a page, set
   apart from the save button somebody is reaching for. */
.danger-zone {
  margin-top: 3rem;
  padding: 0 1rem 1rem;
  border: 1px solid var(--error);
  border-radius: var(--radius);
}

.danger-zone legend {
  color: var(--error);
  font-weight: 600;
  padding: 0 0.375rem;
}

.checkbox {
  display: flex;
  gap: 0.625rem;
  align-items: flex-start;
  margin-top: 1.5rem;
  font-weight: 400;
}

.checkbox input {
  margin-top: 0.3rem;
}

.button {
  display: inline-block;
  margin-top: 1.5rem;
  padding: 0.75rem 1.5rem;
  font: inherit;
  font-weight: 700;
  color: var(--on-accent);
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  cursor: pointer;
  text-decoration: none;
}

.button:hover {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
}

.button.secondary {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
}

.button.secondary:hover {
  border-color: var(--accent);
  background: var(--surface);
}

.button.small {
  margin-top: 0;
  padding: 0.35rem 0.75rem;
  font-size: 0.875rem;
}

.button.danger {
  background: var(--surface);
  color: var(--error);
  border: 1px solid var(--border);
}

.button.danger:hover {
  border-color: var(--error);
  background: var(--surface);
}

/* Already done: following, coming. The button that undoes it is the quiet one,
   and it says what the state is rather than what pressing it would do — the
   same control, read as a fact. */
.button.active {
  background: var(--surface);
  color: var(--accent);
  border-color: var(--accent);
}

/* A button carrying an icon and a word. The gap is what stops the two reading
   as one smudge at small sizes. */
.button .icon {
  display: inline-block;
  vertical-align: -0.15em;
  margin-right: 0.375rem;
}

.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.625rem;
  align-items: center;
  margin-top: 1.5rem;
}

.button-row .button {
  margin-top: 0;
}

/* --- filters: a row of choices above a listing or a map --- */
.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 1rem 0 0;
}

.filters form {
  display: contents;
}

/* A choice that is on or off. A link, when choosing it loads a page; a label
   around a checkbox, when it only redraws the map. Both look the same because
   to the person pressing it they are the same. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  margin: 0;
  padding: 0.25rem 0.75rem;
  font-size: 0.875rem;
  font-weight: 400;
  color: var(--text-muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  text-decoration: none;
  cursor: pointer;
  user-select: none;
}

.chip:hover {
  border-color: var(--accent);
  color: var(--text);
}

.chip[aria-current="true"],
.chip:has(input:checked) {
  color: var(--text);
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
  font-weight: 600;
}

/* The checkbox is still there for the keyboard and the screen reader; the chip
   around it is what is drawn. */
.chip input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.chip:has(input:focus-visible) {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* The shape a chip's layer is drawn with on the map, beside its name. */
.chip .pin {
  position: static;
  flex: none;
}

/* --- data tables --- */

/* A table is the one thing on the page allowed to be wider than the page, so
   it gets its own scroller rather than the body acquiring one. */
.table-wrap {
  overflow-x: auto;
  margin-top: 1rem;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
}

.data-table th,
.data-table td {
  padding: 0.625rem 0.75rem;
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--border);
}

.data-table th {
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

/* Each control in a row is its own form post, and nothing about that should
   show: the forms collapse to nothing so the buttons sit where they look like
   they belong. */
.data-table form {
  display: contents;
}

.data-table .button {
  margin-top: 0;
}

/* The actions column is as wide as its buttons and no wider, so the title
   gets the rest of the page. */
.actions-column {
  width: 1%;
  white-space: nowrap;
}

/* Side by side and never wrapping: two controls stacked on top of each other
   read as two rows, which is exactly what a table must not do. */
.row-actions {
  display: flex;
  gap: 0.375rem;
  align-items: center;
}

/* An identifier is data, not prose: breakable anywhere, so a long one widens
   nothing. */
.data-table .mono,
.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.875rem;
  overflow-wrap: anywhere;
}

/* A square control carrying one icon. The label lives in aria-label and
   title rather than on screen, so the hit area has to be a comfortable size
   on its own. */
.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  color: var(--text-muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}

.icon-button:hover {
  color: var(--text);
  border-color: var(--text-muted);
}

/* Destructive, so it says so before it is pressed rather than only in the
   confirmation afterwards. */
.icon-button.danger:hover {
  color: var(--error);
  border-color: var(--error);
}

.icon {
  display: block;
}

/* A field and its own submit button on one line, for renaming a device in
   place. */
.inline-form {
  display: flex;
  align-items: center;
  gap: 0.375rem;
}

.inline-form input {
  margin: 0;
  min-width: 0;
}

.pagination {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin: 1.5rem 0;
}

.pagination .button {
  margin-top: 0;
}

/* Visually hidden, still read aloud. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* A credential shown once. It has to survive being read off a screen and
   retyped, so it wraps at every character rather than running off the page. */
.receipt-value {
  display: inline-block;
  max-width: 100%;
  padding: 0.5rem 0.625rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 0.875rem;
  overflow-wrap: anywhere;
}

/* Recovery codes, shown once. One per line, because they are copied onto
   paper by hand by somebody who has just lost their phone. */
.code-list {
  list-style: none;
  margin: 0.75rem 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: 0.375rem;
}

.code-list li {
  margin: 0;
}

/* --- map --- */
#map,
.map {
  height: 620px;
  max-width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

/* A map that illustrates one thing — where this action is — rather than being
   the page. */
.map.small {
  height: 320px;
  margin-top: 1rem;
}

@media (max-width: 640px) {
  h1 {
    font-size: 1.5rem;
  }
  #map,
  .map {
    height: 420px;
  }
}

/* Leaflet draws its own chrome in fixed light colours. The popup is the one
   part a reader spends time in, so it takes the theme. */
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
  background: var(--surface);
  color: var(--text);
}

.leaflet-container a.leaflet-popup-close-button {
  color: var(--text-muted);
}

.leaflet-container {
  font-family: var(--font-ui);
}

/* What a marker says when it is opened: the same card as the listings, at the
   width a popup allows. */
.map-popup {
  min-width: 14rem;
  max-width: 20rem;
}

.map-popup .card-meta {
  margin: 0 0 0.35rem;
}

.map-popup .card-title {
  font-size: 1rem;
}

.map-popup .card-title a {
  color: inherit;
}

.map-popup .amount {
  font-size: 1.25rem;
}

.map-popup .card-text {
  margin: 0;
}

/* --- markers ---
 *
 * Three kinds of thing are on the map and each is a colour **and a shape**:
 * a topic is a diamond, an action a circle, a collective a square. The shape
 * is the part that matters — somebody who cannot tell violet from blue, or is
 * looking at the map in sunlight, reads the outline. The glyph inside says the
 * same thing a third way.
 */
.pin {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  font-size: 13px;
  font-weight: 800;
  line-height: 1;
  color: var(--on-mark);
  border: 2px solid var(--surface);
  box-shadow: 0 1px 4px rgb(0 0 0 / 35%);
}

/* The glyph sits in a child so the diamond can be turned without turning it. */
.pin > span {
  display: block;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

.pin.topic {
  background: var(--mark-topic);
  transform: rotate(45deg);
  border-radius: 3px;
}

.pin.topic > span {
  transform: rotate(-45deg);
}

.pin.action {
  background: var(--mark-action);
  border-radius: 50%;
}

.pin.collective {
  background: var(--mark-collective);
  border-radius: 4px;
}

/* In a legend or a chip a marker is an illustration, drawn smaller. */
.pin.legend {
  width: 14px;
  height: 14px;
  font-size: 0;
  border-width: 1px;
  box-shadow: none;
}

/* The cluster count is how many things are in a place — the one figure the
   map is there to give — so it takes the accent rather than the plugin's
   default green and yellow, which belong to no theme here. */
.marker-cluster-small div,
.marker-cluster-medium div,
.marker-cluster-large div {
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
}

.marker-cluster-small,
.marker-cluster-medium,
.marker-cluster-large {
  background: color-mix(in srgb, var(--accent) 45%, transparent);
}

/* Under the map: how much of everything is in view, in words. */
.map-status {
  margin: 0.5rem 0 0;
  font-size: 0.875rem;
  color: var(--text-muted);
  min-height: 1.4em;
}

/* --- empty state --- */
.empty {
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 2rem;
  text-align: center;
  color: var(--text-muted);
}

/* --- page footer --- */
body > footer {
  border-top: 1px solid var(--border);
  background: var(--surface);
}

.footer-inner {
  max-width: var(--page);
  margin: 0 auto;
  padding: 1.5rem var(--gutter);
  color: var(--text-muted);
  font-size: 0.875rem;
}

.footer-inner a {
  color: inherit;
}

/* --- console-only --- */
.warning {
  color: var(--warn);
  border: 1px solid var(--warn);
  font-weight: 600;
  padding: 0.75rem 1rem;
  border-radius: var(--radius);
  margin: 0 0 1.5rem;
}

/* Who is signed in, at the end of the console's navigation row. */
.nav-identity {
  padding: 0.75rem 0.875rem;
  font-size: 0.875rem;
  color: var(--text-muted);
}

/* The organisation picker: a text field, and the matches in a list that
   opens below it while somebody types. Above the map, which sits right
   under one of them and draws its own panes high. */
.combo {
  position: relative;
  margin-bottom: 1rem;
}

.combo-list {
  position: absolute;
  z-index: 1100;
  top: calc(100% + 0.25rem);
  left: 0;
  right: 0;
  max-height: 20rem;
  overflow-y: auto;
  margin: 0;
  padding: 0.25rem;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 0.5rem 1.5rem rgb(0 0 0 / 0.18);
}

.combo-list li {
  display: flex;
  flex-direction: column;
  padding: 0.5rem 0.625rem;
  border-radius: calc(var(--radius) / 2);
  cursor: pointer;
}

.combo-list li[aria-selected="true"] {
  background: var(--accent);
  color: var(--on-accent);
}

.combo-detail {
  font-size: 0.8125rem;
  opacity: 0.75;
}

.combo-list li.combo-empty {
  cursor: default;
  color: var(--text-muted);
}

.combo-input {
  width: 100%;
  padding: 0.625rem 0.75rem;
  font: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.picker-search {
  margin-bottom: 0.375rem;
}

/* A list of names with no bullets and some air between them. */
.plain-list {
  list-style: none;
  padding: 0;
  margin: 0 0 1.5rem;
}

.plain-list li {
  padding: 0.375rem 0;
  border-bottom: 1px solid var(--border);
}

/* A small logo beside a name, in a table row. */
.profile-inline {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
}

/* A web address in a table cell breaks anywhere rather than widening the
   table past the screen. */
td.wrap {
  overflow-wrap: anywhere;
}

/* --- theme library manager --- */
.theme-bar {
  display: flex;
  gap: var(--space-sm);
  flex-wrap: wrap;
  align-items: center;
  margin: 1.5rem 0 0.5rem;
}

.theme-bar .button {
  margin-top: 0;
}

.themes {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-top: 1rem;
}

.theme-card {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

.theme-card.active {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.theme-name {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
  flex: 1;
  min-width: 12rem;
}

.badge {
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
  padding: 0.125rem 0.375rem;
  border-radius: 3px;
  color: var(--on-accent);
  background: var(--accent);
}

.badge.builtin {
  color: var(--text-muted);
  background: transparent;
  border: 1px solid var(--border);
}

.warnpill {
  font-size: 0.75rem;
  color: var(--warn);
  border: 1px solid var(--warn);
  border-radius: 3px;
  padding: 0.05rem 0.375rem;
  cursor: help;
}

.swatches {
  display: flex;
  gap: 4px;
}

.swatch {
  width: 18px;
  height: 18px;
  border-radius: 4px;
  border: 1px solid var(--border);
}

.theme-actions {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
}

/* --- theme images --- */
.theme-assets {
  flex-basis: 100%;
  border-top: 1px solid var(--border);
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.asset-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.asset-label {
  flex: 1;
  min-width: 14rem;
  display: flex;
  flex-direction: column;
}

.asset-slot {
  font-weight: 600;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.875rem;
}

/* One library per section on the theme page. */
.library + .library {
  margin-top: var(--space-lg);
}

/* A structure previews as its values, where a palette previews as chips. */
.sample {
  font-size: 0.6875rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 0.125rem 0.375rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 14rem;
}

/* The colourway a theme is shown in, chosen on its row. */
.theme-colors {
  width: auto;
  min-width: 9rem;
  padding: 0.3rem 0.5rem;
  font-size: 0.875rem;
}
