/* Shared components
 * ============================================================================
 * Widgets that appear on more than one page, written once. Companion to
 * base.css (tokens), nav.css (the bar) and shell.css (the wrapper).
 */

/* Page-level tabs
 * ---------------
 * The control that answers "which view of this page am I looking at". A single
 * housed pill: one trough, with the active choice raised out of it. That reads
 * as one control rather than a row of separate buttons, which is what a view
 * switcher is.
 *
 * An audit on 2026-09-08 found five different visual languages for "pick one of
 * N" across the site, of which two were doing this same job: this housed pill
 * (inventory, sales, gc-wallet, ranking) and a green outline pill (gift-cards,
 * cashback, metrics, analytics). The housed pill won and the outline pill's
 * page-level uses were converted.
 *
 * Deliberately NOT converted, because they are different jobs:
 *   .calc-toggle-btn      research, gwp-calculator: a control inside the
 *                         calculator card, closer to a form input than a tab.
 *   .history-tab-btn      promotions: sits inside a card.
 *   .vtab                 metrics: the same pill reused for two card-level
 *                         switchers (#biz-tabs, #rep-tabs).
 *   .tab-btn / .utabs     retiring: underline tabs on the gradient.
 *   .mini-toggle-btn      inventory, sales: a micro switch inside a table row.
 */
.tabs {
  display: flex;
  gap: 4px;
  background: var(--surface);
  border-radius: 10px;
  padding: 4px;
  width: fit-content;
  max-width: 100%;
  flex-wrap: wrap;
  box-shadow: var(--card-shadow);
  margin-bottom: 20px;
}

.tab {
  padding: 8px 22px;
  border: none;
  background: none;
  border-radius: 7px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-faint);
  cursor: pointer;
  transition: all 0.15s;
  position: relative;
}

.tab:hover:not(.active) { color: var(--ink); }
.tab.active {
  background: linear-gradient(135deg, var(--green), var(--green-dark));
  color: #fff;
}
.tab:focus-visible { outline: 2px solid var(--green-dark); outline-offset: 1px; }

/* A count carried alongside a tab's label (gc-wallet). */
.tab-count { opacity: 0.65; font-weight: 600; margin-left: 6px; }

/* A data-presence dot carried inside a tab's label (analytics). */
.tab .tab-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #4CAF50;
  margin-left: 6px;
  vertical-align: middle;
}
.tab .tab-dot.none { background: var(--ink-dim); }

@media (max-width: 480px) {
  .tab { padding: 8px 14px; }
}

/* Panel
 * -----
 * The white block most content sits in. Seven pages defined .card with eight
 * distinct bodies, split across two generations: 12px radius with a 0 1px 4px
 * shadow, and 14px with 0 1px 6px. The tokens already named the first, so that
 * is the one written here and the 14px pages snap to the scale.
 */
.card {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 24px;
  margin-bottom: 20px;
  box-shadow: var(--card-shadow);
}

/* The small uppercase label above a panel's contents. Four pages had this
 * character for character. metrics and sb-cookies used the same class name for
 * something else, a plain sentence-case heading, so those were renamed
 * .card-heading rather than bent into this. */
.card-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--label);
  margin-bottom: 16px;
  text-transform: uppercase;
  letter-spacing: 0.7px;
}

/* A single figure with its label. */
.stat-card {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 18px 20px;
  box-shadow: var(--stat-shadow);
}

/* Data table
 * ----------
 * Only the container is shared. The cell padding and type deliberately are not:
 * a 13-column gift card matrix and a four-column expense list are different
 * densities, and forcing one on the other makes both worse.
 *
 * retiring keeps its own rule in full. Its table is translucent white on the
 * green gradient, and it sets every property this one does, so it overrides
 * cleanly rather than inheriting half of it.
 */
.table-wrap {
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--card-shadow);
  overflow-x: auto;
}

/* Buttons
 * -------
 * Only the four names that were already shared across pages. The site has 65
 * distinct button class names; the rest are genuinely one-off controls, and
 * renaming them would be churn with no payoff.
 */
