/* Ante, app-specific styles.
 *
 * Loads AFTER tokens.css and components.css, which are byte-for-byte copies of
 * AntSoft's. Everything shared lives there and is edited there. This file holds only
 * what is particular to a billing portal: the balance hero, the threshold panel, the
 * view-as control, the FAQ, and the roster's stacked-card fallback.
 *
 * The charter from the AntSoft blueprint applies here unchanged: AAA contrast, 44px
 * targets, state by icon and word and border rather than colour, three theme states,
 * 320px reflow, reduced motion honoured. Section rhythm over card soup, and one
 * bordered box per page reserved for genuinely tabular data.
 */

/* --- Layout -------------------------------------------------------------- */
/* .wrap comes from components.css at 72rem. Reading pages want a narrower measure;
   the roster and the officer dashboard want the full width. */
/* Width lives in --page-max (components.css) and nowhere else. These selectors are more
   specific than .wrap, so hardcoding a number here would silently win over the variable and
   put the chrome and the content back out of step. */
main.wrap { max-width: var(--page-max); padding-top: 1.2rem; }

.loading { color: var(--muted); font-size: 1.02rem; padding: 2rem 0; }

/* Section rhythm: a hairline and a small uppercase label, instead of stacking
   bordered cards. This is the single biggest visual change from the old design. */
