/* Board-only tokens. Core palette and light/dark surfaces live in tokens.css. */
:root {
  --board-content-max: 100rem;
  --board-gutter: clamp(var(--space-16), 3vw, var(--space-48));
  --board-section-gap: clamp(var(--space-24), 3vw, var(--space-48));
  --board-panel-padding: clamp(var(--space-16), 2vw, var(--space-32));
  --board-spot-padding: clamp(var(--space-16), 2.4vw, var(--space-32));
  --board-title-size: clamp(var(--text-24), 2.8vw, var(--text-48));
  --board-section-title-size: clamp(var(--text-18), 2vw, var(--text-32));
  --board-price-size: clamp(var(--text-32), 4.2vw, var(--text-48));
  --board-price-primary-size: clamp(var(--text-32), 5vw, 4.5rem);
  --board-price-currency-size: clamp(var(--text-18), 2.6vw, var(--text-32));
  --board-table-value-size: clamp(var(--text-14), 1.4vw, var(--text-18));
  --board-control-height: var(--text-48);
  --board-action-min-width: 10rem;
  --board-status-dot-size: var(--space-12);
  --board-border: 0.0625rem;
  --board-border-strong: 0.125rem;
  --board-focus-offset: var(--space-4);
  --board-table-min-width: 48rem;
  --board-modal-max: 42rem;
  --board-icon-size: var(--text-24);
  --board-z-modal: 10;
  /* Matches PRICE_TWEEN_MS in board.js: short enough to rest between one-second ticks. */
  --board-tween-duration: 350ms;
}

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

html,
body {
  min-height: 100%;
}

body {
  min-width: 20rem;
  margin: 0;
  background: var(--theme-page);
  color: var(--theme-text);
  font-family: var(--font-body);
  font-variant-numeric: tabular-nums lining-nums;
  transition:
    background-color var(--transition-standard),
    color var(--transition-standard);
}

button {
  font: inherit;
}

button:focus-visible,
.rate-table-wrap:focus-visible {
  outline: var(--board-border-strong) solid var(--gfe-experimental);
  outline-offset: var(--board-focus-offset);
}

.app-container {
  display: flex;
  flex-direction: column;
  width: min(100%, var(--board-content-max));
  min-height: 100vh;
  margin: 0 auto;
  padding: var(--board-gutter);
}

.header,
.header__controls,
.footer,
.footer-info,
.footer-actions,
.spot-card__head,
.section-heading,
.connection-status,
.theme-toggle,
.share-modal-header,
.share-actions-grid,
.detail-card {
  display: flex;
  align-items: center;
}

.header,
.section-heading,
.footer {
  justify-content: space-between;
}

.header {
  flex-wrap: wrap;
  gap: var(--space-16);
  padding-bottom: var(--space-24);
  border-bottom: var(--board-border) solid var(--theme-border);
}

.brand__eyebrow,
.section-heading__eyebrow,
.detail-card__eyebrow,
.spot-card__label,
.rate-table thead th,
.ticker-source,
.board-announcement__label {
  margin: 0;
  font-size: var(--text-12);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.brand__eyebrow,
.section-heading__eyebrow,
.detail-card__eyebrow,
.spot-card__unit,
.section-heading__note,
.sub-brand,
.spot-card__meta,
.is-empty-value,
.rate-table__note,
.footer,
.placeholder-marker {
  color: var(--theme-text-muted);
}

.brand h1 {
  max-width: 26ch;
  margin: var(--space-4) 0 0;
  color: var(--theme-text);
  font-size: var(--board-title-size);
  font-weight: var(--weight-bold);
  letter-spacing: 0.03em;
  line-height: 1.1;
  text-transform: uppercase;
}

.sub-brand {
  display: block;
  margin-top: var(--space-8);
  font-size: var(--text-14);
  line-height: 1.5;
}

.header__controls {
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-8);
}

.header__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
}

.connection-status {
  gap: var(--space-8);
  min-height: var(--board-control-height);
  padding: var(--space-8) var(--space-12);
  border: var(--board-border-strong) solid var(--theme-control-border);
  border-radius: var(--radius-card);
  background: var(--theme-surface);
  color: var(--theme-text);
  font-size: var(--text-14);
  font-weight: var(--weight-semibold);
}

.status-dot {
  display: grid;
  flex: 0 0 var(--board-status-dot-size);
  width: var(--board-status-dot-size);
  height: var(--board-status-dot-size);
  place-items: center;
  border: var(--board-border-strong) solid currentColor;
  border-radius: var(--radius-pill);
}

.status-dot::after {
  content: 'i';
  font-size: var(--text-12);
  font-weight: var(--weight-bold);
  line-height: 1;
}

.status-copy {
  display: grid;
  gap: var(--space-4);
}

.status-context {
  display: none;
  font-size: var(--text-12);
  font-weight: var(--weight-medium);
}

.connection-status.status-green {
  border-color: var(--gfe-success);
  background: var(--gfe-success);
  color: var(--gfe-bg);
}

.connection-status.status-green .status-dot {
  background: var(--gfe-bg);
  color: var(--gfe-success);
}

.connection-status.status-green .status-dot::after {
  content: '✓';
}

.connection-status.status-amber {
  border-width: var(--board-border-strong);
  border-color: var(--gfe-alert);
  border-style: dashed;
  background: var(--gfe-alert);
  color: var(--gfe-bg);
}

.connection-status.status-amber .status-dot {
  background: var(--gfe-bg);
  color: var(--gfe-alert);
}

.connection-status.status-amber .status-dot::after {
  content: '!';
}

.connection-status.status-amber .status-context--delayed {
  display: block;
}