.btn-add {
  background: linear-gradient(135deg, var(--green), var(--green-dark));
  color: #fff;
  border: none;
  padding: 11px 28px;
  border-radius: var(--radius-md);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: opacity 0.2s;
}
.btn-add:hover { opacity: 0.9; }

.btn-cancel {
  background: #f0f0f0;
  color: var(--ink-soft);
  border: none;
  padding: 10px 22px;
  border-radius: var(--radius-md);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s;
}
/* Every other button on the site answers the pointer. This one did not, which
   read as a disabled control on the dialogs that still use it. */
.btn-cancel:hover { background: #e4e4e4; }

.btn-clear {
  background: none;
  border: 1.5px solid #ddd;
  color: var(--ink-faint);
  padding: 10px 20px;
  border-radius: var(--radius-md);
  font-size: 14px;
  cursor: pointer;
  transition: border-color 0.2s;
}
.btn-clear:hover { border-color: var(--green); color: var(--green); }

.modal-btns { display: flex; gap: 10px; justify-content: center; }

/* Confirm dialog
 * --------------
 * The markup is built by static/js/confirm.js, so these are the only styles a
 * page needs to ask before doing something it cannot undo. Deliberately its own
 * cf- prefix rather than .modal-*: the wallet already owns those names with its
 * own dialog, and reusing them would have the two quietly restyling each other.
 */
.cf-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 2000;
  background: rgba(0, 0, 0, 0.4);
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.cf-overlay.open { display: flex; }
.cf-box {
  background: var(--surface);
  border-radius: 14px;
  padding: 26px 30px;
  width: 100%;
  max-width: 420px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
}
.cf-title { font-size: 17px; font-weight: 700; margin-bottom: 10px; }
.cf-copy { font-size: 14px; color: var(--ink-muted, #555); line-height: 1.6; margin: 0 0 20px; }
.cf-btns { display: flex; gap: 10px; justify-content: center; }
.cf-cancel, .cf-danger {
  min-height: var(--control-md);
  padding: 0 24px;
  border-radius: var(--radius-md);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.cf-cancel {
  background: none;
  border: 1.5px solid #ddd;
  color: var(--ink-faint);
  transition: border-color 0.2s, color 0.2s;
}
.cf-cancel:hover { border-color: var(--green); color: var(--green); }
.cf-danger {
  background: var(--danger);
  border: none;
  color: white;
  font-weight: 700;
  transition: opacity 0.2s;
}
.cf-danger:hover { opacity: 0.88; }

/* Calendar
 * --------
 * The site's replacement for the calendar a browser draws for
 * <input type="date">. See static/js/datepicker.js for why: that one is drawn
 * by the browser, not the page, and takes no CSS at all, so it arrived in blue
 * with its own type scale. The input itself is untouched and still holds the
 * value; only its trigger is swapped.
 *
 * The look follows the range picker on /inventory, which was themed to the site
 * long before this existed, so the two calendars on the site match.
 */
.dp-wrap { position: relative; display: block; }

/* The browser's own trigger, hidden so only one calendar can open. Note what is
 * NOT here: appearance: none on the input itself. It hides the icon just as
 * well, and it also collapses a date field to the width of its own text, so a
 * field that filled its column stopped doing so and left the new button
 * stranded out to the right of it. */
.dp-wrap input[type="date"]::-webkit-calendar-picker-indicator { display: none; }
.dp-wrap input[type="date"]::-webkit-inner-spin-button { display: none; }
/* The wrapper is now the grid or flex item the input used to be, so the input
 * has to be told to fill it. Without this a field that stretched across its
 * column shrinks to the width of "09/10/2026" and leaves the button stranded
 * out at the column's edge. Plus room for the button itself, so a long date
 * cannot run underneath it. */
.dp-wrap input[type="date"] {
  width: 100%;
  box-sizing: border-box;
  padding-right: 38px;
}

.dp-btn {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--icon-rest);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.dp-btn:hover { background: var(--green-wash); color: var(--green-deep); }
.dp-btn svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
}

.dp-panel {
  display: none;
  position: fixed;
  z-index: 2100;
  width: 268px;
  padding-bottom: 6px;
  background: var(--surface);
  border-radius: 14px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
  overflow: hidden;
  user-select: none;
}
.dp-panel.open { display: block; }

.dp-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 10px 6px;
}
.dp-title {
  border: none;
  background: none;
  padding: 4px 10px;
  border-radius: 6px;
  font-family: inherit;
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.15s;
}
.dp-title:hover { background: var(--green-wash); }
.dp-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--green);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.dp-nav:hover { background: var(--green-wash); color: var(--green-deep); }
.dp-nav svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.dp-dow {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  padding: 4px 8px;
  background: var(--surface-sunk);
}
.dp-dow span {
  text-align: center;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--label);
  line-height: 22px;
}