section { margin-top: 2.6rem; }
.zone {
  font-family: var(--mono); font-size: .7rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted);
  border-top: 1px solid var(--line); padding-top: .9rem; margin: 0 0 .35rem;
  /* The generic p rule caps text at 70ch for readability, but this element IS the
     section divider, so its rule has to run the full width of the column. */
  max-width: none;
}
h2 { font-size: clamp(1.3rem, 3vw, 1.7rem); color: var(--blue); margin: 0 0 .6rem; }
h3 { font-size: 1.05rem; margin: 1.3rem 0 .35rem; }
p { margin: 0 0 .9rem; max-width: 70ch; color: var(--body); }
.note { font-size: .85rem; color: var(--muted); max-width: 70ch; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* --- Header additions ---------------------------------------------------- */
.brand .brand-sub { color: var(--muted); font-size: .74rem; }
/* The disc holds a letter now rather than an emoji, so it needs the display face and a
   weight to match the wordmark sitting next to it. */
.brand .peter { font-family: var(--display); font-weight: 800; font-size: 1.02rem;
  letter-spacing: -.02em; }
/* The subtitle is out of the header layout at EVERY width, not just narrow ones.
   Hiding it below 90rem and showing it above meant the brand grew by ~250px on a large
   monitor, which fits the 78rem pages and wraps the 62rem ones: index's header was 119px
   on two rows while accounts was 74px on one, so moving between them shunted the page.
   Visually hidden rather than display:none, so the brand link still reads as
   "Ante, UCI Office of Information Technology" to a screen reader. The footer shows the
   org in text on every page. */
.brand .brand-sub {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}
.brand { flex: 0 1 auto; min-width: 0; }
.hdr-in { row-gap: .5rem; }

/* The header is frozen at EVERY width. That only works because everything except the
   wordmark folds behind one button below 63rem: unfolded it wrapped to five rows and
   314px, and a sticky block that tall leaves a phone with nothing to read. Measured
   across 14 widths, not guessed. */
.burger {
  display: none; width: 2.9rem; height: 2.9rem; flex: none; cursor: pointer;
  background: var(--surface); color: var(--ink);
  border: 2px solid var(--line); border-radius: var(--r-sm, .5rem);
  align-items: center; justify-content: center;
}
.burger:hover { border-color: var(--blue); }
.burger:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
/* Three bars drawn rather than typed: the U+2261 character sits off-centre in several
   of the fallback faces. */
.burger-bars, .burger-bars::before, .burger-bars::after {
  display: block; width: 1.25rem; height: 2px; background: currentColor; border-radius: 2px;
}
.burger-bars { position: relative; }
.burger-bars::before, .burger-bars::after { content: ""; position: absolute; left: 0; }
.burger-bars::before { top: -6px; }
.burger-bars::after { top: 6px; }
.burger[aria-expanded="true"] { background: var(--blue); color: #fff; border-color: var(--blue); }

.hdr-menu { display: flex; align-items: center; gap: .7rem; flex-wrap: wrap; }

/* Above the collapse breakpoint the nav is centred rather than shoved against the
   controls. The single left spacer is dropped and the menu takes the remaining width, so
   auto margins on the tabs balance the space either side of them. Below the breakpoint the
   whole menu is a stacked panel and none of this applies. */
@media (min-width: 70.01rem) {
  .hdr-sp { display: none; }
  .hdr-menu { flex: 1 1 auto; justify-content: flex-end; }
  .hdr-menu .seg { margin-inline: auto; }
  /* A wrap here is a silent second row that changes the height of a sticky header. Make it
     impossible: below this breakpoint everything folds behind the burger instead. */
  .hdr-in, .hdr-menu { flex-wrap: nowrap; }
  .hdr-menu .seg { flex-wrap: nowrap; }
}

@media (max-width: 70rem) {
  .burger { display: inline-flex; }
  .hdr-menu {
    display: none; position: absolute; left: 0; right: 0; top: 100%;
    flex-direction: column; align-items: stretch; gap: .8rem;
    background: var(--surface); border-bottom: 3px solid var(--gold);
    box-shadow: var(--sh-lg); padding: 1rem; max-height: 80vh; overflow-y: auto;
  }
  .hdr-menu.open { display: flex; }
  /* Inside the panel these are stacked rows, not a squeezed toolbar. */
  .hdr-menu .seg { flex-direction: column; gap: .2rem; }
  .hdr-menu .seg a {
    text-align: left; padding: .75rem 1rem; min-height: 2.75rem;
    border-bottom: none; border-left: 3px solid transparent;
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
  }
  .hdr-menu .seg a:hover { border-bottom: none; border-left-color: var(--line); }
  .hdr-menu .seg [aria-current="true"] {
    border-bottom: none; border-left-color: var(--blue); background: var(--surface-2);
  }
  .hdr-menu .imp-field { width: 100%; }
  .hdr-menu .imp-field input { width: 100%; }
  .hdr-menu .imp-field .viewas-results { min-width: 0; left: 0; right: 0; }
  .hdr-menu .a11y { justify-content: flex-start; }
  /* An icon alone in a full-width row says nothing. In the panel these become ordinary
     labelled rows, taking the label from the title the button already carries (the theme
     button rewrites its own title, so it stays accurate). */
  .hdr-menu .iconbtn {
    /* display:flex is set here, not assumed: a button defaults to centring its text, so
       justify-content and gap were both being ignored. */
    display: flex; align-items: center; justify-content: flex-start; text-align: left;
    width: 100%; gap: .6rem; padding: 0 1rem; min-height: 2.75rem;
  }
  .hdr-menu .iconbtn::after {
    content: attr(title); font-family: var(--sans); font-size: .95rem; font-weight: 600;
  }
}

/* --- Hero ---------------------------------------------------------------- */
/* The hero introduces the app and says nothing about this person's money. The balance
   sits in its own panel below, where it can be bigger and properly labelled. */
.hero h1 b { color: var(--gold); }
/* The product name leads the hero sentence, in the same gold the headline uses, so the
   name and the headline read as one voice. Gold on the navy hero is about 10:1, so this is
   emphasis that does not cost contrast. */
.hero-lead b { color: var(--gold); font-weight: 800; }
.hero .btn-row { margin-top: 1rem; }
/* AntSoft's hero carries a faint anchor. Ours carries the A from the favicon and the
   brand disc, so the same mark appears in the tab, the header and the page. Decorative
   only, and at 7% white it changes no text contrast. */
.hero::after {
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="white" fill-rule="evenodd" d="M32 6 L 57 66 L 42.5 66 L 38.3 55 L 25.7 55 L 21.5 66 L 7 66 Z M32 27 L 37 44 L 27 44 Z"/></svg>');
}

/* --- Balance statement --------------------------------------------------- */
/* The one number on the page that has to be readable across the room. Gold rail for a
   balance owed, green once there is nothing to pay, red-edged when there is money with
   nowhere to post it: the state is in the rail, the label AND the sentence, never the
   colour on its own. */
.statement {
  background: var(--surface); border: 2px solid var(--line);
  border-left: 10px solid var(--gold); border-radius: var(--r-md);
  padding: 1.1rem 1.3rem 1.2rem; box-shadow: var(--sh-md);
}
.statement.settled { border-left-color: var(--good); }
.statement.unbilled { border-left-color: var(--bad); }
.statement-period {
  font-size: .74rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 .1rem;
}
.statement-amount {
  font-family: var(--mono); font-weight: 700; color: var(--ink);
  font-size: clamp(2.4rem, 8vw, 3.6rem); line-height: 1.05; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; margin: 0 0 .35rem; overflow-wrap: anywhere;
}
.statement-sub { margin: 0; font-size: 1rem; color: var(--body); max-width: 58ch; }
.statement-who { margin: .7rem 0 0; font-size: .85rem; color: var(--muted); }
.statement .btn-row { margin-top: .9rem; }
.hero-field { margin: 1rem 0 0; max-width: 18rem; }
.hero-field label { color: #d7e6f2; }
.hero-field select {
  background: rgba(255, 255, 255, .96); color: var(--ink);
  border-color: rgba(255, 255, 255, .55);
}

/* --- Jump nav ------------------------------------------------------------- */
/* The page is long and the answer someone wants is usually one specific section, so
   give them the list rather than a scroll. */
.jump { display: flex; flex-wrap: wrap; gap: .45rem; margin: 1rem 0 0; }
.jump a {
  display: inline-flex; align-items: center; min-height: 2.4rem;
  padding: .35rem .8rem; border-radius: var(--r-full);
  background: var(--surface); border: 2px solid var(--line);
  color: var(--ink); text-decoration: none; font-size: .86rem; font-weight: 600;
}
.jump a:hover { border-color: var(--blue); color: var(--link); }
.jump a:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }

/* The header is fixed in place, so an anchor has to stop below it or the heading you
   jumped to is the one thing hidden. --hdr-h is measured by ui.js, because the header
   grows a row while viewing as someone else. The fallback covers the moment before the
   script runs. */
:target, h2[id], h3[id], section[id] { scroll-margin-top: calc(var(--hdr-h, 5rem) + 1.2rem); }

.totop {
  position: fixed; right: 1rem; bottom: 1rem; z-index: 60;
  display: inline-flex; align-items: center; gap: .35rem; min-height: 2.75rem;
  padding: .5rem 1rem; border-radius: var(--r-full);
  background: var(--navy); color: #fff; text-decoration: none;
  border: 2px solid var(--navy); box-shadow: var(--sh-lg); font-size: .88rem; font-weight: 700;
}
.totop:hover { background: var(--blue); border-color: var(--blue); }
.totop:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }
.totop[hidden] { display: none; }

/* --- Tiles --------------------------------------------------------------- */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); gap: .85rem; }
.tile-card {
  background: var(--surface); border: 2px solid var(--line);
  border-left: 8px solid var(--blue); border-radius: var(--r-md);
  padding: .95rem 1.05rem; box-shadow: var(--sh-md);
}
.tile-card.hero-tile { border-left-color: var(--gold); }
.tile-label {
  font-size: .74rem; color: var(--muted); text-transform: uppercase;
  letter-spacing: .06em; font-weight: 700; margin: 0 0 .2rem;
}
.tile-value {
  font-family: var(--mono); font-size: clamp(1.4rem, 3.6vw, 1.9rem); font-weight: 700;
  line-height: 1.1; margin: 0; overflow-wrap: anywhere; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.tile-note { font-size: .8rem; color: var(--muted); margin: .3rem 0 0; }

/* The info mark. U+2139 is a tiny misaligned glyph in some faces and a blue emoji in
   others, so it is drawn instead. */
.cg-i {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.15em; height: 1.15em; border: 2px solid currentColor; border-radius: 50%;
  font-family: var(--sans); font-size: .82em; font-weight: 800; font-style: italic;
  line-height: 1; vertical-align: -.12em;
}

/* --- Where it gets charged ------------------------------------------------ */
/* One lead line for the section, state pill and sentence together, instead of the three
   separate boxes this used to be. */
.acct-lead { display: flex; align-items: flex-start; gap: .6rem; flex-wrap: wrap; max-width: 72ch; }
.acct-lead-text { flex: 1 1 22rem; }


/* The suggestion card.
   It was surface-on-surface with a hairline border, sitting directly above a form built from
   the same surface and the same hairline, so it read as one more input rather than as an
   offer. It is tinted now, with a gold top rule, which is enough to say "this is different"
   without shouting. The account number is the largest thing in it because the number is what
   is being decided. And the caution that used to be a SECOND box underneath is small print
   inside this one: one decision, one object. */
.acct-one {
  position: relative;
  max-width: 34rem;
  margin: 0 0 1.1rem;
  padding: 1rem 1.15rem 1.1rem;
  background: var(--info-bg);
  border: 1px solid var(--line);
  border-top: 4px solid var(--gold);
  border-radius: var(--r-md);
  box-shadow: var(--sh-md);
}
.acct-one-eyebrow {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin: 0 0 .8rem; font-size: .85rem; color: var(--body);
}
.acct-one-badge {
  flex: none;
  padding: .12rem .5rem;
  border-radius: var(--r-full);
  background: var(--navy); color: #fff;
  font-family: var(--mono); font-size: .7rem; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
}
.acct-one-field { max-width: 22rem; margin: 0 0 .9rem; }
.acct-one-field select { background: var(--surface); }

.acct-one-detail { margin: 0 0 1rem; }
/* Selectable text, not just an <option> label: not being able to copy the number was a
   complaint once already. */
.acct-one-num {
  margin: 0;
  font-size: 1.75rem; font-weight: 800; line-height: 1.1;
  letter-spacing: .04em; color: var(--ink);
}
.acct-one-num::selection { background: var(--gold); color: #2b2200; }
.acct-one-facts {
  display: grid; grid-template-columns: auto 1fr; gap: .25rem .8rem;
  /* The number used to have a subtitle beneath it; now the first fact does that job, so the
     gap moves here. */
  margin: .7rem 0 0; padding-top: .7rem; border-top: 1px solid var(--line);
  font-size: .9rem;
}
.acct-one-facts dt {
  color: var(--muted); font-family: var(--mono); font-size: .76rem;
  letter-spacing: .04em; text-transform: uppercase; padding-top: .18rem;
  white-space: nowrap;
}
.acct-one-facts dd { margin: 0; color: var(--body); }

.acct-one-foot { display: flex; flex-direction: column; gap: .7rem; }
.acct-one-cta { min-height: 2.75rem; align-self: flex-start; }
/* Small print, not a warning box. Quiet enough to be secondary, dark enough to read: this
   is --muted's job and it clears AAA on the tint. */
.acct-one-note {
  display: flex; gap: .45rem; align-items: flex-start;
  margin: 0; font-size: .86rem; line-height: 1.5; color: var(--muted);
  max-width: 46ch;
}
.acct-one-note .cg-i { flex: none; margin-top: .15rem; }

@media (max-width: 30rem) {
  .acct-one-facts { grid-template-columns: 1fr; gap: 0; }
  .acct-one-facts dd { margin-bottom: .5rem; }
  .acct-one-cta { width: 100%; align-self: stretch; }
  .acct-one-num { font-size: 1.5rem; }
}

/* Static, above everything in the section, because it is true with or without a suggestion. */
.acct-intro { color: var(--body); max-width: 70ch; margin: 0 0 .9rem; }
}

/* --- Forms --------------------------------------------------------------- */
.grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); gap: 0 1rem; }
.field .opt { font-weight: 500; color: var(--muted); font-size: .78rem; }
/* Four parts of one accounting string, so one row. The account gets more width than the
   three optional codes because it is the one that is always filled in and the one people
   read back to check. Below 52rem they stack, which is the only honest thing to do with
   four labelled inputs on a phone. */