.connection-status.status-red {
  border-color: var(--gfe-alert);
  background: var(--gfe-alert);
  color: var(--gfe-bg);
}

.connection-status.status-red .status-dot {
  background: var(--gfe-bg);
  color: var(--gfe-alert);
}

.connection-status.status-red .status-dot::after {
  content: '×';
}

.connection-status.status-red .status-context--offline {
  display: block;
}

.connection-status.status-closed {
  border-color: var(--theme-control-border);
  background: var(--theme-surface-strong);
  color: var(--theme-text-on-strong);
}

.connection-status.status-closed .status-dot {
  background: var(--theme-text-on-strong);
  color: var(--theme-surface-strong);
}

.connection-status.status-closed .status-dot::after {
  content: '—';
}

.connection-status.status-closed .status-context--closed {
  display: block;
}

.theme-toggle,
.board-action,
.close-share-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: var(--board-border-strong) solid var(--theme-control-border);
  border-radius: var(--radius-control);
  background: var(--theme-surface);
  color: var(--theme-text);
  cursor: pointer;
}

/* The primary action inverts with the theme. A fixed navy fill sat on the dark theme's navy
   cards at 1:1, so Call, Alert me and Watch it showed as bare text (390px dark screenshot,
   13 September 2026). */
.board-action {
  gap: var(--space-8);
  min-width: var(--board-action-min-width);
  min-height: var(--board-control-height);
  padding: var(--space-8) var(--space-12);
  border-color: var(--theme-action-bg);
  background: var(--theme-action-bg);
  color: var(--theme-action-text);
  font-size: var(--text-14);
  font-weight: var(--weight-bold);
}

.board-action:hover,
.board-action:focus-visible {
  border-color: var(--theme-action-hover);
  background: var(--theme-action-hover);
}

.board-action--secondary {
  border-color: var(--theme-control-border);
  background: var(--theme-surface);
  color: var(--theme-text);
}

.board-action--secondary:hover,
.board-action--secondary:focus-visible {
  border-color: var(--theme-control-border);
  background: var(--theme-surface-subtle);
  color: var(--theme-text);
}

.board-action__icon {
  flex: 0 0 auto;
}

.theme-toggle {
  gap: var(--space-8);
  min-height: var(--board-control-height);
  padding: var(--space-8) var(--space-12);
  font-size: var(--text-14);
  font-weight: var(--weight-semibold);
}

.theme-toggle__mark {
  font-size: var(--text-18);
  line-height: 1;
}

/* Unconditional, and quiet. It sits below everything on the board because it qualifies
   every figure above it, and it must never be the reason a rate is pushed off a screen. */
.board-disclaimer {
  margin: var(--space-16) 0 0;
  color: var(--theme-text-muted);
  font-size: var(--text-12);
}

.board-announcement {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
  margin: var(--space-16) 0 0;
  padding: var(--space-12) var(--space-16);
  border-left: var(--board-border-strong) solid var(--gfe-experimental);
  border-radius: var(--radius-control);
  background: var(--theme-surface);
  box-shadow: var(--theme-shadow);
  font-size: var(--text-14);
  line-height: 1.5;
}

.board-announcement__label {
  color: var(--theme-text);
}

.board-main {
  display: grid;
  flex: 1;
  gap: var(--board-section-gap);
  min-width: 0;
  padding: var(--board-section-gap) 0;
}

.section-heading {
  align-items: end;
  flex-wrap: wrap;
  gap: var(--space-12);
  margin-bottom: var(--space-16);
}

.section-heading h2,
.detail-card h2 {
  margin: var(--space-4) 0 0;
  color: var(--theme-text);
  font-size: var(--board-section-title-size);
  font-weight: var(--weight-semibold);
  line-height: 1.2;
}

.section-heading__note {
  max-width: 42ch;
  margin: 0;
  font-size: var(--text-14);
  line-height: 1.5;
  text-align: right;
}

.spot-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-16);
}

.spot-card,
.rate-panel,
.detail-card {
  border: var(--board-border) solid var(--theme-border);
  border-radius: var(--radius-card);
  background: var(--theme-surface);
  box-shadow: var(--theme-shadow);
}

.spot-card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 14rem;
  padding: var(--board-spot-padding);
  border-top: var(--board-border-strong) solid var(--theme-surface-strong);
}

.spot-card--primary {
  min-height: 17rem;
}

.spot-card__head {
  justify-content: space-between;
  gap: var(--space-16);
}

.spot-card__label {
  color: var(--theme-text);
}

.spot-card__unit,
.spot-card__meta {
  margin: var(--space-4) 0 0;
  font-size: var(--text-14);
  line-height: 1.5;
}

.spot-card__index {
  color: var(--theme-text-muted);
  font-size: var(--text-14);
  font-weight: var(--weight-semibold);
}

.spot-card__price {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  min-width: 0;
  margin: auto 0 var(--space-16);
  color: var(--theme-text);
  font-size: var(--board-price-primary-size);
  font-weight: var(--weight-bold);
  letter-spacing: -0.03em;
  line-height: 1.1;
  white-space: nowrap;
}

.spot-card__price--compact {
  font-size: var(--board-price-size);
}

.currency-symbol {
  flex: 0 0 auto;
  font-size: var(--board-price-currency-size);
  font-weight: var(--weight-semibold);
}

.price-value {
  min-width: 0;
  overflow-wrap: anywhere;
}

.spot-card__meta {
  margin-top: auto;
}

.placeholder-marker {
  display: inline-block;
  margin-left: var(--space-4);
  font-size: var(--text-12);
  font-weight: var(--weight-medium);
}

.rate-panel {
  min-width: 0;
  padding: var(--board-panel-padding);
}