.dp-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
  padding: 6px 8px 2px;
}
.dp-day {
  height: 32px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: none;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.dp-day.out { color: #ccc; }
.dp-day:hover { background: var(--green-wash); color: var(--green-text); }
.dp-day.now { border-color: var(--green); }
.dp-day.on,
.dp-day.on:hover {
  background: linear-gradient(135deg, var(--green), var(--green-dark));
  border-color: var(--green-dark);
  color: white;
}

.dp-months {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 4px;
  padding: 8px;
}
.dp-month {
  height: 40px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: none;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.dp-month:hover { background: var(--green-wash); color: var(--green-text); }
.dp-month.now { border-color: var(--green); }
.dp-month.on,
.dp-month.on:hover {
  background: linear-gradient(135deg, var(--green), var(--green-dark));
  border-color: var(--green-dark);
  color: white;
}

.dp-foot {
  display: flex;
  justify-content: space-between;
  padding: 4px 12px 2px;
}
.dp-link {
  border: none;
  background: none;
  padding: 4px 2px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  color: var(--green-deep);
  cursor: pointer;
}
.dp-link:hover { text-decoration: underline; }

/* Dropdown
 * --------
 * The site's replacement for a native <select>. See static/js/dropdown.js for
 * why: the closed control takes CSS, but a native open list is drawn by the OS
 * and cannot be styled at all, so the <select> is hidden and kept as the source
 * of truth while a button and a real menu are drawn on top.
 *
 * Promoted from /metrics, which had skinned its 19 controls this way since
 * before the design audit.
 */
/* A native select shrinks to fit its content, so this does too. dropdown.js
 * measures the select before hiding it and adds .dd-block where the original
 * filled its container, which is the case in a form but not in a toolbar. */
.dd-wrap {
  position: relative;
  display: inline-block;
  width: auto;
  max-width: 100%;
  vertical-align: middle;
}
.dd-wrap.dd-block { display: block; width: 100%; }

/* Not display:none, because a hidden select is skipped by form submission and
 * by some assistive tech. It stays in the layout at zero size. */
.dd-wrap > select {
  position: absolute;
  width: 0;
  height: 0;
  opacity: 0;
  pointer-events: none;
}

.dd-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  background: var(--surface);
  border: 1.5px solid var(--border);
  color: var(--ink-soft);
  padding: 7px 12px;
  border-radius: var(--radius-md);
  font-size: 13px;
  font-family: inherit;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  min-height: var(--control-md);
  transition: border-color 0.15s, color 0.15s;
}
.dd-btn:hover,
.dd-wrap.open > .dd-btn { border-color: var(--green); color: var(--green-deep); }
.dd-btn:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }
.dd-btn:disabled { opacity: 0.55; cursor: default; }
.dd-btn .dd-txt { overflow: hidden; text-overflow: ellipsis; }
.dd-btn .dd-caret { color: var(--label); font-size: 10px; flex: 0 0 auto; }

/* position: fixed, not absolute: some of these live inside a scroll box and an
 * absolutely positioned menu is clipped by it. dropdown.js keeps it under its
 * button. */
.dd-menu {
  display: none;
  position: fixed;
  background: var(--surface);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-md);
  z-index: 400;
  max-height: 280px;
  overflow-y: auto;
  padding: 4px 0;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
.dd-wrap.open > .dd-menu { display: block; }

.dd-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 16px;
  background: none;
  border: none;
  font-family: inherit;
  font-size: 13px;
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
}
/* .hot is the keyboard cursor; hover paints the same so the two never disagree */
.dd-item:hover,
.dd-item.hot { background: var(--surface-sunk); }
.dd-item.active { color: var(--green-deep); font-weight: 600; }