.grid-4 {
  display: grid; gap: 0 1rem;
  grid-template-columns: minmax(11rem, 1.4fr) repeat(3, minmax(7rem, 1fr));
  align-items: start;
}
@media (max-width: 52rem) { .grid-4 { grid-template-columns: 1fr; } }
.field-acct input { font-family: var(--mono); font-size: 1.05rem;
  letter-spacing: .04em; text-transform: uppercase; }
.field .err[hidden] { display: none; }
.field .err {
  display: flex; align-items: center; gap: .35rem;
  color: var(--bad); font-weight: 700; font-size: .84rem; margin-top: .3rem;
}
.btn-row { display: flex; gap: .6rem; flex-wrap: wrap; margin: 1rem 0 0; }
/* Grid items stretch by default, which turns a button into a full-width slab by
   accident. The blueprint calls this out; pin them to their natural width. */
.btn-row .btn { justify-self: start; }

/* --- View as ------------------------------------------------------------- */
/* Compact in the header, per AntSoft's .imp-field: a small input that widens on
   focus, rather than a full-width band taking a whole row. */
.imp-field { position: relative; display: flex; align-items: center; gap: .4rem; }
.imp-field .viewas-results {
  position: absolute; z-index: 80; right: 0; top: calc(100% + .35rem);
  min-width: 26rem; max-width: calc(100vw - 2rem);
  background: var(--surface); border: 2px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--sh-lg);
  /* Tall enough to browse, short enough to stay on screen. Scrolling the last 40px of
     it loads the next page. */
  max-height: 26rem; overflow-y: auto; overscroll-behavior: contain;
}
.viewas-head {
  margin: 0; padding: .6rem .8rem; font-size: .8rem; color: var(--muted);
  background: var(--surface-2); border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 1;
}
.viewas-more {
  margin: 0; padding: .6rem .8rem; font-size: .8rem; color: var(--muted);
  border-top: 1px solid var(--line); text-align: center;
}
.viewas-hit {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: var(--surface); color: var(--ink); border: 0;
  border-bottom: 1px solid var(--line); padding: .6rem .8rem; font: inherit;
  min-height: 2.75rem;
}
.viewas-group {
  margin: 0; padding: .45rem .8rem; font-size: .72rem; font-weight: 800;
  letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
  background: var(--surface-2); border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.viewas-hit-top { display: flex; align-items: baseline; gap: .45rem; flex-wrap: wrap; }
.viewas-hit-name { font-weight: 600; }
.viewas-tag {
  font-size: .68rem; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
  color: var(--gold-deep); background: rgba(255, 210, 0, .18);
  border: 1px solid var(--gold-deep); border-radius: var(--r-full); padding: .05em .5em;
}
.viewas-tag.quiet {
  color: var(--muted); background: var(--surface-2); border-color: var(--line);
}
.viewas-hit:last-child { border-bottom: 0; }
.viewas-hit:hover, .viewas-hit.on { background: var(--surface-2); }
.viewas-hit-uid { font-family: var(--mono); font-weight: 700; margin-right: .5rem; }
.viewas-hit-meta { display: block; font-size: .78rem; color: var(--muted); margin-top: .12rem; }
.viewas-spend { color: var(--good); font-weight: 700; }
.viewas-none { color: var(--muted); }
.viewas-empty { padding: .8rem; margin: 0; color: var(--muted); font-size: .88rem; }
.imp-banner .who { font-family: var(--mono); font-weight: 800; }

/* --- FAQ ----------------------------------------------------------------- */
details.faq {
  background: var(--surface); border: 2px solid var(--line);
  border-radius: var(--r-md); padding: 0 1rem; margin: 0 0 .6rem;
}
details.faq > summary {
  cursor: pointer; font-weight: 700; padding: .8rem 0; list-style: none;
  display: flex; align-items: baseline; gap: .6rem; min-height: 2.85rem;
  color: var(--ink);
}
details.faq > summary::-webkit-details-marker { display: none; }
details.faq > summary::before {
  content: "+"; font-family: var(--mono); font-weight: 700;
  color: var(--blue); flex: none; width: 1ch;
}
details.faq[open] > summary::before { content: "\2212"; }
details.faq > summary:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
details.faq > p { margin: 0 0 .9rem 1.6rem; font-size: .92rem; }

/* --- Tables -------------------------------------------------------------- */
table.lc td.num, table.lc th.num {
  text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
table.lc .uid { font-family: var(--mono); }
/* Unit and department are context, not the point, so they get the leftover width rather
   than pushing the money column off screen. */
table.lc td.small { font-size: .82rem; color: var(--muted); max-width: 12rem; overflow-wrap: anywhere; }
table.lc caption {
  text-align: left; font-weight: 700; padding: .7rem .85rem; color: var(--ink);
  background: var(--surface);
}

/* Below 720px the roster re-renders as stacked cards, per the blueprint. The table and
   the cards are both in the DOM; components.css swaps which one shows. */
.as-cards .rcard {
  background: var(--surface); border: 2px solid var(--line);
  border-radius: var(--r-md); padding: .65rem .8rem; box-shadow: var(--sh-md);
}
.as-cards .rcard + .rcard { margin-top: .55rem; }
.as-cards .rcard dl { margin: .4rem 0 0; display: grid; grid-template-columns: auto 1fr; gap: .15rem .7rem; }
.as-cards .rcard dt { font-size: .76rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.as-cards .rcard dd { margin: 0; font-size: .9rem; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.as-cards .rcard .rname { font-weight: 800; font-size: .98rem; }
.as-cards .rcard .rname span { display: block; font-weight: 500; font-size: .8rem; color: var(--muted); font-family: var(--sans); }

/* --- How old are these figures ------------------------------------------- */
/* The report is rebuilt every two hours, so it has to say so. A number that is quietly
   two hours old is worse than one that admits it. */
.asof {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  font-size: .85rem; color: var(--muted); margin: .4rem 0 0;
}
.btn-inline { padding: .25rem .7rem; min-height: 2.2rem; font-size: .82rem; }
.bad-text { color: var(--bad); font-weight: 700; }

/* --- Roster finder -------------------------------------------------------- */
/* A search box, filter chips and a sort control, so finding one person in a period does
   not mean scrolling past everyone else. */
.finder {
  display: grid; gap: 0 1rem; align-items: end;
  grid-template-columns: minmax(14rem, 2fr) minmax(9rem, 1fr);
  margin: 0 0 .6rem;
}
@media (max-width: 40rem) { .finder { grid-template-columns: 1fr; } }

.chips { display: flex; flex-wrap: wrap; gap: .4rem; margin: 0 0 .6rem; }
.chip {
  display: inline-flex; align-items: center; gap: .45rem; cursor: pointer;
  min-height: 2.4rem; padding: .3rem .8rem; border-radius: var(--r-full);
  background: var(--surface); border: 2px solid var(--line); color: var(--ink);
  font: inherit; font-size: .86rem; font-weight: 600;
}
.chip:hover { border-color: var(--blue); }
.chip:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
/* The selected chip is filled AND has a heavier border, so it does not rely on colour. */
.chip.on { background: var(--navy); border-color: var(--navy); color: #fff; }
.chip-n {
  font-family: var(--mono); font-size: .78rem; font-weight: 700;
  background: var(--surface-2); color: var(--ink);
  border-radius: var(--r-full); padding: .05em .5em;
}
.chip.on .chip-n { background: rgba(255, 255, 255, .22); color: #fff; }

.sortpair { display: flex; gap: .4rem; align-items: stretch; }
.sortpair select { min-width: 0; flex: 1 1 auto; }
.sortdir { white-space: nowrap; flex: none; font-size: .84rem; padding: 0 .7rem; }

.finder-count { font-size: .88rem; color: var(--muted); margin: 0 0 .6rem; }
.finder-count strong { color: var(--ink); }

/* --- Period picker in the hero ------------------------------------------- */
/* A pull-down, not chips. The multi-month case is an option inside it. */
.hero-periods-note { font-size: .84rem; color: #cfe0f2; margin: .5rem 0 0; max-width: 52ch; }
.hero-periods-note:empty { display: none; }
.hero-field select { max-width: 100%; }

.held-pick { margin: 0; }
.held-pick select { max-width: 100%; }
.nowrap { white-space: nowrap; }

/* --- Held back from the file ---------------------------------------------- */
/* Counts with a way through to the people, instead of two more tables repeating rows
   that are already in the roster below. */
.held { display: grid; gap: .8rem; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
.held-item {
  background: var(--surface); border: 2px solid var(--line);
  border-left: 8px solid var(--warn, var(--gold)); border-radius: var(--r-md);
  padding: .9rem 1rem; box-shadow: var(--sh-md);
}
.held-n { margin: 0; font-weight: 800; }
.held-sum {
  margin: .1rem 0 .3rem; font-family: var(--mono); font-weight: 700;
  font-size: 1.5rem; font-variant-numeric: tabular-nums; color: var(--ink);
}
.held-why { margin: 0 0 .7rem; font-size: .85rem; color: var(--muted); }

.preview-fold { margin-top: 1rem; }

/* --- Sortable headings ---------------------------------------------------- */
/* The control is a real button inside the th, so keyboard operation comes for free and
   aria-sort can live where a screen reader expects it. */
th.sortable { padding: 0; }
.sortbtn {
  display: flex; align-items: center; gap: .3rem; width: 100%;
  background: none; border: 0; cursor: pointer; font: inherit; color: inherit;
  padding: .45rem .7rem; min-height: 2.2rem; text-align: left;
}
th.num .sortbtn { justify-content: flex-end; }
.sortbtn:hover { background: var(--line); }
.sortbtn:focus-visible { outline: 3px solid var(--focus); outline-offset: -3px; }
.sortbtn.on { color: var(--link); }
.sortarrow { font-size: .7em; width: .8em; flex: none; }

/* --- Zebra striping ------------------------------------------------------- */
/* Long lists of near-identical rows are where the eye slips a line. Kept very light so it
   does not fight the status pills.
   Striped with a class from the renderer, not :nth-child. Two reasons: the fiscal officer
   table interleaves hidden editor rows, which throws off any positional selector, and
   `:nth-child(even of S)` is too new to rely on. A class is exact everywhere. */
table.lc tbody tr.zebra > * { background: var(--surface-2); }
table.lc tbody tr.zebra.simulated > * { background: rgba(180, 30, 20, .07); }

/* --- Fiscal officer table ------------------------------------------------- */
/* Dense on purpose. This table used to carry a number input and two 44px buttons on every
   row, so a two-person account was 350px tall, and five columns in a full-width container
   stretched to 1370px with the money nowhere near the names.
   Accessible, responsive, modern: the row is one line, the numbers sit next to the person
   they belong to, and the controls appear when they are needed. */
/* Narrow enough that the 100%-width table has almost no slack to hand the person column,
   which is what left a hand's width of nothing between the names and the money. The
   content needs about 44rem; this gives it 46. */
/* Seven columns now that unit and department are shown, so the block is wider than the
   five-column version. Still bounded, and still far from the 1370px it used to sprawl to. */
.acct-block { margin-top: 1.5rem; max-width: 66rem; }
.acct-head {
  display: flex; align-items: baseline; gap: .8rem; flex-wrap: wrap;
  justify-content: space-between; margin: 0 0 .4rem;
}
.acct-num { font-size: 1.05rem; margin: 0; }
.acct-meta {
  display: flex; gap: .9rem; flex-wrap: wrap; margin: 0;
  font-size: .82rem; color: var(--muted);
}
.acct-meta strong { color: var(--ink); font-family: var(--mono); font-weight: 700; }

/* Column widths do the work that padding was doing badly: the name takes the slack, the
   numbers stay beside it. */
.fo-table { font-size: .9rem; }
.fo-table th, .fo-table td { padding: .45rem .7rem; }
.fo-table thead th {
  font-size: .68rem; letter-spacing: .06em; text-transform: uppercase; white-space: nowrap;
}
.fo-table col, .fo-table th:first-child { width: auto; }
.fo-table th.num, .fo-table td.num { width: 7.5rem; }
.fo-table .fo-th { width: 8.5rem; }
.fo-table .fo-act { width: 4rem; text-align: right; }
.fo-name { font-weight: 600; color: var(--body); }
.fo-amount { font-weight: 700; }
/* State stays legible without a colour alone: "not told" is bold and dark as well as
   warm-toned, and it sits next to the words it qualifies. */
.fo-note { display: block; font-size: .74rem; color: var(--muted); }
.fo-untold { color: var(--gold-deep); font-weight: 700; }

/* One compact control per row instead of three large ones. WCAG 2.5.8 (AA) asks for 24px
   targets and 2.5.5 (AAA) for 44px; three 44px buttons per row is what made this table
   unusable. This is 32px with real padding, comfortably past AA, and the editor it opens
   keeps full-size targets because that is where the work happens. */
.linkbtn {
  background: none; border: 0; padding: .35rem .5rem; min-height: 2rem;
  font: inherit; font-size: .84rem; font-weight: 700; color: var(--link);
  text-decoration: underline; text-underline-offset: .15em; cursor: pointer;
  border-radius: var(--r-sm, .4rem);
}
.linkbtn:hover { color: var(--ink); background: var(--surface-2); }
.linkbtn:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }

.fo-editor > td { background: var(--surface-2); }
.fo-editor[hidden] { display: none; }
.fo-editor-in {
  display: flex; align-items: flex-end; gap: .8rem; flex-wrap: wrap;
  padding: .3rem 0 .1rem;
}
.fo-editor-in .field { margin: 0; }
.fo-editor-in input { max-width: 9rem; }
.fo-editor-in .btn-row { margin: 0; }
.fo-editor-note { margin: .4rem 0 0; max-width: 60ch; }

/* The editor moved into a card on a phone. Same controls, stacked. */
.card-editor {
  margin-top: .6rem; padding-top: .6rem; border-top: 1px solid var(--line);
}
.card-editor .fo-editor-in { flex-direction: column; align-items: stretch; }
.card-editor .fo-editor-in input { max-width: 100%; }
.card-editor .btn-row { width: 100%; }
.rcard .spread .linkbtn { flex: none; }
.rcard.simulated { border-left: 8px solid var(--bad); }

/* --- Simulated data ------------------------------------------------------ */
/* A preview of assumed account assignments. Marked on every account and every row,
   because a screenshot of this page must never be mistakeable for the real thing. */
.sim-tag {
  font-family: var(--sans); font-size: .66rem; font-weight: 800; letter-spacing: .07em;
  text-transform: uppercase; vertical-align: .18em;
  color: var(--bad); background: var(--bad-bg, rgba(180, 30, 20, .1));
  border: 2px solid var(--bad); border-radius: var(--r-full); padding: .1em .55em;
}
tr.simulated th, tr.simulated td { background: rgba(180, 30, 20, .05); }

/* --- Export preview ------------------------------------------------------ */
pre.preview {
  background: var(--surface-2); border: 2px solid var(--line); border-radius: var(--r-md);
  padding: .85rem; overflow-x: auto; font-family: var(--mono); font-size: .8rem;
  line-height: 1.5; margin: .7rem 0; color: var(--ink);
}

/* --- Footer -------------------------------------------------------------- */
.foot-in { max-width: var(--page-max); margin: 0 auto; padding: 0 1rem; }
.foot-in p { margin: 0 0 .3rem; color: inherit; max-width: none; }
.foot-in p:last-child { margin: 0; }

/* --- Utility ------------------------------------------------------------- */
.muted { color: var(--muted); }
.small { font-size: .85rem; }
.spread { display: flex; gap: 1rem; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; }
/* Flex items default to min-width:auto, so they refuse to shrink below their longest
   unbreakable word. Keep them shrinkable. */
.spread > * { min-width: 0; }

/* components.css gives .tbl-wrap overflow-x:auto, which lets the table scroll inside it
   but does NOT stop the table contributing to the DOCUMENT's scroll width, so the whole
   fiscal officer page scrolled sideways at 320px (472px wide in a 320px viewport).
   Containment makes the wrapper a real boundary. Confirmed by trying seven candidate
   fixes against the live page: this one and table-layout:fixed were the only two that
   worked, and fixed layout would change how every column is sized. */
.tbl-wrap { contain: paint; }


/* --- Help and FAQ page ---------------------------------------------------- */
/* Deliberately NOT the marketing hero. The first version of this page opened with the same
   navy hero and the same balance panel as index.html, and Mike's reaction was that it
   looked identical to his own balance. A help page has to announce itself as a help page
   before anything else on it is read. */
.help-hero {
  display: flex; gap: 1.1rem; align-items: flex-start;
  background: var(--surface); border: 2px solid var(--line);
  border-left: 10px solid var(--blue); border-radius: var(--r-md);
  padding: 1.2rem 1.3rem 1.3rem; margin: 1.2rem 0 1rem; box-shadow: var(--sh-md);
}
.help-hero-mark {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 3.4rem; height: 3.4rem; border-radius: var(--r-full);
  background: var(--navy); color: #fff;
  font-family: var(--display, var(--sans)); font-size: 2rem; font-weight: 800;
  line-height: 1;
}
.help-hero-eyebrow {
  margin: 0 0 .2rem; font-family: var(--mono); font-size: .8rem; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--link);
}
.help-hero h1 { margin: 0 0 .4rem; }
.help-hero p { margin: 0 0 .5rem; }
.help-hero p:last-child { margin-bottom: 0; }

/* Numbered walkthrough. The numbers are real sequence, not decoration: you cannot name an
   account before you have signed in. */
.steps { list-style: none; margin: 1rem 0 0; padding: 0; counter-reset: step; }
.steps > li {
  position: relative; counter-increment: step;
  padding: 0 0 1rem 3.1rem; margin: 0;
}
.steps > li::before {
  content: counter(step);
  position: absolute; left: 0; top: 0;
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.2rem; height: 2.2rem; border-radius: var(--r-full);
  background: var(--navy); color: #fff;
  font-family: var(--mono); font-size: 1rem; font-weight: 700; line-height: 1;
}
.steps > li > strong { display: block; margin-bottom: .15rem; }
.steps > li > p { margin: 0; color: var(--muted); max-width: 68ch; }

/* A specimen, not the reader's own balance. Dashed frame, striped surround and a corner
   badge, so a panel lifted out of here and forwarded still says what it is. The stripes sit
   in the frame's padding, never behind text, so contrast is untouched. */
.fig {
  position: relative; margin: 1.4rem 0 0; padding: 2.4rem 1rem 1rem;
  border: 3px dashed var(--line); border-radius: var(--r-md);
  background-color: var(--surface-2);
  background-image: repeating-linear-gradient(
    135deg, transparent 0 12px, rgba(120, 130, 145, .10) 12px 24px);
}
.fig-badge {
  position: absolute; top: .55rem; left: 1rem;
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .2rem .55rem; border-radius: var(--r-sm);
  border: 2px solid var(--warn); background: var(--warn-bg); color: var(--warn);
  font-family: var(--mono); font-size: .78rem; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
}
.fig > figcaption {
  margin: .85rem 0 0; padding-top: .7rem; border-top: 1px solid var(--line);
  color: var(--ink); font-size: .95rem; max-width: 72ch;
}
.fig > figcaption b { font-family: var(--mono); font-size: .88rem; letter-spacing: .04em; }
.fig .statement { margin: 0 0 .8rem; }
.fig .tiles { margin: 0; }
@media (max-width: 34rem) {
  .help-hero { flex-direction: column; gap: .7rem; }
  .fig { padding: 2.3rem .6rem .8rem; }
}


/* --- Waiting for a figure -------------------------------------------------- */
/* Warm, get_me answers in about 50ms. Into a cold Lambda container it is about 2.5s, and
   the page used to hide EVERYTHING behind that. Now the page arrives at once and only the
   figures shimmer, so there is something to read while the number is on its way.
   Deliberately quiet: a bar the size of the thing it replaces, one slow sweep, no spinner. */
.skel {
  display: block; position: relative; overflow: hidden;
  border-radius: var(--r-sm);
  background: var(--surface-2);
}
.skel::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .55), transparent);
  transform: translateX(-100%);
  animation: skel-sweep 1.6s ease-in-out infinite;
}
:root[data-theme="dark"] .skel::after,
:root:not([data-theme="light"]) .skel::after {
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .10), transparent);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .skel::after {
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .55), transparent);
  }
}
@keyframes skel-sweep { to { transform: translateX(100%); } }
/* A sweep is decoration. Someone who asked for less motion still gets the grey bar, which
   is what actually communicates "waiting". */