.section-heading--panel {
  margin-bottom: var(--space-24);
}

.rate-table-wrap {
  overflow-x: auto;
  border: var(--board-border) solid var(--theme-border);
  border-radius: var(--radius-control);
}

.rate-table {
  width: 100%;
  min-width: var(--board-table-min-width);
  border-collapse: collapse;
  color: var(--theme-text);
  font-size: var(--board-table-value-size);
}

.rate-table th,
.rate-table td {
  padding: var(--space-16);
  border-bottom: var(--board-border) solid var(--theme-border);
  text-align: left;
  vertical-align: middle;
}

.rate-table thead th {
  background: var(--theme-surface-subtle);
  color: var(--theme-text-muted);
  white-space: nowrap;
}

.rate-table tbody th {
  font-weight: var(--weight-semibold);
}

.rate-table tbody td:nth-child(n + 3) {
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}

.rate-table tbody tr:last-child th,
.rate-table tbody tr:last-child td {
  border-bottom: 0;
}

.rate-table__tag {
  display: inline-block;
  margin-left: var(--space-4);
  padding: var(--space-4) var(--space-8);
  border-radius: var(--radius-pill);
  background: var(--theme-surface-strong);
  color: var(--theme-text-on-strong);
  font-size: var(--text-12);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

/*
 * A cell with no figure in it. It reads as prose, not as a number: lighter than the rates
 * beside it and allowed to wrap, so "Not yet available" is never mistaken for a value and
 * never forces the table wider on a phone.
 */
.rate-table tbody td.is-empty-value {
  font-weight: var(--weight-regular);
  white-space: normal;
}

.rate-table__note {
  display: block;
  font-size: var(--text-12);
  font-weight: var(--weight-regular);
}

.spot-card__price.is-empty-value {
  font-size: var(--text-18);
  font-weight: var(--weight-semibold);
  letter-spacing: 0;
  white-space: normal;
}

/* Fills columns by how many cards the shop actually has — About, Contact and Bank are each
   optional — rather than a fixed two, which left a third card alone on a half-empty row. */
.board-details {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: var(--space-16);
  /* The navigation's "About us" entry scrolls here; keep the heading clear of the top edge. */
  scroll-margin-top: var(--space-24);
}

.detail-card {
  display: block;
  padding: var(--board-panel-padding);
}

.detail-card h2 {
  font-size: var(--text-24);
}

.detail-card p:last-child,
.detail-card__line,
.detail-card__body {
  margin: var(--space-12) 0 0;
  color: var(--theme-text-muted);
  font-size: var(--text-14);
  line-height: 1.6;
}

/* The jeweller's own words, rendered exactly as typed. An unbroken token in them — a
   pasted URL in the about text, a run-on address — must wrap inside the card rather
   than push the page wider than a 360px screen. */
.detail-card__line,
.detail-card__body,
.board-announcement__text {
  min-width: 0;
  overflow-wrap: anywhere;
}

.detail-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-12);
  margin-top: var(--space-16);
}

/* Call and Directions are anchors, not buttons: they hand off to the phone's dialler
   and its map app. They borrow the button treatment and drop the underline that comes
   with an anchor. */
.contact-action {
  text-decoration: none;
}

.contact-action:focus-visible {
  outline: var(--board-border-strong) solid var(--gfe-experimental);
  outline-offset: var(--board-focus-offset);
}

/*
 * The tenant-copy regions hide with the `hidden` attribute rather than a class, because
 * "the jeweller has not given us this one" is a fact about the element and not a style
 * choice. Each of them is given an explicit `display` above, which outranks the user
 * agent's own `[hidden] { display: none }`, so every one has to be named here. Scoped
 * deliberately: a blanket `[hidden]` rule would also change regions this task does not
 * own.
 */
.board-announcement[hidden],
.board-details[hidden],
.detail-card[hidden],
.detail-card__line[hidden],
.contact-action[hidden] {
  display: none;
}

.market-frozen .spot-card--primary {
  border-top-color: var(--theme-control-border);
}