/* Data table
 * ----------
 * The container (.table-wrap) was shared in the component pass; this is the
 * table inside it. An audit on 2026-09-09 found twelve tables in twelve styles:
 * eight header paddings, five header sizes, eight cell paddings, and three
 * pages that did not uppercase their headers while nine did.
 *
 * The majority was already here. expenses, sales, inventory and ranking shared
 * a .tbl class at 8-9px 12px with an 11px/700/uppercase header, and research
 * and metrics sat a pixel or two away. That majority is what this is.
 *
 * Row lines and hover take --row-line and --row-hover rather than the #f5f5f5
 * and #fafcfa the four pages had hard-coded. The difference is under four
 * points and is the whole reason the tokens exist.
 */
.tbl {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.tbl th {
  text-align: left;
  padding: 8px 12px;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--label);
  background: var(--surface-sunk);
  border-bottom: 1px solid var(--row-line);
  white-space: nowrap;
}

.tbl td {
  padding: 8px 12px;
  border-bottom: 1px solid var(--row-line);
  vertical-align: middle;
}

.tbl tr:last-child td { border-bottom: none; }
.tbl tr:hover td { background: var(--row-hover); }

.tbl tfoot td {
  background: var(--page-bg);
  font-weight: 700;
  border-top: 2px solid #dbe4dd;
  border-bottom: none;
}

/* Sortable headers, as expenses/sales/inventory already had them. */
.tbl th.sortable { cursor: pointer; user-select: none; }
.tbl th.sortable:hover,
.tbl th:hover { color: var(--green); }
.tbl th .sort-arrow { margin-left: 4px; opacity: 0.4; }
.tbl th.sorted .sort-arrow { opacity: 1; color: var(--green); }

/* Compact: many narrow columns that have to fit without sideways scrolling.
 * gift-cards and cashback put 13 gift card sites across the page, and the
 * standard padding costs more width than they have. Structure, borders, hover
 * and colour are the shared ones; only the density differs, and the header
 * stays sentence case because uppercase is wider. */
.tbl.compact th,
.tbl.compact td { padding: 10px 6px; }
/* The value columns centre; the first column is the row's label and stays left.
 * Written as :not(:first-child) rather than centring everything, because
 * `.tbl.compact td` outranks the pages' own `td:first-child { text-align: left }`
 * and centred the retailer name under its own sub-line. */
.tbl.compact th:not(:first-child),
.tbl.compact td:not(:first-child) { text-align: center; }
.tbl.compact th { font-size: 10px; text-transform: none; letter-spacing: 0; }
.tbl.compact td { font-size: 12px; }

/* Sortable header affordance, shared by the pages that sort from their own data
 * (expenses, sales, inventory) and by static/js/table-sort.js, which adds
 * sorting to tables that had none. */
.tbl th.sortable::after  { content: ' \2195'; opacity: 0.35; }
.tbl th.sortable.asc::after  { content: ' \25B2'; opacity: 1; }
.tbl th.sortable.desc::after { content: ' \25BC'; opacity: 1; }
.tbl th.sortable:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; }

/* Roomy: a list of records rather than a grid of values, where each row is a
 * thing you act on. gift-cards' and cashback's alert lists and gc-wallet's card
 * table all used these proportions before the tables were unified. */
.tbl.roomy th,
.tbl.roomy td { padding: 11px 16px; }
.tbl.roomy td { font-size: 13px; }

/* Search field
 * -----------
 * One look for every search box. Measured on 2026-09-09 there were six: heights
 * 30 to 36, two border colours, radius 6 or 8, and one page sitting its field on
 * white inside a white card, where a hairline border leaves nothing to see.
 *
 * The fill contrasts with whatever the field sits on, which is the rule the
 * site was already following in the places that looked right: white on the grey
 * page background, grey inside a white card or table wrapper. A white field on
 * a white card has only a hairline border to say it is a field, which is what
 * made /expenses look wrong.
 *
 * A first attempt made every field grey. That was wrong on the pages whose
 * toolbars sit on the page background, so the fill is conditional now rather
 * than fixed.
 */