@media (prefers-reduced-motion: reduce) {
  .skel::after { animation: none; }
}

.skel-sm { width: 7rem; height: .8rem; margin: 0 0 .55rem; }
.skel-amount { width: 11rem; height: 2.9rem; margin: 0 0 .7rem; }
.skel-value { width: 6.5rem; height: 1.7rem; margin: 0 0 .5rem; }
.skel-line { width: min(24rem, 80%); height: .9rem; }

/* Same frame as the real statement, so the swap does not move the page. */
.skel-statement {
  background: var(--surface); border: 2px solid var(--line);
  border-left: 10px solid var(--surface-2); border-radius: var(--r-md);
  padding: 1.1rem 1.3rem 1.2rem; box-shadow: var(--sh-md);
}
.tile-card .skel-sm { width: 5.5rem; }

/* --- The threshold card --------------------------------------------------- */
/* Same structural language as the suggestion card (tinted, accent top rule, badge eyebrow,
   small print at the foot) but a NEUTRAL tint, so two cards on the same page do not read as
   duplicates of each other. This replaced a notice() box stacked on a .card full of inline
   styles: one subject was being presented as two objects.

   State is carried FOUR ways and never by colour alone: the accent rule, the bar's fill, the
   percentage as a number, and a sentence. */
.th-card {
  max-width: 34rem;
  margin: 0;
  padding: 1rem 1.15rem 1.1rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-top: 4px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--sh-md);
}
/* Gold when there is no threshold, which is the state almost everybody is in and the one
   that looked dull: a pale grey rule drew no eye at all. Once a threshold EXISTS the rule
   goes semantic, because then there is something to report and the colour is doing work
   rather than decorating.

   Mike suggested a fixed yellow here and red on the account card. Half of that is done: gold
   on the idle threshold card. Not the red, and not a fixed yellow when a threshold is set,
   for two reasons.

   Red is not decorative in this palette. --bad is the error colour, on .btn.danger, on
   .callout.bad, on the statement rail when a balance has nowhere to post, and on this card
   when somebody is over. A red rule across the top of a neutral suggestion card would read
   as "something is wrong with this account" to anybody who has seen red anywhere else here.

   And a fixed yellow on this card would spend a channel that is currently carrying real
   information. The two cards are already told apart by their tint (blue against grey) and by
   their badge word. */