.market-frozen .spot-card--primary::after {
  content: 'Rates locked for market close';
  display: inline-flex;
  align-self: flex-start;
  margin-top: var(--space-8);
  padding: var(--space-4) var(--space-8);
  border: var(--board-border) solid var(--theme-control-border);
  border-radius: var(--radius-pill);
  color: var(--theme-text-muted);
  font-size: var(--text-12);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.market-frozen .spot-card__price {
  color: var(--theme-text-muted);
}

.price-up {
  animation: rate-rise var(--board-tween-duration) ease-out;
}

.price-down {
  animation: rate-fall var(--board-tween-duration) ease-out;
}

@keyframes rate-rise {
  from {
    box-shadow: 0 0 0 var(--space-4) var(--gfe-success-bg);
  }
  to {
    box-shadow: var(--theme-shadow);
  }
}

@keyframes rate-fall {
  from {
    box-shadow: 0 0 0 var(--space-4) var(--gfe-alert-bg);
  }
  to {
    box-shadow: var(--theme-shadow);
  }
}

.footer {
  flex-wrap: wrap;
  gap: var(--space-16);
  padding-top: var(--space-16);
  border-top: var(--board-border) solid var(--theme-border);
  font-size: var(--text-12);
}

.footer-info,
.footer-actions {
  flex-wrap: wrap;
  gap: var(--space-12);
}

.ticker-source {
  color: var(--theme-text);
}

/* The line states where the numbers came from, so it must not read as decoration when
   it is carrying bad news. Held is the only one that raises its voice: closed and
   expired are already said loudly by the badge and the frozen-card treatment. */
.ticker-source--closed,
.ticker-source--expired {
  color: var(--theme-text-muted);
}

.ticker-source--held {
  color: var(--gfe-alert);
}

.timestamp {
  font-size: var(--text-12);
}

.share-modal-overlay {
  position: fixed;
  z-index: var(--board-z-modal);
  inset: 0;
  display: grid;
  place-items: center;
  padding: var(--space-16);
  background: var(--theme-overlay);
}

.share-modal-overlay.hidden,
.hidden {
  display: none;
}

.share-modal {
  width: min(100%, var(--board-modal-max));
  max-height: calc(100vh - var(--space-32));
  overflow: auto;
  border-radius: var(--radius-modal);
  background: var(--theme-surface);
  color: var(--theme-text);
  box-shadow: var(--shadow-lg);
}

.share-modal-header {
  justify-content: space-between;
  gap: var(--space-16);
  padding: var(--space-16) var(--space-24);
  border-bottom: var(--board-border) solid var(--theme-border);
}

.share-modal-header h3 {
  margin: 0;
  font-size: var(--text-24);
  font-weight: var(--weight-semibold);
}

.close-share-btn {
  width: var(--board-control-height);
  height: var(--board-control-height);
  font-size: var(--text-32);
  line-height: 1;
}

.share-modal-body {
  padding: var(--space-24);
}

.share-modal__eyebrow {
  margin: 0;
  color: var(--theme-text-muted);
  font-size: var(--text-12);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.share-modal__intro {
  margin: var(--space-4) 0 var(--space-16);
  color: var(--theme-text);
  font-size: var(--text-16);
  line-height: 1.5;
}

.card-preview-container {
  display: grid;
  min-height: 14rem;
  place-items: center;
  overflow: hidden;
  border: var(--board-border-strong) solid var(--gfe-navy);
  border-radius: var(--radius-card);
  background: var(--gfe-bg);
}

.card-preview-container img {
  display: block;
  width: 100%;
  height: auto;
}

.preview-loading,
.preview-error {
  margin: var(--space-16);
  color: var(--theme-text-muted);
  font-size: var(--text-14);
  text-align: center;
  line-height: 1.5;
}

.preview-error {
  padding: var(--space-12);
  border-radius: var(--radius-control);
  background: var(--gfe-alert);
  color: var(--gfe-bg);
  font-weight: var(--weight-semibold);
}

.share-actions-grid {
  align-items: stretch;
  flex-wrap: wrap;
  gap: var(--space-12);
  margin-top: var(--space-24);
}

.btn {
  min-height: var(--board-control-height);
  padding: var(--space-12) var(--space-16);
  border-radius: var(--radius-control);
  font-size: var(--text-14);
  font-weight: var(--weight-bold);
  cursor: pointer;
}

.btn-whatsapp {
  flex: 1 1 100%;
  border: var(--board-border-strong) solid var(--gfe-whatsapp-dark);
  background: var(--gfe-whatsapp-dark);
  color: var(--gfe-bg);
}

.btn-whatsapp:hover,
.btn-whatsapp:focus-visible {
  border-color: var(--gfe-whatsapp-dark-hover);
  background: var(--gfe-whatsapp-dark-hover);
}

.btn-secondary {
  flex: 1 1 12rem;
  border: var(--board-border-strong) solid var(--theme-control-border);
  background: var(--theme-surface);
  color: var(--theme-text);
}

.share-toast {
  margin-top: var(--space-16);
  padding: var(--space-12);
  border-radius: var(--radius-control);
  background: var(--gfe-success);
  color: var(--gfe-bg);
  font-size: var(--text-14);
  font-weight: var(--weight-semibold);
  text-align: center;
}

.sr-only {
  position: absolute;
  width: var(--space-4);
  height: var(--space-4);
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 60rem) {
  .spot-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .spot-card--inr {
    grid-column: span 2;
    min-height: 12rem;
  }
}

@media (max-width: 42rem) {
  .header__controls {
    width: 100%;
    justify-content: space-between;
  }

  .connection-status {
    flex: 1;
  }

  .header__actions {
    width: 100%;
  }

  .board-action {
    flex: 1 1 var(--board-action-min-width);
  }

  .section-heading__note {
    text-align: left;
  }

  .spot-grid,
  .board-details {
    grid-template-columns: 1fr;
  }

  .spot-card--inr {
    grid-column: auto;
  }

  .spot-card,
  .spot-card--primary {
    min-height: 0;
  }

  .spot-card__price {
    margin-top: var(--space-32);
  }

  .footer-info {
    display: grid;
  }
}

@media (orientation: portrait) and (min-width: 43rem) {
  .app-container {
    width: min(100%, 52rem);
  }

  .spot-grid {
    grid-template-columns: 1fr;
  }

  .spot-card--inr {
    grid-column: auto;
  }

  .spot-card {
    min-height: 13rem;
  }

  .spot-card--primary {
    min-height: 16rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  body {
    transition: none;
  }

  .price-up,
  .price-down {
    animation: none;
  }
}

/* ---------------------------------------------------------------------------
   Session figures and sparkline.

   Both are strictly retrospective: where the price opened, how far it has
   travelled since, and the shape of that path. Nothing here projects forward —
   a chart is the easiest place on a rate board to slide from reporting a price
   into implying where it is going, which is the line that separates a display
   product from investment advice.

   Colours come from the GFE tokens, not from a metal: the gold row is drawn in
   navy and the silver row in the secondary blue, because the palette forbids a
   gold hue and the label already says which metal it is.
   --------------------------------------------------------------------------- */

.session {
  display: flex;
  align-items: baseline;
  gap: var(--space-24);
  margin-bottom: var(--space-16);
  flex-wrap: wrap;
}

.session-item {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.session-label {
  font-size: var(--text-12);
  font-weight: var(--weight-bold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--theme-text-muted);
}

.session-value {
  font-size: var(--text-18);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  color: var(--theme-text);
}

.session-change {
  margin-left: auto;
  padding: var(--space-4) var(--space-8);
  border-radius: var(--radius-control);
  background: var(--theme-surface-subtle);
  color: var(--theme-text-muted);
  font-size: var(--text-16);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
}

/* Direction is carried by colour and an arrow, never by a word — "up" is an
   observation, "rising" reads as a forecast. */
.session-change.is-up {
  background: var(--gfe-success-bg);
  color: var(--gfe-success);
}

.session-change.is-down {
  background: var(--gfe-alert-bg);
  color: var(--gfe-alert);
}

.spark {
  display: block;
  width: 100%;
  height: 4.25rem;
  margin-bottom: var(--space-16);
  overflow: visible;
}

.spark polyline {
  fill: none;
  stroke-width: 2.5;
  stroke-linejoin: round;
  stroke-linecap: round;
  /* The polyline is drawn in a 100x30 viewBox stretched to the card width, which
     would scale the stroke with it and leave a wedge-shaped line. */
  vector-effect: non-scaling-stroke;
}

.gold-card .spark polyline {
  stroke: var(--gfe-navy);
}

.silver-card .spark polyline {
  stroke: var(--gfe-experimental);
}

.spark polygon {
  stroke: none;
}

/* A board is read from across a shop, where a fill much lighter than this
   disappears entirely. */
.gold-card .spark polygon {
  fill: rgb(20 33 61 / 14%);
}

.silver-card .spark polygon {
  fill: rgb(29 78 137 / 12%);
}

/* A board with no history yet says so rather than drawing a flat line, which
   would read as a motionless market instead of missing data. */
.spark.is-empty::after {
  content: '';
}

.session.is-empty .session-value,
.session.is-empty .session-change {
  color: var(--theme-text-muted);
}

body.market-frozen .spark polyline,
body.market-frozen .spark polygon {
  opacity: 0.35;
}

/* The weight grid.

   Deliberately styled as the product table and not as something new: it is the same figures
   restated, so making it look like a different kind of claim would be the design telling a
   customer something the arithmetic does not. */
/* All four figures are the same claim, so they are typeset the same.

   This has to out-specify `.dashboard .rate-table tbody td:nth-child(n + 4)` in
   dashboard.css, which greys and shrinks the fourth column onward. Matching that rule's
   specificity is not enough -- `:nth-child` counts as a class, so the two tie and
   dashboard.css wins on load order. Hence the id. That is right in the
   product table -- the rate is the answer, the day range is context -- and wrong here, where
   the fourth and fifth columns are a tola and an ounce. Inherited, it made the two weights a
   customer is most likely to ask about look like a footnote to the two they did not.

   Right-aligned with tabular figures so the decimal points line up down each column: the
   panel exists to be compared across, and ragged numbers defeat that. */
#weight-grid .rate-table tbody td {
  color: var(--theme-text);
  font-size: var(--board-table-value-size);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

#weight-grid .rate-table thead th:not(:first-child) {
  text-align: right;
}

/* The narrow-viewport rules in dashboard.css pin the product table to a fixed layout with
   per-column widths -- 3rem for the unit, 8rem for the rate -- because that table is
   product / weight / rate / high / low. This one is product and four rates, so those widths
   put a five-figure number in a 3rem column and the columns ran into each other with no gap
   at all. It scrolls sideways in its own container instead, like the table above it. */
@media (max-width: 42.99rem) {
  #weight-grid .rate-table {
    table-layout: auto;
    min-width: 32rem;
  }

  #weight-grid .rate-table th,
  #weight-grid .rate-table td {
    width: auto;
    padding: var(--space-12) var(--space-8);
    font-size: var(--text-14);
    white-space: nowrap;
  }
}

.weight-grid__note {
  display: block;
  margin-top: 0.125rem;
  font-size: var(--text-12);
  font-weight: var(--weight-regular);
  letter-spacing: 0;
  text-transform: none;
  opacity: 0.7;
}

/* Calculators.

   Four cards on one panel, each self-contained: inputs, the rate it is working from named
   in words, and a definition list of results. Styled as part of the board rather than as a
   widget bolted on, because every figure in them is this board's own rate restated. */
.calc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: var(--space-16);
  padding: var(--space-16);
}