.search {
  min-height: var(--control-md);
  padding: 8px 14px;
  font-size: 13px;
  color: var(--ink);
  background: var(--surface);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-md);
  outline: none;
}

/* Sunk where the surface behind it is white. */
.card .search,
.table-wrap .search,
.tbl-wrap .search,
.panel .search { background: var(--surface-sunk); }

.search::placeholder { color: var(--ink-dim); }
.search:focus { border-color: var(--green); }

/* The page's own search, where it stands alone and is the point of the page. */
.search.hero {
  min-height: var(--control-hero);
  padding: 14px 20px;
  font-size: 16px;
  background: var(--surface);
  border-width: 2px;
  border-radius: var(--radius-lg);
}

/* Retailer badge
 * --------------
 * The coloured chip a shop's name sits in. Colours come from
 * static/js/store-color.js, which is the canonical palette; gc-wallet carries
 * its own copy because it ships as an integrity-hashed bundle.
 */
.store-badge {
  display: inline-block;
  padding: 3px 7px;
  border-radius: var(--radius-sm);
  font-size: 12px;
  font-weight: 700;
  color: #fff;
}

/* The row that opens under a retailer to set an alert. Green wash, matching the
 * hover and selected fills, rather than the yellow it used to be. */
.alert-row td {
  padding: 0;
  background: var(--green-wash);
  border-bottom: 1.5px solid var(--border-soft);
}

/* Alert controls: the picker and its Save button are one row, so they share a
 * height, and the picker is a fixed width so the Save buttons line up down the
 * column instead of stepping with "5% off" against "10% off". */
.alert-wrap .dd-wrap,
.alerts-tbl .dd-wrap { width: 118px; }

.alert-save-btn,
.alerts-tbl .save-btn {
  min-height: var(--control-md);
  display: inline-flex;
  align-items: center;
  padding: 0 18px;
  border: none;
  border-radius: var(--radius-md);
  background: var(--green);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.alert-save-btn:hover,
.alerts-tbl .save-btn:hover { background: var(--green-dark); }
.alerts-tbl .save-btn:disabled { opacity: 0.85; cursor: default; }

/* Its Delete sits in the same row, so it takes the same height. Outlined rather
 * than filled, because it is the destructive one and should not compete with
 * Save for the eye. */
/* Delete
 * ------
 * One look for every button that deletes something: outlined rather than
 * filled, danger red for the label and a softer red for the border, tinted on
 * hover. Filled red shouts at you every time the page renders, for an action
 * that always asks before it happens anyway.
 *
 * The size follows the buttons beside it rather than being fixed, because the
 * same control appears in a page-level button row, in a dense table row and in
 * a dialog footer. Add .sm in a tight row; add nothing at page level.
 *
 * This is for buttons that say Delete. An icon-only remove, the trash glyph in
 * an inventory row or the x on a line item, is a different control and keeps
 * its own quiet styling.
 */
.btn-delete,
.alert-remove-btn,
.alerts-tbl .remove-btn {
  min-height: var(--control-md);
  display: inline-flex;
  align-items: center;
  padding: 0 16px;
  background: none;
  color: var(--danger);
  border: 1.5px solid #e0b0ad;
  border-radius: var(--radius-md);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.btn-delete:hover,
.alert-remove-btn:hover,
.alerts-tbl .remove-btn:hover { background: #fdf0ef; border-color: var(--danger); }

.btn-delete {
  font-family: inherit;
  white-space: nowrap;
  transition: background 0.15s, border-color 0.15s;
}
.btn-delete.sm {
  min-height: var(--control-sm);
  padding: 0 12px;
  font-size: 12px;
}

/* The picker and its Save button, as one row with a gap. Appending the two
 * straight into the cell left them touching. */
.alert-at { display: flex; align-items: center; gap: 10px; }

/* The best number is a link to the site offering it, so it needs the badge's
 * own colour rather than the browser's link blue and underline. */
a.current-badge { text-decoration: none; cursor: pointer; }
a.current-badge:hover { filter: brightness(0.94); }

/* Which site the best number came from, under the badge. */
.best-site { display: block; font-size: 11px; color: var(--ink-faint); margin-top: 3px; }
