/* BidConnect — palette matched to the Waco Habitat StoreConnect storefront */
:root {
  --brand: #385988;        /* waco-blue-primary */
  --brand-dark: #2a4374;   /* waco-blue-primary-hover */
  --action: #1565c0;       /* waco-blue-action (buttons) */
  --action-dark: #0d47a1;  /* waco-blue-action-hover */
  --link: #428bca;         /* waco-blue-link */
  --accent: #ff671f;       /* waco-orange (countdown) */
  --success: #44b02b;      /* waco-green */
  --bg: #f2f2f2;           /* waco-gray-bg */
  --card: #ffffff;
  --text: #333333;         /* waco-gray-heading */
  --muted: #757575;        /* waco-gray-body */
  --border: #cbd5e1;       /* waco-gray-border */
  --danger: #d32f2f;
}
* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--text);
  font: 16px/1.5 "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  /* Nothing to reserve: the header is sticky and the footer sits at the end
     of the page, so each takes its own space. */
}
main { width: 100%; margin: 0 auto; padding: 1rem 1.5rem; }
a { color: var(--link); }

/* Sticky, not fixed: it stays at the top the same way, but keeps its place
   in the page, so the space above the content is always the header's real
   height. It was fixed over a 128px allowance, and the header grows when it
   wraps: a logged-in bidder on a phone measured 154px without a logo, which
   hid the countdown bar behind it (tester feedback, 2026-09-25). */
.site-header {
  position: sticky; top: 0; z-index: 1000;
  display: flex; flex-wrap: wrap; gap: .5rem 1rem; align-items: flex-start;
  justify-content: space-between; padding: .75rem 1rem;
  background: var(--brand); color: #fff;
}
/* The auction name centred under the logo, at every width (Keith,
   2026-09-25). On a desktop the pair stays at the left of the header. */