.calc {
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
  padding: var(--space-16);
  border: var(--board-border) solid var(--theme-border);
  border-radius: var(--radius-card);
  background: var(--theme-surface);
}

.calc__title {
  margin: 0;
  font-size: var(--text-16);
  font-weight: var(--weight-semibold);
}

.calc__intro {
  margin: 0;
  color: var(--theme-text-muted);
  font-size: var(--text-14);
  line-height: 1.5;
}

.calc__fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: var(--space-8);
}

.calc__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  font-size: var(--text-12);
  color: var(--theme-text-muted);
}

.calc__field--wide {
  grid-column: 1 / -1;
}

.calc__field input,
.calc__field select {
  padding: var(--space-8);
  border: var(--board-border) solid var(--theme-control-border);
  border-radius: var(--radius-control);
  background: var(--theme-surface);
  color: var(--theme-text);
  font: inherit;
  font-size: var(--text-14);
  /* Number inputs otherwise overflow their column on a phone. */
  min-width: 0;
}

/* The rate a panel is working from, named rather than assumed. */
.calc__rate {
  margin: 0;
  color: var(--theme-text-muted);
  font-size: var(--text-12);
  line-height: 1.5;
}

.calc__out {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-4) var(--space-16);
  margin: 0;
  padding-top: var(--space-12);
  border-top: var(--board-border) solid var(--theme-border);
}