.th-card.th-none  { border-top-color: var(--gold); }
.th-card.th-under { border-top-color: var(--good); }
.th-card.th-near  { border-top-color: var(--warn); }
.th-card.th-over  { border-top-color: var(--bad); }

.th-eyebrow {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin: 0 0 .85rem; font-size: .85rem; color: var(--body);
}
.th-badge {
  flex: none; padding: .12rem .5rem; border-radius: var(--r-full);
  background: var(--navy); color: #fff;
  font-family: var(--mono); font-size: .7rem; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
}

.th-figure {
  display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
  margin: 0 0 .5rem;
}
.th-spent { font-size: 1.6rem; font-weight: 800; color: var(--ink); letter-spacing: -.01em; }
.th-of { font-size: .95rem; color: var(--body); }
/* The percentage is the text half of what the bar shows, so it never depends on the bar. */
.th-pct {
  margin-left: auto;
  font-family: var(--mono); font-size: .95rem; font-weight: 700; color: var(--ink);
}

/* Honest here in a way a bar usually is not: spend against a threshold really is a
   proportion. Decorative, because the figure and the sentence already say it. */
.th-bar {
  height: .5rem; border-radius: var(--r-full);
  background: var(--surface); border: 1px solid var(--line);
  overflow: hidden; margin: 0 0 .6rem;
}
.th-bar > span { display: block; height: 100%; background: var(--good); }
.th-near .th-bar > span { background: var(--warn); }
.th-over .th-bar > span { background: var(--bad); }