.site-header .brand {
  color: #fff; font-weight: 700; text-decoration: none;
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: .3rem;
}
.site-header .brand span { font-size: 1.3rem; }
.site-header .brand img {
  height: 58px; width: auto; display: block;
  background: #fff; padding: 6px 14px; border-radius: 8px;
}
.site-header nav { display: flex; flex-direction: column; align-items: flex-end; gap: .3rem; font-size: .9rem; }
.site-header nav a { color: #fff; text-decoration: none; }
.site-header nav .logged-in-as { color: rgb(255 255 255 / .75); text-align: right; }
.site-header nav .nav-links { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: .65rem; }
/* More: a native <details>, so it opens and closes with no script; auction.js
   only adds closing it on a click elsewhere or Escape. The menu drops below
   the link on its own layer, right-aligned so it never runs off the edge of a
   phone screen. */
.nav-more { position: relative; }
.nav-more > summary {
  list-style: none; cursor: pointer; color: #fff;
  user-select: none; -webkit-user-select: none;
}
.nav-more > summary::-webkit-details-marker { display: none; }
.nav-more > summary::after { content: " \25BE"; }
.nav-more[open] > summary::after { content: " \25B4"; }
.nav-more > summary:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.nav-more-menu {
  position: absolute; right: 0; top: calc(100% + .45rem); z-index: 1100;
  display: flex; flex-direction: column; min-width: 11rem; padding: .35rem 0;
  background: var(--card); border-radius: 8px; box-shadow: 0 6px 20px rgb(0 0 0 / .18);
}
.site-header .nav-more-menu a { color: var(--text); padding: .5rem 1rem; }
.site-header .nav-more-menu a:hover,
.site-header .nav-more-menu a:focus-visible { background: var(--bg); }
/* Log out is a form (it must be a POST) dressed as the menu's other items,
   last and set off by a rule. */
.nav-more-logout { margin: .35rem 0 0; padding-top: .35rem; border-top: 1px solid var(--border); }
.nav-more-logout button {
  display: block; width: 100%; padding: .5rem 1rem; text-align: left; cursor: pointer;
  background: none; border: 0; color: var(--text); font: inherit;
}
.nav-more-logout button:hover, .nav-more-logout button:focus-visible { background: var(--bg); }
/* Footer (Keith, 2026-09-25), laid out after botwerx.ai's: the
   organization's name centred on top; Contact, Legal and Text Alerts link
   columns; the three "by" credits stacked in the lower right corner; then a
   rule and the copyright line. It sits at the end of the page rather than
   pinned to the screen: pinned, link columns would cover a large part of
   every page. */
.site-footer {
  background: var(--brand); color: #fff; font-size: .9rem;
  padding: 1.5rem 1.5rem 1rem; margin-top: 2rem;
}
.site-footer a { color: rgb(255 255 255 / .85); text-decoration: none; }
.site-footer a:hover { color: #fff; text-decoration: underline; }
.site-footer .footer-org { text-align: center; font-weight: 600; font-size: 1rem; margin-bottom: 1.25rem; }
.site-footer .footer-main {
  display: grid; grid-template-columns: repeat(3, auto) 1fr; align-items: start;
  /* Narrower gaps on a narrower window: at a fixed 3rem the credits ran 6px
     past the footer's edge at 800px. */
  gap: 1.25rem clamp(1.25rem, 4vw, 3rem);
}
.site-footer .foot-col { display: flex; flex-direction: column; gap: .45rem; }
.site-footer .foot-h {
  font-size: .72rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: rgb(255 255 255 / .6);
}
/* The credits: logos in one column, right-aligned, labels beside them. */
.site-footer .powered-by-stack {
  display: flex; flex-direction: column; gap: .5rem; justify-self: end; align-self: end;
}
.site-footer .powered-by {
  display: grid; grid-template-columns: 140px auto; align-items: center; gap: .6rem;
  font-size: .8rem; white-space: nowrap;
}
.site-footer .powered-by img { height: 20px; width: 20px; display: block; justify-self: end; }
/* Botwerx's mark is a wordmark (735x76), not a square. */
.site-footer .powered-by img.botwerx-logo { width: auto; height: 13px; }
.site-footer .footer-bar {
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: .4rem 1.5rem;
  margin-top: 1.25rem; padding-top: .9rem; border-top: 1px solid rgb(255 255 255 / .2);
  font-size: .78rem; color: rgb(255 255 255 / .65);
}
.site-footer .footer-bar address { font-style: normal; }
.site-footer .nowrap { white-space: nowrap; }
@media (max-width: 760px) {
  /* Phones: link columns two to a row, then the credits with every logo
     flush right against the page's centre line and its label just past it
     (Keith, 2026-09-25), then the copyright centred. */
  .site-footer .footer-main { grid-template-columns: 1fr 1fr; gap: 1.25rem 1rem; }
  .site-footer .powered-by-stack { grid-column: 1 / -1; justify-self: stretch; }
  .site-footer .powered-by { grid-template-columns: 1fr 1fr; }
  .site-footer .footer-bar { flex-direction: column; align-items: center; text-align: center; }
}
.inline-form { display: inline; }
/* Inherits its colour from where it sits. It was hardcoded white for the one
   use in the header, and four of its five uses are on light grounds: the
   email-confirmation banner and three buttons on the account page, including
   the self-service "Turn off text alerts". White on white measured 1.00:1 --
   those controls were invisible. The header still gets white, from its own
   colour. */
.linklike {
  background: none; border: none; color: inherit; cursor: pointer;
  font: inherit; padding: 0; text-decoration: underline;
}

.countdown {
  background: var(--accent); color: #fff; text-align: center;
  padding: .4rem; font-weight: 600;
}
/* Phones: the header and footer scroll with the page, and the countdown is
   what stays at the top. Pinned, the two bars wrap to several lines and
   between them covered 287px of an 812px screen (measured logged in, no
   logo); the footer, at 133px over a 76px allowance, also kept the last
   item's bid button from ever scrolling into view. The time left is the one
   thing a bidder on a phone needs in view throughout. */
@media (max-width: 760px) {
  .site-header, .site-footer { position: static; }
  body { padding-bottom: 0; }
  .countdown { position: sticky; top: 0; z-index: 1000; }
}
.banner { padding: .6rem 1rem; text-align: center; }
.banner-pre { background: #e0e7ff; }
.banner-verify { background: #fef3c7; }
.banner-setup { background: #dbeafe; }
/* The setup checklist is a reading page, not a login form. It used to reuse
   .auth-card, whose 420px was chosen for a username and a password, so six
   steps of explanation stacked into a thin ribbon with two or three words a
   line. Wider, and the steps run two across once there is room for them. */
.setup-card {
  max-width: 920px; margin: 1.5rem auto; background: var(--card);
  border-radius: 10px; padding: 1.75rem 2rem; box-shadow: 0 1px 3px rgb(0 0 0 / .1);
}
.setup-card h1 { margin-top: 0; }
.setup-card .lede { font-size: 1.05rem; }

.onboarding-steps { padding-left: 1.2rem; }
.onboarding-steps li { margin-bottom: .9rem; }
@media (min-width: 760px) {
  .onboarding-steps {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .5rem 2.5rem; padding-left: 1.4rem;
  }
  /* Each step is a self-contained instruction, so a column break inside one
     would split a heading from the text that explains it. */
  .onboarding-steps li { break-inside: avoid; margin-bottom: 1.1rem; }
}
.onboarding-steps .step-done { color: #4b5563; }

.messages { width: 100%; margin: .5rem auto 0; padding: 0 1.5rem; }
.msg { padding: .6rem .9rem; border-radius: 6px; margin-bottom: .4rem; }
.msg-success { background: #d1fae5; }
.msg-warning { background: #fef3c7; }
.msg-error { background: #fee2e2; color: var(--danger); }

/* Listing: one thin horizontal strip per item — info left, bidding right. */
.category-heading {
  margin: 1.75rem 0 .5rem; padding-bottom: .3rem;
  border-bottom: 2px solid var(--border); color: var(--brand-dark);
}
.category-heading:first-of-type { margin-top: .5rem; }
.item-list { display: flex; flex-direction: column; gap: .5rem; margin-bottom: .5rem; }
.item-row {
  display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; align-items: center;
  justify-content: space-between;
  background: var(--card); border-radius: 8px; padding: .55rem 1rem;
  box-shadow: 0 1px 2px rgb(0 0 0 / .08);
}
/* Live bid-update flash — same animation as .bb-pulse (Big Board kiosk,
   see below) reused here for visual consistency; retriggerable from JS
   via a remove/reflow/re-add cycle since a class re-add alone won't
   replay a CSS animation that's already present. */
.live-flash { animation: bb-pulse 1.3s ease-out; }
/* Price text and the item's own image flash the same --danger red on a
   live update, so the two visually tie together as "this is the item
   that just changed." Image gets a permanent transparent border as a
   baseline (not just during the animation) so the flash never shifts
   layout by suddenly adding border width that wasn't reserved before. */
@keyframes price-flash { from { color: var(--danger); } to { color: inherit; } }
.price-flash { animation: price-flash 1.3s ease-out; }
@keyframes image-border-flash { from { border-color: var(--danger); } to { border-color: transparent; } }
.image-border-flash { animation: image-border-flash 1.3s ease-out; }
.item-row-info { display: flex; gap: .75rem; align-items: center; min-width: 0; flex: 1 1 320px; }
.item-row-info img {
  width: 72px; aspect-ratio: 4/3; object-fit: cover;
  border-radius: 6px; flex-shrink: 0; border: 3px solid transparent;
}
.item-thumb { position: relative; flex-shrink: 0; line-height: 0; }
.hot-badge {
  position: absolute; top: -6px; right: -6px;
  background: var(--card); border-radius: 50%;
  width: 22px; height: 22px; display: flex; align-items: center; justify-content: center;
  font-size: .9rem; box-shadow: 0 1px 3px rgb(0 0 0 / .25);
}
.hot-badge-standalone { position: static; margin-right: .5rem; box-shadow: none; background: none; }
.item-row-text { min-width: 0; }
.item-row-info h2 { font-size: 1.05rem; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.item-row-info h2 a { color: inherit; text-decoration: none; }
.item-row-info h2 a:hover { text-decoration: underline; }
.item-row-info .description {
  margin: .1rem 0 0; color: var(--muted); font-size: .9rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.item-row-bidding {
  display: flex; flex-wrap: wrap; gap: .4rem .9rem; align-items: center;
  justify-content: flex-end;
}
.item-row-bidding .price { margin: 0; white-space: nowrap; }
.item-row-bidding .bid-count { font-size: .9rem; }

/* Compact inline bid bar */
.bid-form { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.bid-form input[type="number"] {
  width: 7.5rem; padding: .4rem .5rem; border: 1px solid var(--border);
  border-radius: 6px; font-size: .95rem;
}
.bid-form .mode-select {
  padding: .4rem .4rem; border: 1px solid var(--border); border-radius: 6px;
  font-size: .95rem; background: #fff;
}
.bid-form .max-toggle {
  margin: 0; padding: 0; border: none; font-weight: 500;
  font-size: .9rem; white-space: nowrap;
}
.bid-form .btn-primary { padding: .42rem .9rem; font-size: .95rem; }
.price { margin: .25rem 0; }
.price.big { font-size: 1.3rem; }
.price strong { color: var(--brand-dark); }
/* A little more room between "Current Bid" and the amount (Keith, 2026-09-25). */
.price [data-live="high-bid"] { margin-left: .3rem; }
.bid-count { color: var(--muted); font-size: .9rem; margin: 0; }
.price-hidden { color: var(--muted); font-style: italic; }

.item-detail { display: grid; gap: 1.5rem; grid-template-columns: 1fr; }
@media (min-width: 720px) { .item-detail { grid-template-columns: 1fr 1fr; } }
.item-media { display: flex; flex-direction: column; gap: .6rem; }
.item-media #item-main-photo { width: 100%; border-radius: 10px; border: 3px solid transparent; order: 2; }
.item-gallery { display: flex; gap: .6rem; flex-wrap: wrap; order: 1; }
.item-gallery-thumb { padding: 0; border: 2px solid transparent; border-radius: 8px; background: none; cursor: pointer; line-height: 0; }
.item-gallery-thumb img { width: 72px; height: 72px; object-fit: cover; border-radius: 6px; display: block; }
.item-gallery-thumb:hover, .item-gallery-thumb:focus-visible, .item-gallery-thumb.active { border-color: var(--action); }
/* Thumbnails move to a column beside the main photo once there's room for
   both side by side, matching a standard product-gallery layout. Below that
   width they stay a horizontal row above the photo instead (avoids
   squeezing the main image down to a sliver on a phone-width screen). */
@media (min-width: 480px) {
  .item-media { flex-direction: row; align-items: flex-start; }
  .item-media #item-main-photo { order: 2; flex: 1; min-width: 0; }
  .item-gallery { order: 1; flex-direction: column; flex-shrink: 0; flex-wrap: nowrap; }
}


.btn-primary {
  background: var(--action); border: none; color: #fff; cursor: pointer;
  padding: .55rem 1.3rem; border-radius: 8px; font-size: 1rem;
  text-decoration: none; display: inline-block;
}
.btn-primary:hover { background: var(--action-dark); }

.winning-badge, .outbid-badge {
  display: inline-block; padding: .1rem .55rem; border-radius: 999px;
  font-size: .8rem; font-weight: 600; margin-left: .4rem;
}
.winning-badge { background: #e4f5df; color: var(--success); }
.outbid-badge { background: #fee2e2; color: var(--danger); }

.bids-table { width: 100%; border-collapse: collapse; background: var(--card); border-radius: 10px; }
.bids-table th, .bids-table td { text-align: left; padding: .6rem .8rem; border-bottom: 1px solid #e5e7eb; }

/* Account pages: log in, register, password reset, my account.

   Built only from the variables an organization's accepted theme can set
   (base.html), so each charity's page background, text, link colour and fonts
   carry through. No colour here is new to the site.

   Links in the card use the organization's link colour darkened by mixing in
   black, rather than the colour as-is: the default link blue measures 3.6:1 on
   white, below AA for small text, and the darkened mix 6.3:1. It stays the
   organization's hue whatever their theme sets.

   Forms render with form.as_p, so every rule targets that markup and no
   template had to change to get it. */
.auth-card {
  max-width: 440px; margin: 2.5rem auto; background: var(--card);
  border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  border-radius: 14px; padding: 2rem 2.1rem 1.75rem;
  box-shadow: 0 1px 2px rgb(0 0 0 / .04), 0 14px 34px -20px rgb(0 0 0 / .25);
}
.auth-card h1 { margin: 0 0 .45rem; font-size: 1.65rem; line-height: 1.2; letter-spacing: -.01em; }
.auth-card h1 + p { color: var(--muted); margin: 0 0 1.4rem; }
.auth-card h1 + form, .auth-card h1 + dl { margin-top: 1.25rem; }

.auth-card form p { margin: 0 0 1rem; }
.auth-card label { display: block; font-weight: 600; font-size: .9rem; margin-bottom: .35rem; }
.auth-card input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]) {
  width: 100%; padding: .7rem .8rem; border: 1px solid var(--border); border-radius: 8px;
  font: inherit; font-size: 1rem; background: #fff; color: var(--text);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.auth-card input:focus-visible {
  outline: none; border-color: var(--action);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--action) 22%, transparent);
}
.auth-card .helptext { display: block; color: var(--muted); font-size: .85rem; line-height: 1.45; margin-top: .35rem; }
.auth-card .helptext ul { margin: .25rem 0 0; padding-left: 1.1rem; }
/* Django's password rules are a <ul> inside the help-text <span> inside a <p>.
   A list is not allowed in either, so the browser closes both and the list
   lands as a sibling of the field -- outside .helptext. Styled where it
   actually ends up. */
.auth-card form > ul:not(.errorlist) {
  color: var(--muted); font-size: .85rem; line-height: 1.45;
  margin: -.6rem 0 1rem; padding-left: 1.1rem;
}
.auth-card .errorlist {
  list-style: none; margin: 0 0 .45rem; padding: 0;
  color: var(--danger); font-size: .88rem; font-weight: 600;
}
.auth-card .errorlist.nonfield {
  margin-bottom: 1.1rem; padding: .65rem .85rem; border-radius: 8px;
  background: color-mix(in srgb, var(--danger) 8%, #fff);
}

/* A checkbox beside its label, not a full-width box under it. :has() rather
   than a template change, because as_p puts the label first. */
.auth-card p:has(> input[type="checkbox"]) {
  display: flex; flex-wrap: wrap; align-items: center; gap: .3rem .6rem;
}
.auth-card p:has(> input[type="checkbox"]) input { order: 1; width: 1.1rem; height: 1.1rem; margin: 0; accent-color: var(--action); }
.auth-card p:has(> input[type="checkbox"]) label { order: 2; margin: 0; font-weight: 500; }
.auth-card p:has(> input[type="checkbox"]) .helptext { order: 3; flex-basis: 100%; margin-top: 0; }

.auth-card form .btn-primary { width: 100%; padding: .75rem 1rem; font-weight: 600; margin-top: .35rem; }
.auth-card form ~ p { text-align: center; color: var(--muted); font-size: .93rem; margin: .55rem 0 0; }
.auth-card form + p {
  margin-top: 1.4rem; padding-top: 1.2rem;
  border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
.auth-card a:not(.btn-primary), .auth-card .linklike {
  color: color-mix(in srgb, var(--link) 72%, #000); font-weight: 600;
}
.auth-card a:not(.btn-primary) { text-decoration: none; }
.auth-card a:not(.btn-primary):hover { text-decoration: underline; }
.auth-card a:focus-visible, .auth-card button:focus-visible {
  outline: 2px solid var(--action); outline-offset: 2px; border-radius: 3px;
}

/* My Account: a label/value list rather than run-on text. */
.auth-card dl { margin: 0; }
.auth-card dt {
  font-size: .76rem; font-weight: 600; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted);
}
.auth-card dd { margin: .2rem 0 1.05rem; }
.auth-card dd .inline-form { display: block; margin-top: .3rem; }
.auth-card dl + p { margin-top: 1.4rem; }

@media (max-width: 520px) {
  .auth-card { margin: 1.25rem .75rem; padding: 1.5rem 1.25rem 1.35rem; }
}

/* SMS toast (demo) — phone-notification banners, top right. Starts below
   the fixed header (see body's padding-top) so it never covers the admin
   nav links, rather than sitting at the very top of the viewport. */
.sms-toast-stack {
  position: fixed; top: 140px; right: 1rem; z-index: 900;
  display: flex; flex-direction: column; gap: .6rem;
  width: min(360px, calc(100vw - 2rem));
}
.sms-toast {
  background: rgba(28, 28, 30, .96); color: #fff;
  border-radius: 14px; padding: .7rem .9rem;
  box-shadow: 0 8px 28px rgb(0 0 0 / .35);
  transform: translateX(120%); opacity: 0;
  transition: transform .35s cubic-bezier(.2, .9, .3, 1.1), opacity .35s;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
.sms-toast.show { transform: translateX(0); opacity: 1; }
.sms-toast-header {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: .3rem;
}
.sms-toast-app { font-weight: 700; font-size: .85rem; letter-spacing: .02em; }
.sms-toast-to { color: #9ca3af; font-size: .75rem; }
.sms-toast-body { font-size: .92rem; line-height: 1.35; overflow-wrap: break-word; }
.sms-toast-link { color: #6cb2ff; }

.post-auction { text-align: center; padding: 3rem 1rem; }
.hint { color: var(--muted); }

/* The SMS opt-in notice on the account page. Same treatment as the same
   notice's help text on the registration form (.auth-card .helptext above),
   because it is the same wording being agreed to in both places. */
/* The mobile-number field on the account page: an inline edit next to the
   value, not a form that looks like a second page. */
.phone-form { display: inline-flex; gap: .4rem; align-items: center; margin-left: .5rem; }
.phone-form input { width: 12rem; padding: .25rem .4rem; font-size: .9rem; }

.sms-notice {
  display: block; color: var(--muted); font-size: .85rem; line-height: 1.45;
  margin: .5rem 0 .75rem; max-width: 62ch;
}

/* Analytics dashboard */
.kpi-row { display: flex; flex-wrap: wrap; gap: 1rem; margin: 1rem 0 1.5rem; }
.kpi-tile {
  background: var(--card); border-radius: 10px; padding: 1rem 1.5rem;
  box-shadow: 0 1px 3px rgb(0 0 0 / .1); min-width: 140px; text-align: center;
}
.kpi-value { display: block; font-size: 2rem; font-weight: 700; color: var(--brand-dark); }
.kpi-label { display: block; color: var(--muted); font-size: .85rem; margin-top: .2rem; }

.chart-card {
  background: var(--card); border-radius: 10px; padding: 1.25rem 1.5rem;
  box-shadow: 0 1px 3px rgb(0 0 0 / .1); margin-bottom: 1.25rem;
}
.chart-card h2 { margin-top: 0; font-size: 1.1rem; }
.chart-svg-wrap { width: 100%; overflow-x: auto; }
.chart-svg { width: 100%; height: 160px; display: block; }
.chart-area { fill: color-mix(in srgb, var(--action) 18%, transparent); stroke: none; }
.chart-line { fill: none; stroke: var(--action); stroke-width: 2; }

.bars { display: flex; flex-direction: column; gap: .6rem; }
.bar-row { display: grid; grid-template-columns: 160px 1fr 160px; gap: .75rem; align-items: center; }
.bar-label { font-size: .9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bar-track { background: var(--bg); border-radius: 999px; height: 10px; overflow: hidden; }
.bar-fill { background: var(--action); height: 100%; border-radius: 999px; transition: width .4s ease; }
.bar-value { font-size: .85rem; color: var(--muted); white-space: nowrap; }
@media (max-width: 640px) {
  .bar-row { grid-template-columns: 1fr; gap: .25rem; }
}

/* Donut charts (category / geography breakdowns) */
.chart-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; }
.chart-row .chart-card { margin-bottom: 0; }
@media (max-width: 800px) {
  .chart-row { grid-template-columns: 1fr; }
}
.donut-wrap { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: center; }
.donut-svg { width: 200px; height: 200px; flex-shrink: 0; }
.donut-total { font-size: 2rem; font-weight: 700; fill: var(--text); }
.donut-total-label { font-size: .7rem; fill: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.donut-legend { list-style: none; margin: 0; padding: 0; flex: 1 1 220px; min-width: 200px; }
.donut-legend li { display: flex; align-items: center; gap: .5rem; font-size: .9rem; padding: .3rem 0; }
.donut-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }

/* Big Board — venue TV/projector kiosk. Deliberately a different (dark,
   fullscreen, oversized-type) register from the rest of the light, compact
   site: this fills an entire screen from across a room, not a laptop tab. */
body.big-board {
  margin: 0; min-height: 100vh; background: #0b0b0f; color: #fff;
  font-family: "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  display: flex; flex-direction: column; padding: 2.5rem 3.5rem;
}
.big-board-header { display: flex; align-items: center; gap: 1.5rem; margin-bottom: 2rem; }
.big-board-header img { height: 56px; background: #fff; padding: 6px 14px; border-radius: 8px; }
.big-board-header h1 { font-size: 2.1rem; margin: 0; font-weight: 700; letter-spacing: .01em; }
.big-board-list { flex: 1; display: flex; flex-direction: column; gap: 1rem; overflow-y: auto; }
.big-board-row {
  display: grid; grid-template-columns: 70px 1fr 260px 180px; align-items: center;
  gap: 1.5rem; background: rgba(255, 255, 255, .07); border-radius: 14px;
  padding: 1.4rem 2rem;
}
.bb-rank { font-size: 2.2rem; font-weight: 700; color: var(--accent); text-align: center; }
.bb-name { font-size: 1.9rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bb-flame { font-size: 1.4rem; }
.bb-bid { font-size: 2.6rem; font-weight: 700; color: #4ade80; text-align: right; white-space: nowrap; }
.bb-count { font-size: 1.15rem; color: rgba(255, 255, 255, .55); text-align: right; white-space: nowrap; }
.big-board-empty { font-size: 1.6rem; color: rgba(255, 255, 255, .55); text-align: center; margin-top: 4rem; }
@keyframes bb-pulse {
  from { box-shadow: 0 0 0 4px rgba(255, 159, 26, .95); }
  to { box-shadow: 0 0 0 0 rgba(255, 159, 26, 0); }
}
.bb-pulse { animation: bb-pulse 1.3s ease-out; }
@media (max-width: 900px) {
  .big-board-row { grid-template-columns: 50px 1fr; grid-template-areas: "rank name" "rank bid" "rank count"; }
  .bb-bid, .bb-count { text-align: left; }
}

/* Theme review: comparing what a site uses now with what was read off the
   organization's own website. */
.theme-table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
.theme-table th, .theme-table td {
  text-align: left; padding: .55rem .6rem; border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
.theme-table th { font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.swatch {
  display: inline-block; width: 1.1rem; height: 1.1rem; border-radius: 4px;
  border: 1px solid var(--border); vertical-align: -.2rem; margin-right: .35rem;
}
.theme-note {
  background: #fef3c7; color: #78350f; border-radius: 8px;
  padding: .7rem .9rem; margin: 1rem 0;
}

/* The way back into settings, from the public site. Marked out because it
   leaves the auction for the administration area, which is a different place
   with different navigation. */
.nav-admin { font-weight: 700; }
.nav-admin::before { content: "⚙ "; }

/* Plan and cancellation screens. */
.plan-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; margin: 1.5rem 0; }
.plan { border: 1px solid var(--border); border-radius: 10px; padding: 1.1rem 1.25rem; }
.plan--current { border-color: var(--accent); border-width: 2px; }
.plan h2, .plan h3 { margin-top: 0; }
.plan__price { font-size: 1.5rem; font-weight: 700; margin: .2rem 0 .8rem; }

/* The Account page: the person's details as cards in the plan section's
   style, side by side where there is room (Keith, 2026-09-25). The field
   labels and link weight are the ones this page had as a login-style card. */
/* Each card its own height: stretched to the tallest (Alerts, with the full
   text-message consent wording), the others were mostly empty. */
.account-grid { align-items: start; }
.account-grid h2 { font-size: 1.1rem; }
.account-grid dl { margin: 0; }
.account-grid dt {
  font-size: .76rem; font-weight: 600; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted);
}
.account-grid dd { margin: .2rem 0 1rem; overflow-wrap: anywhere; }
.account-grid dd:last-child { margin-bottom: 0; }
.account-grid dd .inline-form { display: block; margin-top: .3rem; }
.account-grid .phone-form { display: flex; gap: .4rem; margin: .4rem 0 0; }
.account-grid .phone-form input { width: auto; flex: 1; min-width: 0; }
.account-grid .linklike, .account-page > p a {
  color: color-mix(in srgb, var(--link) 72%, #000); font-weight: 600;
}
.account-grid .linklike { text-align: left; }
.account-plan {
  margin-top: 2rem; padding-top: 1.5rem;
  border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  /* Arriving at #plan (the old Plan & Billing link) must clear what is pinned
     at the top: the sticky header on a desktop, which measured up to 174px
     with a logo, and only the countdown on a phone. */
  scroll-margin-top: 11.5rem;
}
@media (max-width: 760px) {
  .account-plan { scroll-margin-top: 3.5rem; }
}
.account-plan > h2 { margin-top: 0; }
.plan__current { font-weight: 700; color: var(--muted); }
.plan ul { padding-left: 1.1rem; }
.plan li { margin-bottom: .35rem; }

/* Loud on purpose: this is the one screen where skimming costs someone their
   configuration, weeks later, when nothing can be recovered. */
.danger-panel { background: #fef2f2; border: 1px solid #fecaca; color: #7f1d1d; border-radius: 10px; padding: 1rem 1.25rem; margin: 1.25rem 0; }
.danger-panel h2 { margin-top: 0; }
.rescue-panel { background: #ecfdf5; border: 1px solid #a7f3d0; color: #065f46; border-radius: 10px; padding: 1rem 1.25rem; margin: 1.25rem 0; }
.rescue-panel h2 { margin-top: 0; }
.btn-danger { background: #b91c1c; color: #fff; border: 0; border-radius: 8px; padding: .6rem 1.1rem; font-weight: 700; cursor: pointer; }
.cancel-link { margin-top: 2rem; }
.cancel-link a { color: var(--muted); font-size: .9rem; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

/* The cancellation confirmation. Wide enough to read the whole sentence back:
   a default input scrolls it out of view as you type, on the one screen where
   reading carefully is the point. */
.confirm-phrase {
  display: block; width: 100%; max-width: 34rem; box-sizing: border-box;
  padding: .7rem .8rem; font-size: 1.05rem; margin: .5rem 0 1rem;
  border: 2px solid var(--border); border-radius: 8px;
}
.confirm-phrase:focus { outline: 2px solid #b91c1c; outline-offset: 1px; }

/* --- Bidder UX notes, 2026-09-21 (plans/bidder-ux-notes-findings.md) ----
   Appended rather than edited in place: develop carries styling work on
   earlier lines of this file, and additions here merge cleanly with it. */

/* Note 4: the winning/outbid badge now leads the price line, so its spacing
   flips. Scoped to the slot, so My Bids' trailing badges are unchanged. */
.standing .winning-badge, .standing .outbid-badge { margin-left: 0; margin-right: .45rem; }

/* Note 4, continued: a badge makes the bidding side of a row wider, and at
   the original 320px floor for the item name and description a row with a
   badge wrapped its bid controls onto a second line while its neighbours did
   not (seen at 1200px). The text column already truncates with an ellipsis,
   so letting it give way sooner keeps rows alike. */
.item-row-info { flex-basis: 240px; }

/* Note 3: a large, obvious way back from an item to the auction. */
.back-to-auction { margin: 0 0 1rem; }
.btn-back-to-auction { font-size: 1.05rem; font-weight: 600; padding: .7rem 1.4rem; }
@media (max-width: 600px) {
  .btn-back-to-auction { display: block; text-align: center; }
}

/* Note 3: the anchor it links to lands with a little room above the row. */
.item-row { scroll-margin-top: 1rem; }
/* On a phone the countdown is pinned at the top (see .countdown), so clear it too. */
@media (max-width: 760px) {
  .item-row { scroll-margin-top: 3.5rem; }
}

/* Note 5: collapsible category sections (native <details>). */
.category-section { margin-top: 1.25rem; }
.category-section:first-of-type { margin-top: .5rem; }
.category-section > summary.category-heading {
  margin: 0 0 .5rem; cursor: pointer; list-style: none;
  display: flex; align-items: baseline; gap: .6rem;
  font-size: 1.5rem; font-weight: 700;
}
.category-section > summary::-webkit-details-marker { display: none; }
.category-section > summary::before {
  content: "▸"; display: inline-block; width: 1em; transition: transform .15s ease;
}
.category-section[open] > summary::before { transform: rotate(90deg); }
.category-section > summary:focus-visible { outline: 2px solid var(--action); outline-offset: 3px; }
.category-count { font-size: .9rem; font-weight: 400; color: var(--muted); }
@media (prefers-reduced-motion: reduce) {
  .category-section > summary::before { transition: none; }
}

/* Cancel link and phrase in red (Keith, 2026-09-22). #b91c1c is .btn-danger's
   red: 6.3:1 on white, readable as text as well as on the button. The link
   stays small and last on the page; red marks it as destructive, not as a
   call to action. */
.danger-text, .cancel-link a.danger-text { color: #b91c1c; font-weight: 600; }
.btn-danger[disabled] { opacity: .45; cursor: not-allowed; }

/* Back to Auction: green while the auction runs (Keith, 2026-09-22).
   #2e7d32 rather than the site's #44b02b: white text on #44b02b is ~2.8:1,
   too faint for a label; on #2e7d32 it is ~5.1:1. */
.btn-back-to-auction--live { background: #2e7d32; }
.btn-back-to-auction--live:hover { background: #1b5e20; }

/* The bidder's own bid, max bid and tiebreaker on an item page. */
.my-bid-summary { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: .6rem .9rem; margin: .5rem 0 1rem; }
.my-bid-summary h3 { margin: 0 0 .3rem; font-size: 1rem; }
.my-bid-summary dl { display: grid; grid-template-columns: max-content 1fr; gap: .25rem 1rem; margin: 0; }
.my-bid-summary dt { color: var(--muted); }
.my-bid-summary dd { margin: 0; font-variant-numeric: tabular-nums; }
.my-bid-summary .hint { display: block; font-size: .85rem; }

/* Listing rows: the item name and its description sit together beside the
   photo (Keith, 2026-09-22). The row's name is an <h3>, but the older rules
   above target .item-row-info h2, so the h3 kept the browser's default
   heading margins (~1em above and below), which pushed the description down
   below the photo. Only the spacing is fixed here; the name keeps its
   current size and link style. */
.item-row-info h3 { margin: 0; line-height: 1.25; }
/* Up to two lines of description before it's cut off, rather than one:
   the older rule cut it to a single line with "...", which at narrower
   widths (bid controls beside it) hid most of it. */
.item-row-info .description {
  margin-top: .2rem; white-space: normal;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
  overflow: hidden;
}

/* Bid errors on the auction page itself (Keith, 2026-09-22): the box at
   fault turns red and the reason appears in red under the bid fields.
   #b91c1c is .btn-danger's red (6.3:1 on white). */
.bid-form input.field-error { border-color: #b91c1c; background: #fef2f2; box-shadow: 0 0 0 1px #b91c1c; }
.bid-form input.field-error:focus { outline: 2px solid #b91c1c; outline-offset: 1px; }
/* The fields are their own row (.bid-fields) and the message sits below it,
   outside that row. Inside the row, the message's width and its gap were
   added to the form's width (a wrapping flex row sums its items), so the form
   grew, the fields jumped left out of line with every other row and "Current
   bid" dropped (Keith's screenshot, 2026-09-22). contain: inline-size keeps a
   long message from widening the form; it wraps under the fields instead. */
.bid-form { display: block; }
.bid-fields { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.bid-feedback { margin: .3rem 0 0; font-size: .9rem; font-weight: 600; contain: inline-size; }
/* Keep "Current bid" level with the bid fields' text, not with the middle of
   a form that grows a line when a message appears. */
.item-row-bidding { align-items: baseline; }
.bid-feedback--error { color: #b91c1c; }
.bid-feedback--ok { color: #2e7d32; }
.bid-feedback--warn { color: #92400e; }
/* The tiebreaker is required whenever "+ max bid" is ticked. */
.required-mark { color: #b91c1c; font-weight: 700; margin-left: .1rem; }

/* Category sections: + when closed, − when open, instead of the arrow
   (Keith, 2026-09-22). Overrides the arrow and its rotation above. The
   heading can't be text-selected, so a double-click toggles the section
   without highlighting the name. */
.category-section > summary { user-select: none; -webkit-user-select: none; }
.category-section > summary::before {
  content: "+"; transform: none; transition: none;
  width: 1em; text-align: center;
  font-weight: 400; /* not bold (Keith, 2026-09-22); the heading itself is 700 */
}
.category-section[open] > summary::before { content: "\2212"; transform: none; }

/* Phones: the auction listing, item page and header (Keith, 2026-09-25).
   Last in the file so these win over the row and form rules above. */
@media (max-width: 760px) {
  /* The organization's logo and auction name, centred, and under them the
     account line and the links, centred too (Keith, 2026-09-25). */
  .site-header .brand { width: 100%; align-items: center; text-align: center; }
  .site-header nav { width: 100%; align-items: center; }
  .site-header nav .logged-in-as { text-align: center; }
  .site-header nav .nav-links { justify-content: center; }

  /* The photo's top level with the item's name, rather than centred against
     the name and description together. */
  .item-row-info { align-items: flex-start; }

  /* Winning / outbid on its own line above the price, and both flush left
     with the photo, on the listing and the item page. The standing slot is
     empty when there is nothing to say; auction.js fills it live, and :empty
     follows. */
  .item-row-bidding { flex: 1 1 100%; flex-direction: column; align-items: stretch; }
  .item-row-bidding .price { white-space: normal; }
  .standing:not(:empty) { display: block; margin-bottom: .35rem; }
  .standing:empty { display: none; }

  /* The bid fields in rows: bid type and amount; "+ max bid"; the max and
     tiebreaker once that is ticked; then Place bid, full width, last. Placed
     by row so each field keeps its line when others are hidden -- JS hides
     the amount for "Max bid only" and the max fields until ticked, and a
     hidden field takes no space. */
  .bid-fields { display: grid; grid-template-columns: 1fr 1fr; column-gap: .5rem; row-gap: 0; align-items: center; }
  /* Rows spaced by margins, not row-gap: a row whose fields are all hidden
     then takes no space at all, where a gap either side of it doubled the
     space above Place bid. */
  .bid-form .bid-fields > * { grid-column: 1 / -1; margin: 0 0 .5rem; }
  .bid-form .bid-fields > .btn-primary { margin-bottom: 0; }
  .bid-fields .mode-select { grid-row: 1; grid-column: 1; }
  .bid-fields [data-pane="fixed"], .bid-fields [data-pane="increment"] { grid-row: 1; grid-column: 2; }
  .bid-fields .max-toggle { grid-row: 2; }
  .bid-fields [data-pane="max"] { grid-row: 3; grid-column: 1; }
  .bid-fields [data-pane="max2"] { grid-row: 3; grid-column: 2; }
  /* The red asterisk, inside the tiebreaker's box at its right edge. */
  .bid-fields .required-mark {
    grid-row: 3; grid-column: 2; justify-self: end; margin-right: .6rem; pointer-events: none;
  }
  .bid-fields .btn-primary { grid-row: 4; padding: .6rem 1rem; }
  .bid-form input[type="number"], .bid-form .mode-select { width: 100%; min-width: 0; }

  /* A category's name has its line to itself, and the item count sits under
     the name (not under the +/- marker). 1.2rem keeps the longest of the
     test store's names, "Dining & Entertainment", on one line at 375px; a
     longer name still wraps rather than being cut off. */
  .category-section > summary.category-heading { flex-wrap: wrap; row-gap: .1rem; font-size: 1.2rem; }
  .category-section > summary .category-count { flex-basis: 100%; padding-left: calc(1.2rem + .6rem); }

  /* Photo and name side by side, then the description across the whole row,
     then the bidding. The text wrapper steps aside (display: contents) so the
     name and description can be placed on the row's grid directly. */
  .item-row-info { display: grid; grid-template-columns: auto 1fr; column-gap: .75rem; row-gap: .45rem; }
  .item-row-text { display: contents; }
  .item-row-info h3 { grid-column: 2; }
  .item-row-info:not(:has(.item-thumb, .hot-badge-standalone)) h3 { grid-column: 1 / -1; }
  .item-row-info .description { grid-column: 1 / -1; margin: 0; }
}

/* "+ max bid" centred on its checkbox, at every width (Keith, 2026-09-25):
   baseline alignment left the text 1.5px low. */
.bid-form .max-toggle { display: inline-flex; align-items: center; gap: .35rem; }
.bid-form .max-toggle input { margin: 0; }