.calc__out dt {
  color: var(--theme-text-muted);
  font-size: var(--text-14);
}

.calc__out dd {
  margin: 0;
  text-align: right;
  font-size: var(--text-14);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

/* The last pair is the answer the rest was building to. */
.calc__out dt:last-of-type,
.calc__out dd:last-of-type {
  padding-top: var(--space-8);
  border-top: var(--board-border) solid var(--theme-border);
  font-size: var(--text-16);
}

/* Two conclusions, not one.

   Every other calculator builds to a single answer and the last line is it, which is what
   the rule above says. This panel ends on two figures for two different parties -- what the
   payer hands over, what the payee receives -- and emphasising whichever happens to be last
   would pick one of them as the answer for a reader who has not read the labels. */
.calc__out--twin dt:last-of-type,
.calc__out--twin dd:last-of-type {
  padding-top: 0;
  border-top: 0;
  font-size: var(--text-14);
}

/* Why a line is missing, in words. Never styled as an error: an unstated tax rate is a
   fact about the shop's configuration, not a fault. */
.calc__warn:empty {
  display: none;
}

.calc__warn {
  margin: 0;
  color: var(--theme-text-muted);
  font-size: var(--text-12);
  line-height: 1.5;
  font-style: italic;
}

/* Price alerts. Styled as a panel of the board rather than a widget, for the same reason
   the calculators are: it acts on this board's own published rates. */
.alerts {
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
  padding: var(--space-16);
}

.alerts__fields {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-8);
}

.alerts__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  flex: 1 1 9rem;
  min-width: 0;
  font-size: var(--text-12);
  color: var(--theme-text-muted);
}

.alerts__field select,
.alerts__field input {
  padding: var(--space-8);
  border: var(--board-border) solid var(--theme-control-border);
  border-radius: var(--radius-control);
  background: var(--theme-surface);
  color: var(--theme-text);
  font: inherit;
  font-size: var(--text-14);
  min-width: 0;
}

.alerts__status:empty {
  display: none;
}

.alerts__status {
  margin: 0;
  font-size: var(--text-14);
  color: var(--theme-text);
}

.alerts__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  margin: 0;
  padding: 0;
  list-style: none;
}

.alerts__list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
  padding: var(--space-8) var(--space-12);
  border: var(--board-border) solid var(--theme-border);
  border-radius: var(--radius-control);
  font-size: var(--text-14);
}

.alerts__empty {
  color: var(--theme-text-muted);
  border-style: dashed;
}

.alerts__remove {
  padding: var(--space-4) var(--space-12);
  border: var(--board-border) solid var(--theme-control-border);
  border-radius: var(--radius-pill);
  background: var(--theme-surface);
  color: var(--theme-text);
  font: inherit;
  font-size: var(--text-12);
  cursor: pointer;
}

.alerts__note {
  margin: 0;
  color: var(--theme-text-muted);
  font-size: var(--text-12);
  line-height: 1.5;
}

/* The price chart. Inline SVG, styled here rather than with presentation attributes so it
   follows the theme like everything else on the board. */
.chart {
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
  padding: var(--space-16);
}

.chart__controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-12);
  justify-content: space-between;
}

.chart__tabs {
  display: flex;
  gap: var(--space-4);
}

.chart__tab {
  padding: var(--space-4) var(--space-12);
  border: var(--board-border) solid var(--theme-border);
  border-radius: var(--radius-pill);
  background: var(--theme-surface);
  color: var(--theme-text-muted);
  font: inherit;
  font-size: var(--text-14);
  cursor: pointer;
}

.chart__tab[aria-pressed='true'] {
  border-color: var(--theme-surface-strong);
  background: var(--theme-surface-strong);
  color: var(--theme-text-on-strong);
}

.chart__figure {
  margin: 0;
}

.chart__svg {
  display: block;
  width: 100%;
  /* Fixed height with a non-uniform preserveAspectRatio: the line stretches to the panel
     width while the labels keep a readable size at every viewport. */
  height: 13.75rem;
}

.chart__line {
  fill: none;
  stroke: var(--theme-surface-strong);
  stroke-width: 2;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.chart__area {
  fill: var(--theme-surface-strong);
  opacity: 0.08;
  stroke: none;
}

.chart__grid {
  stroke: var(--theme-border);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.chart__axis {
  fill: var(--theme-text-muted);
  font-size: 11px;
  font-family: var(--font-body);
}

.chart__desc {
  margin: var(--space-8) 0 0;
  color: var(--theme-text-muted);
  font-size: var(--text-14);
  line-height: 1.5;
}

.chart__stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
  gap: var(--space-12) var(--space-16);
  margin: 0;
  padding-top: var(--space-12);
  border-top: var(--board-border) solid var(--theme-border);
}

/* One grid cell per label-and-value pair, so the two can never be split across a wrap. */
.chart__stat {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}

.chart__stats dt {
  color: var(--theme-text-muted);
  font-size: var(--text-12);
}

.chart__stats dd {
  margin: 0;
  font-size: var(--text-16);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

/* The Trend Indicator's state line and its standing disclaimer. Styled as a quiet block
   rather than a badge: §3.8.3 requires the computed-not-reviewed line on every view, and a
   line that shouts is a line people stop reading. */
.trend {
  padding-top: var(--space-12);
  border-top: var(--board-border) solid var(--theme-border);
}

.trend__state {
  margin: 0;
  font-size: var(--text-16);
  font-weight: var(--weight-semibold);
}

.trend__note {
  margin: var(--space-4) 0 0;
  color: var(--theme-text-muted);
  font-size: var(--text-12);
  line-height: 1.5;
}

.trend__note a {
  color: inherit;
}

/* The language picker. Deliberately quiet and deliberately small: it changes one sentence, and
   a control styled like a primary action would suggest it changes the board. */
.trend__lang {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  margin-top: var(--space-8);
}

.trend__lang-label {
  color: var(--theme-text-muted);
  font-size: var(--text-12);
}

/* A native select, not a custom menu. Nine scripts render in the platform's own font stack and
   the phone's own picker, which is the one control on the page a customer already knows how to
   use in their language. */
.trend__lang-select {
  padding: var(--space-4) var(--space-8);
  border: var(--board-border) solid var(--theme-border);
  border-radius: var(--radius-control);
  background: var(--theme-surface);
  color: var(--theme-text);
  font: inherit;
  font-size: var(--text-12);
  /* Tamil and Kannada endonyms are taller than Latin ones; without room the option text clips
     on Android. */
  line-height: 1.6;
}

/* Device-local price watches (§3.1 #4). Shares the alerts panel's shape deliberately — they
   do the same job for the customer and only differ in reach — but keeps its own class names,
   because the two panels appear together on a configured deployment and one stylesheet rule
   quietly covering both is how a change to one silently edits the other. */
.watch {
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
  padding: var(--space-16);
}

.watch__fields {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-8);
}

.watch__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  flex: 1 1 9rem;
  min-width: 0;
  font-size: var(--text-12);
  color: var(--theme-text-muted);
}