.th-words { margin: 0 0 .5rem; color: var(--body); max-width: 60ch; }
.th-setby { margin: 0 0 .5rem; font-size: .9rem; color: var(--body); }

.th-form { padding-top: .8rem; margin-top: .3rem; border-top: 1px solid var(--line); }
/* The wrapper is full width so the label sits on one line; only the input is narrow.
   Capping the wrapper wrapped "Amount in dollars (blank for none)" onto two lines. */
.th-field { margin: 0 0 .7rem; }
.th-field input { max-width: 10rem; background: var(--surface); }
.th-actions { margin: 0; }

.th-note {
  display: flex; gap: .45rem; align-items: flex-start;
  margin: .8rem 0 0; font-size: .86rem; line-height: 1.5; color: var(--muted);
  max-width: 46ch;
}
.th-note .cg-i { flex: none; margin-top: .15rem; }

@media (max-width: 30rem) {
  .th-pct { margin-left: 0; }
  .th-actions .btn { width: 100%; }
}


/* Animated "processing" loader, ui.js progress(). Used where a cold start or a live pull can
   take a few seconds; motion reassures the page is working, not frozen. */
.proc { max-width: 34rem; margin: 2.5rem auto; text-align: center; }
.proc-title { font-weight: 700; font-size: 1.15rem; margin: 0 0 .3rem; color: var(--ink); }
.proc-msg { color: var(--muted); min-height: 1.5em; margin: 0 0 1rem; }
.proc-bar { height: 12px; border-radius: var(--r-full, 999px); background: var(--surface-2);
  border: 1px solid var(--line); overflow: hidden; }
.proc-fill { height: 100%; width: 6%; border-radius: var(--r-full, 999px); position: relative;
  overflow: hidden; background: linear-gradient(90deg, var(--navy), var(--gold));
  transition: width .45s ease; }
.proc-fill::after { content: ""; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.55), transparent);
  animation: proc-shimmer 1.1s linear infinite; }
.proc-pct { font-family: var(--mono); font-weight: 700; margin: .55rem 0 0; color: var(--ink); }
@keyframes proc-shimmer { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .proc-fill::after { animation: none; } }