.watch__field select,
.watch__field input {
  padding: var(--space-8);
  border: var(--board-border) solid var(--theme-border);
  border-radius: var(--radius-control);
  background: var(--theme-surface);
  color: var(--theme-text);
  font: inherit;
  font-variant-numeric: tabular-nums;
  min-width: 0;
}

.watch__status:empty,
.watch__fired:empty {
  display: none;
}

.watch__status {
  margin: 0;
  font-size: var(--text-14);
  color: var(--theme-text-muted);
}

.watch__status.is-error {
  color: var(--gfe-alert, var(--theme-text));
}

/* A firing is the one thing on this panel a customer must not miss, so it is the one thing
   here that is allowed to be loud. */
.watch__fired {
  margin: 0;
  padding: var(--space-8) var(--space-12);
  border-radius: var(--radius-control);
  background: var(--gfe-success-bg);
  color: var(--theme-text);
  font-size: var(--text-14);
  font-weight: var(--weight-semibold);
}

.watch__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}

.watch__item {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  flex-wrap: wrap;
  padding: var(--space-8);
  border: var(--board-border) solid var(--theme-border);
  border-radius: var(--radius-control);
  font-size: var(--text-14);
}

.watch__item--fired {
  border-color: var(--gfe-success, var(--theme-border));
}

.watch__text {
  flex: 1 1 12rem;
  min-width: 0;
  font-variant-numeric: tabular-nums;
}

.watch__action {
  padding: var(--space-4) var(--space-8);
  border: var(--board-border) solid var(--theme-border);
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--theme-text-muted);
  font: inherit;
  font-size: var(--text-12);
  cursor: pointer;
  /* A shop counter is touched, not clicked. Below this the two buttons on a fired row are
     close enough together to hit the wrong one. */
  min-height: 2.25rem;
}

.watch__action:hover {
  color: var(--theme-text);
}

.watch__note {
  margin: 0;
  color: var(--theme-text-muted);
  font-size: var(--text-12);
  line-height: 1.5;
}

/* Inline with the sentence that explains the limit, so the offer to notify is read in the
   same breath as "only while this board is open". */
.watch__notify {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}

/* Floating quick-contact: Call bottom-left, WhatsApp bottom-right.
   Above page content and the phone navigation bar, below the share modal. */
.quick-contact {
  position: fixed;
  bottom: max(1rem, env(safe-area-inset-bottom));
  z-index: 5;
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  min-height: 3.25rem;
  padding: 0 var(--space-16) 0 var(--space-12);
  border-radius: var(--radius-pill);
  box-shadow: 0 0.375rem 1.25rem rgb(0 0 0 / 28%);
  font-size: var(--text-16);
  font-weight: var(--weight-bold);
  text-decoration: none;
}

.quick-contact[hidden] {
  display: none;
}

/* Stepped aside on the Contact tab (public/js/tabs.js sets body[data-active-tab]), whose card carries
   its own Call and WhatsApp — two of each on one short page is clutter. Every other tab keeps
   them, the Bank tab included, where "call the shop to confirm" is the instruction. `visibility`
   so a docked button can neither be tapped nor tabbed to. */
.quick-contact {
  transition:
    opacity var(--transition-fast),
    visibility var(--transition-fast);
}

body[data-active-tab='contact'] .quick-contact {
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
}

.quick-contact__icon {
  flex: 0 0 1.5rem;
  width: 1.5rem;
  height: 1.5rem;
  fill: currentColor;
}

/* The ring is not decoration. The buttons float over whatever is scrolling beneath them, and
   the Call button's navy is the same navy as the gold price card: without a light ring the pill
   vanished into the card and only its label was left, found on a 390px screenshot. */
.quick-contact--call {
  left: 1rem;
  border: 0.125rem solid rgb(255 255 255 / 90%);
  background: var(--gfe-navy);
  color: var(--gfe-bg);
}

/* WhatsApp's own green, with near-black text rather than the white WhatsApp uses. White on
   #25D366 is under 2:1 and fails WCAG AA; this pairing is well over 7:1 and the green is still
   the one a customer recognises. */
.quick-contact--whatsapp {
  right: 1rem;
  background: #25d366;
  color: #062a14;
}

.quick-contact:hover,
.quick-contact:focus-visible {
  transform: translateY(-1px);
  box-shadow: 0 0.5rem 1.5rem rgb(0 0 0 / 34%);
}

.quick-contact:focus-visible {
  outline: var(--board-border-strong) solid var(--gfe-experimental);
  outline-offset: var(--board-focus-offset);
}

/* Room at the foot of the page, so the last line of content is never under a button.
   `body.dashboard` rather than `.dashboard`: dashboard.css loads after this file and sets the
   same padding at the same specificity, which would otherwise win and leave the last lines of
   the page under the buttons. */
body.dashboard .app-container {
  padding-bottom: 5.5rem;
}

/* Phones and tablets: the navigation bar is pinned to the bottom, so the buttons ride above it
   rather than covering it, and the page keeps room for both. */
@media screen and (max-width: 74.99rem) {
  .quick-contact {
    bottom: calc(5.25rem + env(safe-area-inset-bottom));
  }

  body.dashboard .app-container {
    padding-bottom: 10rem;
  }
}

/* Desktop: the navigation rail runs down the left edge, so Call clears it. */
@media (min-width: 75rem) {
  .quick-contact--call {
    left: calc(var(--dashboard-rail-width) + 1.5rem);
  }
}

/* A wall-mounted shop screen in fullscreen, and a printed rate sheet, have nothing to tap. */
:fullscreen .quick-contact,
html:fullscreen .quick-contact {
  display: none;
}

@media print {
  .quick-contact {
    display: none;
  }
}

/* Phones: round buttons, not labelled pills. At 390 px the pills were a third of the screen's width
   each and sat over the gold card's day low and change as soon as the board opened. The label stays
   for assistive technology (board.js sets an aria-label naming the shop and number), and the icons
   are the two a customer already knows from their own phone. */
@media (max-width: 42.99rem) {
  .quick-contact {
    justify-content: center;
    width: 3.25rem;
    min-height: 3.25rem;
    height: 3.25rem;
    padding: 0;
  }
  .quick-contact--call {
    left: 0.75rem;
  }
  .quick-contact--whatsapp {
    right: 0.75rem;
  }
  .quick-contact__label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
}

@media (prefers-reduced-motion: reduce) {
  .quick-contact:hover,
  .quick-contact:focus-visible {
    transform: none;
  }
}

/* A board the gateway refused. Everything that promises rates — the price cards, the rate book,
   the feed badge, the share and fullscreen actions, the navigation between rate sections — goes,
   and a plain notice stays. Hiding by class rather than removing nodes, so a later successful
   connection on the same page could restore the board without a reload. */
.board-unavailable {
  max-width: 40rem;
  margin: var(--space-32) 0;
  padding: var(--space-24);
  border: var(--board-border) solid var(--theme-border);
  border-radius: var(--radius-card);
  background: var(--theme-surface);
}

.board-unavailable__title {
  margin: 0 0 var(--space-8);
  font-size: var(--board-section-title-size);
  line-height: 1.2;
}

.board-unavailable__text {
  margin: 0;
  color: var(--theme-text-muted);
  font-size: var(--text-16);
  line-height: 1.6;
}

body.board-is-unavailable .board-main > section:not(#board-unavailable),
body.board-is-unavailable .header__controls,
body.board-is-unavailable .sub-brand,
body.board-is-unavailable .brand__eyebrow,
body.board-is-unavailable .dashboard-nav,
body.board-is-unavailable .footer,
body.board-is-unavailable .board-announcement,
body.board-is-unavailable .quick-contact {
  display: none;
}

/* The bank card. Values in tabular figures and allowed to break anywhere, because an 18-digit
   account number must never overflow a narrow phone and push the page sideways. */
.bank-details {
  display: grid;
  gap: var(--space-8);
  margin: var(--space-12) 0;
}

.bank-details__row {
  display: grid;
  grid-template-columns: minmax(6.5rem, auto) 1fr;
  align-items: center;
  gap: var(--space-8);
}

.bank-details dt {
  color: var(--theme-text-muted);
  font-size: var(--text-14);
}

.bank-details dd {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  margin: 0;
  min-width: 0;
}

.bank-details__value {
  min-width: 0;
  color: var(--theme-text);
  font-size: var(--text-16);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.bank-details__copy {
  flex: 0 0 auto;
  min-height: 2.25rem;
  padding: 0 var(--space-12);
  border: var(--board-border) solid var(--theme-control-border);
  border-radius: var(--radius-control);
  background: var(--theme-surface);
  color: var(--theme-text);
  font-size: var(--text-12);
  font-weight: var(--weight-semibold);
  cursor: pointer;
}

.bank-details__copy:hover,
.bank-details__copy:focus-visible {
  /* --theme-text, not navy: a navy border vanishes on the dark theme's navy card. */
  border-color: var(--theme-text);
}

/* The one line on this card that protects a customer's money. Quiet in size, not in colour. */
.bank-details__warning {
  margin: 0;
  padding: var(--space-8) var(--space-12);
  border-left: var(--board-border-strong) solid var(--gfe-alert);
  background: var(--gfe-alert-bg);
  /* A fixed dark red rather than --theme-text. --gfe-alert-bg is the same pale pink in both
     themes, and --theme-text turns light in the dark theme — which would put light text on a
     pale background on exactly the line meant to stop a fraudulent payment. */
  color: #3d1415;
  font-size: var(--text-14);
  line-height: 1.5;
}

.bank-details__status:empty {
  display: none;
}

.bank-details__status {
  margin: var(--space-8) 0 0;
  color: var(--theme-text-muted);
  font-size: var(--text-12);
}
