/* ==========================================================================
   Toral Panel — Userside widgets (shared base)
   --------------------------------------------------------------------------
   Rebuilt on the design tokens in toral-userside-tokens.css.

   Rules:
   - No hard-coded colour, radius or shadow. Every value is a token, so all
     five themes and both directions work from this one file.
   - Class names and DOM structure are UNCHANGED from the previous version:
     the widgets' PHP and the toral-userside-*.js selectors keep working.
   - Direction comes from dir="rtl"/"ltr" on <html> via logical properties.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Base wrapper
   -------------------------------------------------------------------------- */
.tp-widget { width: 100%; min-width: 0; color: var(--tp-us-text); font-family: var(--tp-us-font); }
.tp-widget * { box-sizing: border-box; }

/* THIS is the rule behind the recurring "button label / icon is the same
   colour as the button" bug, so read before changing it.

   `.tp-widget a` (0,1,1) outranks a bare `.tp-btn` (0,1,0), so every link
   inside a widget was painted in the accent colour — including links that are
   really buttons, whose background IS the accent colour. It used to be fixed
   by listing the button classes to skip, one `:not()` at a time. That list is
   a maintenance trap: every new button class (tp-ann-iconbtn was the latest)
   is invisible until somebody remembers to add it, which is exactly how the
   bug kept coming back.
   The list is therefore replaced by a structural test. Every button class in
   this codebase contains "btn" — tp-btn, tp-dash-btn, tp-dash-notice__btn,
   tp-iconbtn, tp-actbtn, tp-ann-iconbtn — so one attribute match covers all of
   them, and covers the ones that do not exist yet. */
.tp-widget a:not([class*="btn"]) {
  color: var(--tp-us-accent);
  text-decoration: none;
}
.tp-widget a:not([class*="btn"]):hover {
  text-decoration: underline;
}

/* --------------------------------------------------------------------------
   2. Generic card (filters + groups)
   -------------------------------------------------------------------------- */
.tp-card {
  min-width: 0;
  padding: 14px;
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-lg);
  background: var(--tp-us-surface);
  box-shadow: var(--tp-us-shadow-sm);
}

/* Filters as separate card */
.tp-filters-card { margin-bottom: 14px; }

/* --------------------------------------------------------------------------
   3. Platform icons bar
   -------------------------------------------------------------------------- */
.tp-platform-icons-card { margin-bottom: 14px; padding: 10px 12px; }
.tp-platform-icons { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

.tp-platform-icons .tp-platform-icon {
  width: 44px;
  height: 44px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-md);
  background: transparent;
  color: var(--tp-us-text);
  cursor: pointer;
  transition: border-color var(--tp-us-transition), box-shadow var(--tp-us-transition),
              transform var(--tp-us-transition);
}
.tp-platform-icons .tp-platform-icon:hover { transform: translateY(-2px); border-color: var(--tp-us-accent); }
.tp-platform-icons .tp-platform-icon.is-active {
  border-color: var(--tp-us-accent);
  box-shadow: 0 0 0 2px var(--tp-us-accent-soft);
}
/* The picture takes almost the whole button. It used to be 24px inside a 44px
   box, which left a wide empty ring around every logo and made the bar read as
   a row of empty squares. */
.tp-platform-icons img { width: 34px; height: 34px; object-fit: contain; display: block; }
.tp-platform-fallback { font-weight: 600; opacity: .7; }

/* --------------------------------------------------------------------------
   4. Generic filters row
   -------------------------------------------------------------------------- */
.tp-filters { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.tp-filters select, .tp-filters input, .tp-filters button { padding: 8px 10px; min-height: 38px; }

/* Services filters: one single row. The stepped filters (platform -> category
   -> search -> Reset) sit at the inline start, the standalone category filter
   is pushed to the far inline end of the card. */
.tp-filters-row {
  align-items: center;
  gap: 10px;
  margin-bottom: 0;
}
.tp-filters-row .tp-filters-step {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  min-width: 0;
  margin-bottom: 0;
}
.tp-filters-row .tp-filters-simple {
  margin-bottom: 0;
  margin-inline-start: auto;
  padding-inline-end: 2px;
}
/* `.tp-input` / `.tp-select` are width:100%, which would put every control on
   its own line. Inside this row they size to their content instead. */
.tp-filters-row select,
.tp-filters-row input {
  width: auto;
  flex: 0 1 auto;
  min-width: 150px;
}
.tp-filters-row .tp-q { flex: 1 1 160px; }
.tp-filters-row .tp-filters-reset {
  min-width: 0;
  padding: 8px 12px;
  white-space: nowrap;
  font-size: 12.5px;
}
.tp-filters-row .tp-filters-reset .tp-icon { flex: none; }

/* Tablet: the row still fits, but each control has to be free to shrink. */
@media (max-width: 1024px) {
  .tp-filters-row select,
  .tp-filters-row input { min-width: 120px; }
}

/* Phone: one control per line, Reset full width under them, and the
   standalone category filter back at the top where it is easiest to reach. */
@media (max-width: 700px) {
  .tp-filters-row { flex-direction: column; align-items: stretch; gap: 8px; }
  .tp-filters-row .tp-filters-step { flex-direction: column; align-items: stretch; }
  /* `.tp-filters-row .tp-q` is listed explicitly: the rule that gives it
     `flex: 1 1 160px` is a two-class selector and would otherwise outrank this
     reset. That flex-basis is a WIDTH while the row is horizontal, but in this
     column layout it becomes the HEIGHT — which is what made the search box
     roughly 160px tall on a phone. */
  .tp-filters-row select,
  .tp-filters-row input,
  .tp-filters-row .tp-q,
  .tp-filters-row .tp-filters-reset {
    width: 100%;
    min-width: 0;
    flex: 0 0 auto;
  }
  .tp-filters-row .tp-filters-simple { order: -1; margin-inline-start: 0; padding-inline-end: 0; }
}

/* --------------------------------------------------------------------------
   5. Generic table
   -------------------------------------------------------------------------- */
.tp-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }

.tp-table th, .tp-table td {
  padding: 10px 12px;
  border-bottom: 1px solid var(--tp-us-border);
  vertical-align: middle;
  text-align: start;
}

/* Sticky column header (services list).
   The background must be fully opaque — the rows scroll underneath it — and
   `border-collapse: collapse` drops a sticky cell's own bottom border in most
   browsers, so the separating line is drawn with an inset box-shadow. */
.tp-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--tp-us-table-head);
  color: var(--tp-us-text-soft);
  border-bottom: 1px solid var(--tp-us-border-strong);
  box-shadow: inset 0 -1px 0 var(--tp-us-border-strong);
  white-space: nowrap;
}
.tp-table tbody tr:hover td { background: var(--tp-us-surface-3); }

/* Horizontal scroll for wide tables on mobile */
/* Wide tables scroll sideways ONLY where they actually have to — below 769px,
   where `.tp-table` is forced to 900px (see the media query below).
   Above that the table fits, and `overflow-x: auto` would still turn this into
   a scroll container, which would stop the sticky column header from following
   the page scroll and would clip the header tooltips. */
.tp-table-scroll {
  width: 100%;
  max-width: 100%;
  overflow-x: visible;
}
@media (max-width: 1024px) {
  .tp-table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
}

/* Keep the table wide so it scrolls instead of squeezing. Raised from 768px to
   1024px to match the scroll container above: between the two a tablet used to
   crush the Name column instead of scrolling. */
@media (max-width: 1024px) {
  .tp-table { min-width: 900px; }
}

/* --------------------------------------------------------------------------
   6. Generic pager
   -------------------------------------------------------------------------- */
.tp-pager { display: flex; gap: 6px; align-items: center; justify-content: flex-end; margin-top: 12px; }
.tp-pager .tp-page {
  padding: 6px 10px;
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-sm);
  background: var(--tp-us-surface);
  color: var(--tp-us-text);
  cursor: pointer;
  transition: background var(--tp-us-transition), border-color var(--tp-us-transition);
}
.tp-pager .tp-page:hover { background: var(--tp-us-surface-3); border-color: var(--tp-us-border-strong); }
.tp-pager .tp-page-info { padding: 0 8px; color: var(--tp-us-text-soft); }

/* --------------------------------------------------------------------------
   7. Service groups (category cards)
   -------------------------------------------------------------------------- */
/* Neither `overflow: hidden` nor a hover `transform` here any more: both make
   this element the containing block for the sticky column header inside it,
   which would pin the header to the top of the card instead of to the page.
   The rounded bottom corners are handled by the last row's cells instead. */
.tp-group {
  margin-bottom: 8px;
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-lg);
  background: var(--tp-us-surface);
  box-shadow: var(--tp-us-shadow-sm);
  transition: box-shadow var(--tp-us-transition);
}
.tp-group:hover { box-shadow: var(--tp-us-shadow); }
.tp-group .tp-table tbody tr:last-child td:first-child { border-end-start-radius: var(--tp-us-radius-lg); }
.tp-group .tp-table tbody tr:last-child td:last-child  { border-end-end-radius: var(--tp-us-radius-lg); }
.tp-group .tp-table tbody tr:last-child td { border-bottom: 0; }
.tp-group + .tp-group { margin-top: 14px; }

.tp-group-title {
  padding: 10px 12px;
  border-bottom: 1px solid var(--tp-us-border);
  border-start-start-radius: var(--tp-us-radius-lg);
  border-start-end-radius: var(--tp-us-radius-lg);
  background: var(--tp-us-accent);
  color: var(--tp-us-on-accent);
  font-weight: 600;
  text-align: center;
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, .15);
}
.tp-widget-services .tp-group-title { color: var(--tp-us-on-accent) !important; }

.tp-widget-services .tp-table thead tr { background: var(--tp-us-table-head); }
/* No `opacity` here. The header is sticky, so anything less than fully opaque
   lets the rows scrolling underneath show straight through it. The softer look
   comes from the text colour instead. */
.tp-widget-services .tp-table thead th {
  font-size: 12px;
  font-weight: 600;
  color: var(--tp-us-text-soft);
}

/* Table inside a group should not look like one big global table */
.tp-group .tp-table { margin: 0; }
.tp-group .tp-table thead th { background: var(--tp-us-table-head); }

/* Legacy group row (kept for compatibility if somewhere used) */
.tp-row-group td {
  padding-top: 14px;
  border-bottom: 1px solid var(--tp-us-border-strong);
  font-weight: 600;
}

/* --------------------------------------------------------------------------
   8. Elevation for the top filter blocks (same feel as the service groups)
   -------------------------------------------------------------------------- */
.tp-filters-card,
.tp-platform-icons-card {
  background: var(--tp-us-surface);
  border-radius: var(--tp-us-radius-lg);
  box-shadow: var(--tp-us-shadow-sm);
  transition: box-shadow var(--tp-us-transition), transform var(--tp-us-transition);
}
.tp-filters-card:hover,
.tp-platform-icons-card:hover { box-shadow: var(--tp-us-shadow); transform: translateY(-2px); }

/* --------------------------------------------------------------------------
   9. Generic modal
   -------------------------------------------------------------------------- */
.tp-modal[hidden] { display: none; }

/* ROOT FIX, scoped to the Toral widgets: the user agent's own
   `[hidden] { display: none }` is a plain element rule, so ANY class rule that
   sets `display` — .tp-card, .tp-btn, .tp-modal, .tp-filters … — silently
   beats it and a `hidden` element stays on screen. Widgets toggle `hidden`
   from PHP and JS all over the place, so it is stated once, here, instead of
   being rediscovered one component at a time. Scoped to `.tp-widget` so it
   never reaches the theme or Elementor markup around it. */
.tp-widget [hidden] { display: none !important; }

/* Present to the browser, invisible to the reader.
   Used for the hidden username field the panel puts next to a password box:
   a password manager only pairs a password with a username field it can
   actually see in the DOM, so display:none would defeat the purpose - while
   leaving it out altogether made the manager hunt through the page and fill
   the sidebar search box instead. */
.tp-offscreen {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  pointer-events: none;
}
.tp-modal { position: fixed; inset: 0; z-index: 9999; }
.tp-modal-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, .5); backdrop-filter: blur(2px); }

.tp-modal-box {
  position: relative;
  max-width: 720px;
  margin: 10vh auto 0;
  padding: 16px;
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-xl);
  background: var(--tp-us-surface);
  color: var(--tp-us-text);
  box-shadow: var(--tp-us-shadow-lg);
}

/* Logical inset so the close button lands correctly in both directions. */
.tp-modal-close {
  position: absolute;
  top: 10px;
  inset-inline-end: 10px;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border: 0;
  border-radius: var(--tp-us-radius-sm);
  background: transparent;
  color: var(--tp-us-text-soft);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  transition: background var(--tp-us-transition), color var(--tp-us-transition);
}
.tp-modal-close:hover { background: var(--tp-us-surface-3); color: var(--tp-us-text); }

.tp-modal-title { margin: 0 0 12px; font-size: 16px; font-weight: 700; }

.tp-row-loading { opacity: .7; }

/* The row a ?service=<id> deep link points at (e.g. "View service" on an
   Updates announcement): marked, not animated, so it is still obvious after
   the customer scrolls away and back.

   The mark used to be an inset ring on EVERY cell, which drew a hard box
   around each column and made the whole table look like a different, heavily
   bordered one - the rest of the panel's tables have horizontal rules only.
   It is now a soft tint plus one accent bar at the start of the row, which
   reads the same way and leaves the table's own lines alone. */
.tp-row.is-deeplink > td {
  background: color-mix(in srgb, var(--tp-us-accent) 8%, transparent);
}
.tp-row.is-deeplink > td:first-child {
  border-inline-start: 3px solid var(--tp-us-accent);
}

/* --------------------------------------------------------------------------
   10. Custom-rate badge
   -------------------------------------------------------------------------- */
.tp-custom-rate-badge {
  position: relative;
  display: inline-block;
  margin-inline-start: 4px;
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}
.tp-custom-rate-badge::after {
  content: attr(data-tip);
  position: absolute;
  bottom: 100%;
  inset-inline-start: 50%;
  transform: translateX(-50%);
  z-index: 50;
  margin-bottom: 6px;
  padding: 4px 6px;
  border-radius: var(--tp-us-radius-sm);
  background: var(--tp-us-text);
  color: var(--tp-us-surface);
  font-size: 11px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--tp-us-transition);
}
[dir="rtl"] .tp-custom-rate-badge::after { transform: translateX(50%); }
.tp-custom-rate-badge:hover::after,
.tp-custom-rate-badge.is-open::after { opacity: 1; }

/* --------------------------------------------------------------------------
   11. Sort indicator
   -------------------------------------------------------------------------- */
/* The sort arrow is an inline marker, NOT a flex layout: `display: inline-flex`
   on a <th> stops it being a table cell, which threw the header out of
   alignment with its column and broke the sticky header. Both `.tp-sort` and
   `.tp-tip` therefore have their display forced back to a table cell. */
.tp-table thead th.tp-sort,
.tp-table thead th.tp-tip { display: table-cell; }

.tp-table th.tp-sort { white-space: nowrap; }
.tp-table th.tp-sort::before {
  content: "↕";
  display: inline-block;
  margin-inline-end: 6px;
  color: var(--tp-us-accent);
  font-size: 12px;
  line-height: 1;
}
/* A sortable header is a click target and says so. It used to need `.tp-sort.tp-tip`
   in the selector to beat `.tp-tip`'s `cursor: help`; the sort headers carry no tip
   any more (see buildGroupBlock() in toral-userside-services.js), so the plain
   `.tp-sort` selector is the right home for it. */
.tp-table th.tp-sort { cursor: pointer; }
.tp-table th.tp-sort:hover { color: var(--tp-us-accent); }

/* --------------------------------------------------------------------------
   12. Service description modal
   -------------------------------------------------------------------------- */
.tp-modal .tp-modal-box { max-width: 920px; width: calc(100% - 24px); max-height: 85vh; overflow: hidden; }
.tp-modal .tp-modal-content { max-height: calc(85vh - 20px); overflow: auto; padding: 12px; }

.tp-desc-modal { display: block; }
.tp-desc-sec {
  margin: 0 0 10px;
  padding: 12px;
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-md);
}
.tp-desc-title { margin: 0 0 10px; font-size: 14px; font-weight: 700; line-height: 1.4; }
.tp-desc-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.tp-desc-kv {
  padding: 8px 10px;
  border: 1px dashed var(--tp-us-border);
  border-radius: var(--tp-us-radius-md);
}
.tp-desc-k { margin: 0 0 4px; color: var(--tp-us-text-soft); font-size: 12px; }
.tp-desc-v { font-size: 13px; font-weight: 600; word-break: break-word; }
.tp-desc-pills { display: flex; flex-wrap: wrap; gap: 8px; }
.tp-desc-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  padding: 6px 10px;
  border: 1px dashed var(--tp-us-border);
  border-radius: var(--tp-us-radius-pill);
}
.tp-desc-pill-k { color: var(--tp-us-text-soft); font-size: 12px; }
.tp-desc-pill-v { font-size: 12px; font-weight: 600; }
.tp-desc-ico { font-size: 12px; line-height: 1; }
.tp-desc-body { font-size: 13px; line-height: 1.7; word-break: break-word; }
.tp-desc-body img,
.tp-desc-body svg { max-width: 100%; height: auto; }
.tp-desc-empty { color: var(--tp-us-text-dim); }

/* The four figures stay two by two on a phone (each box takes half the width). */
@media (max-width: 560px) {
  .tp-desc-kv { padding: 8px; }
  .tp-desc-k { font-size: 11.5px; }
  .tp-desc-v { font-size: 12.5px; }
}

/* Info icon beside the Reliability / Avg time labels, and the explanation it
   opens under the grid. */
.tp-desc-info {
  display: inline-grid;
  place-items: center;
  width: 16px;
  height: 16px;
  margin-inline-start: 6px;
  padding: 0;
  border: 1px solid var(--tp-us-text-soft);
  border-radius: 50%;
  background: transparent;
  color: var(--tp-us-text-soft);
  font: italic 700 10px/1 Georgia, serif;
  vertical-align: middle;
  cursor: pointer;
}
.tp-desc-info:hover,
.tp-desc-info[aria-expanded="true"] {
  border-color: var(--tp-us-accent);
  background: var(--tp-us-accent);
  color: #fff;
}
.tp-desc-tip {
  margin-top: 10px;
  padding: 10px 12px;
  border: 1px dashed var(--tp-us-border);
  border-radius: var(--tp-us-radius-md);
  background: var(--tp-us-surface-3);
  font-size: 12px;
  line-height: 1.7;
}
.tp-desc-tip-list,
.tp-desc-tip-avg { margin-top: 6px; }

html[dir="rtl"] .tp-desc-modal,
body.rtl .tp-desc-modal { direction: rtl; text-align: right; }

/* Phones: vh is the height with the browser toolbars hidden, so a box sized in
   vh runs under Chrome's bottom bar. The box is sized from the modal itself
   (position: fixed, so exactly the visible area) and only its content scrolls. */
@media (max-width: 640px) {
  .tp-modal { padding-bottom: env(safe-area-inset-bottom, 0px); }
  .tp-modal .tp-modal-box {
    display: flex;
    flex-direction: column;
    margin-top: 12px;
    max-height: calc(100% - 24px);
  }
  .tp-modal .tp-modal-content {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
  }
}

/* --------------------------------------------------------------------------
   12b. Compare services (toral-userside-services-compare.js)
   -------------------------------------------------------------------------- */
.tp-compare-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 10px;
  margin-bottom: 14px;
  padding: 10px 12px;
  border-radius: var(--tp-us-radius-lg);
  background: var(--tp-us-surface);
  box-shadow: var(--tp-us-shadow-sm);
}
.tp-compare-bar.is-active { box-shadow: inset 0 0 0 1px var(--tp-us-accent), var(--tp-us-shadow-sm); }
.tp-compare-hint { flex: 1 1 220px; color: var(--tp-us-text-soft); font-size: 12.5px; }
.tp-compare-hint.is-warning { color: var(--tp-us-red); font-weight: 600; }
.tp-compare-count { font-size: 13px; font-weight: 700; }
.tp-compare-bar .tp-btn[disabled] { opacity: .5; cursor: not-allowed; transform: none; box-shadow: none; }

.tp-cmp-pick { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.tp-cmp-pick .tp-cmp-check { margin: 0; accent-color: var(--tp-us-accent); cursor: pointer; }

/* Every column fits the box at any width: fixed layout splits the width
   evenly, long values wrap instead of widening a column, and the text gets
   smaller on narrow screens. Nothing scrolls sideways. */
.tp-compare-table { table-layout: fixed; width: 100%; font-size: 13px; }
/* Beats the generic "min-width: 900px below 1024px" rule of section 5, which
   exists to make the services list scroll - the opposite of what this needs. */
.tp-table.tp-compare-table { min-width: 0; }
.tp-compare-table thead th { white-space: normal; }
.tp-compare-table th,
.tp-compare-table td {
  padding: 8px 6px;
  overflow-wrap: anywhere;
  word-break: break-word;
  text-align: center;
}
.tp-compare-table thead th { position: static; }
.tp-compare-table th[scope="row"] {
  width: 26%;
  color: var(--tp-us-text-soft);
  font-size: 12px;
  font-weight: 600;
  text-align: start;
}
.tp-compare-cols-2 th[scope="row"] { width: 30%; }
.tp-compare-id { color: var(--tp-us-text); font-weight: 700; }

.tp-compare-tip {
  margin-inline-start: 4px;
  vertical-align: middle;
  color: var(--tp-us-accent);
  cursor: pointer;
}
.tp-compare-tip .tp-tooltip {
  inset-inline-end: auto;
  inset-inline-start: 0;
  width: max-content;
  max-width: min(240px, 70vw);
  white-space: normal;
  font-weight: 500;
}
.tp-compare-tip.is-end .tp-tooltip { inset-inline-start: auto; inset-inline-end: 0; }

@media (max-width: 560px) {
  .tp-modal .tp-modal-content:has(.tp-compare-modal) { padding: 8px 4px; }
  .tp-compare-table { font-size: 11px; }
  .tp-compare-table th,
  .tp-compare-table td { padding: 6px 3px; }
  .tp-compare-table th[scope="row"] { width: 24%; font-size: 10.5px; }
  .tp-compare-id { font-size: 11px; }
  .tp-compare-tip { margin-inline-start: 2px; }
}

/* --------------------------------------------------------------------------
   13. Favorites
   -------------------------------------------------------------------------- */
/* Sized with the logos beside it, so the Favourites button at the start of the
   bar does not read as a smaller item than the platforms. */
.tp-platform-icon.tp-fav-star { font-size: 24px; line-height: 1; }
.tp-platform-icon.tp-fav-star.is-active { color: var(--tp-us-red); }

/* --------------------------------------------------------------------------
   13b. Services table row buttons

   These used to be full `.tp-btn`s carrying an emoji, which made them large
   and — stacked in a narrow column — pushed every row taller. They are now
   compact square icon buttons that sit side by side.
   -------------------------------------------------------------------------- */
.tp-actbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-sm);
  background: var(--tp-us-surface-2);
  color: var(--tp-us-text-soft);
  cursor: pointer;
  text-decoration: none;
  transition: background var(--tp-us-transition), color var(--tp-us-transition),
              border-color var(--tp-us-transition);
}
.tp-actbtn:hover {
  background: var(--tp-us-surface-3);
  border-color: var(--tp-us-border-strong);
  color: var(--tp-us-text);
  text-decoration: none;
}
.tp-actbtn[disabled] { opacity: .55; cursor: not-allowed; }
.tp-actbtn .tp-icon { display: block; }

.tp-actbtn-primary {
  border-color: var(--tp-us-accent);
  background: var(--tp-us-accent);
  color: var(--tp-us-on-accent);
}
.tp-actbtn-primary:hover {
  background: var(--tp-us-accent);
  border-color: var(--tp-us-accent);
  color: var(--tp-us-on-accent);
  filter: brightness(.93);
}

/* The glyph follows the button's own colour. These buttons have several
   colour variants below (favourite, details, reliability), so the colour
   itself is left to normal cascade — only `stroke` is pinned, because a
   stroke-drawn icon disappears the moment anything repaints `currentColor`. */
.tp-actbtn svg { stroke: currentColor; }

.tp-actbtn.tp-fav.is-active { color: var(--tp-us-red); border-color: color-mix(in srgb, var(--tp-us-red) 40%, transparent); }

/* The two "show me more about this service" buttons — details and the
   reliability check — share a pale blue so they read as one kind of action,
   distinct from the order button and the favourite heart. */
.tp-actbtn.tp-desc,
.tp-actbtn.tp-stats {
  background: color-mix(in srgb, var(--tp-us-blue) 14%, var(--tp-us-surface));
  border-color: color-mix(in srgb, var(--tp-us-blue) 34%, transparent);
  color: var(--tp-us-blue);
}
.tp-actbtn.tp-desc:hover,
.tp-actbtn.tp-stats:hover {
  background: color-mix(in srgb, var(--tp-us-blue) 24%, var(--tp-us-surface));
  border-color: var(--tp-us-blue);
  color: var(--tp-us-blue);
}

/* Description / Order / Favorite, in that order, on one line. */
.tp-rowacts { display: inline-flex; align-items: center; gap: 5px; }

/* Reliability + average time with the refresh button beside them. */
.tp-relbox { display: inline-flex; align-items: center; gap: 6px; }
.tp-relvals { display: inline-flex; flex-direction: column; line-height: 1.35; min-width: 0; }
.tp-relvals .tp-rel,
.tp-relvals .tp-avg { white-space: nowrap; }
.tp-relvals .tp-avg { color: var(--tp-us-text-soft); font-size: 12px; }

/* --------------------------------------------------------------------------
   14. Tooltip (services table)
   -------------------------------------------------------------------------- */
.tp-tip { position: relative; display: inline-flex; align-items: center; cursor: help; }
.tp-tip:focus { outline: none; }

.tp-tooltip {
  display: none;
  position: absolute;
  top: calc(100% + 8px);
  inset-inline-end: 0;
  z-index: 99999;
  width: auto;
  max-width: min(340px, 90vw);
  padding: 10px 12px;
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-md);
  background: var(--tp-us-text);
  color: var(--tp-us-surface);
  box-shadow: var(--tp-us-shadow-lg);
  text-align: left;
  direction: ltr;
  font-size: 12px;
  line-height: 1.55;
}
.tp-tip:hover .tp-tooltip,
.tp-tip:focus .tp-tooltip,
.tp-tip.tp-tip-open .tp-tooltip { display: block; }
.tp-tip-bul { margin-top: 6px; }

/* On a phone the bubble is pinned to the VIEWPORT, not to the header it hangs
   under.
   Below 1024px the services table scrolls sideways inside .tp-table-scroll, so
   a bubble anchored to the "Reliability / Avg Time" header began wherever that
   header happened to have been scrolled to and ran off the screen edge; the
   rest of it could only be read by scrolling the table further sideways.
   `position: fixed` takes it out of the table entirely - no ancestor here sets
   a transform/filter, so the viewport is its containing block - and the
   inline insets make it exactly as wide as the screen allows, wherever the
   table is scrolled to.

   It rises from the bottom rather than being centred, and that is deliberate:
   the sticky column header is `position: sticky; z-index: 2`, which makes it a
   stacking context, so this bubble's own z-index cannot lift it above the
   mobile bottom bar (z-index 40) from in there. Sitting above the bar it never
   has to. max-height plus overflow-y keeps a long legend inside the screen on
   a short phone.

   Scoped to a header tip inside a .tp-table, so the compare modal's own
   `.tp-compare-tip .tp-tooltip` (anchored to a table cell in a dialog that has
   no sideways scroll) is left exactly as it was. */
@media (max-width: 768px) {
  .tp-table thead .tp-tip .tp-tooltip {
    position: fixed;
    top: auto;
    bottom: 14px;
    inset-inline: 14px;
    width: auto;
    max-width: none;
    max-height: calc(100dvh - 96px);
    max-height: calc(100vh - 96px);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* Clear of the bottom bar on the phones that have one. The geometry is the
   bar's own: it sits 8px + the safe area from the bottom and is
   --tp-us-bnav-h tall (toral-userside-bottomnav.css), and .tp-us-app--bnav is
   the same class Shell puts on the app only when the bar is actually
   rendered. */
@media (max-width: 640px) {
  .tp-us-app--bnav .tp-table thead .tp-tip .tp-tooltip {
    bottom: calc(22px + var(--tp-us-bnav-h, 62px) + env(safe-area-inset-bottom, 0px));
    max-height: calc(100dvh - 96px - var(--tp-us-bnav-h, 62px));
    max-height: calc(100vh - 96px - var(--tp-us-bnav-h, 62px));
  }
}

/* --------------------------------------------------------------------------
   15. Orders widget
   -------------------------------------------------------------------------- */
.tp-widget-orders .tp-orders-card {
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-lg);
  background: var(--tp-us-surface);
  box-shadow: var(--tp-us-shadow-sm);
  transition: box-shadow var(--tp-us-transition), transform var(--tp-us-transition);
}
/* No lift on hover here, unlike the other cards: a `transform` on an ancestor
   makes that ancestor the containing block, which breaks the sticky column
   header inside this card. The shadow alone gives the same feedback. */
.tp-widget-orders .tp-orders-card:hover { box-shadow: var(--tp-us-shadow); }

.tp-widget-orders .tp-orders-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 14px;
  border-start-start-radius: var(--tp-us-radius-lg);
  border-start-end-radius: var(--tp-us-radius-lg);
  background: var(--tp-us-accent);
  color: var(--tp-us-on-accent);
}
.tp-widget-orders .tp-orders-title { font-weight: 700; }
.tp-widget-orders .tp-orders-meta { font-size: 12px; opacity: .85; }

/* Status bar */
.tp-statusbar { display: flex; flex-wrap: wrap; gap: 8px; }
.tp-statusbtn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-md);
  background: var(--tp-us-surface);
  color: var(--tp-us-text);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  transition: box-shadow var(--tp-us-transition), border-color var(--tp-us-transition);
}
.tp-statusbtn:hover { border-color: var(--tp-us-border-strong); }
.tp-statusbtn.is-active { border-color: var(--tp-us-accent); box-shadow: var(--tp-us-shadow); }
.tp-statusbtn__label { white-space: nowrap; }

.tp-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 26px;
  height: 20px;
  padding: 0 8px;
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-pill);
  font-size: 11px;
  font-weight: 700;
}
.tp-badge-ok         { background: color-mix(in srgb, var(--tp-us-green) 18%, transparent); }
.tp-badge-pending    { background: color-mix(in srgb, var(--tp-us-amber) 18%, transparent); }
.tp-badge-processing { background: color-mix(in srgb, var(--tp-us-blue) 18%, transparent); }
.tp-badge-progress   { background: color-mix(in srgb, var(--tp-us-cyan) 16%, transparent); }
.tp-badge-warn       { background: color-mix(in srgb, var(--tp-us-amber) 22%, transparent); }
.tp-badge-danger     { background: color-mix(in srgb, var(--tp-us-red) 18%, transparent); }

/* Orders grid "table" */
.tp-orders-table { width: 100%; }
.tp-ot-head, .tp-ot-row {
  display: grid;
  /* The # column also holds the copy-order-number button, so it is wider
     than the bare number needed; the width comes out of Order Details, which
     wraps, so the total stays the same. */
  grid-template-columns: 6% 38% 8% 9% 9% 9% 17%;
  gap: 12px;
  align-items: start;
}
/* Sticky column header.
   The scroll container is .tp-us-main (the chrome's main column), so `top: 0`
   pins the header to the top of the content area while the list scrolls under
   it. The background must be opaque, or the rows would show through. */
.tp-ot-head {
  position: sticky;
  top: 0;
  z-index: 3;
  padding: 10px 8px;
  border-bottom: 1px solid var(--tp-us-border);
  background: var(--tp-us-table-head);
  color: var(--tp-us-text-soft);
  font-size: 12px;
  font-weight: 600;
}
.tp-ot-row {
  padding: 12px 8px;
  border-bottom: 1px solid var(--tp-us-border);
  transition: background var(--tp-us-transition);
}
.tp-ot-row:hover { background: var(--tp-us-surface-2); }

/* Fixed columns: prevent content from resizing the grid */
.tp-ot-col { font-size: 13px; min-width: 0; overflow: hidden; }
.tp-ot-id,
.tp-ot-qty,
.tp-ot-price,
.tp-ot-date,
.tp-ot-status,
.tp-ot-act { white-space: nowrap; text-overflow: ellipsis; }
.tp-ot-details { white-space: normal; }

/* Details */
.tp-od-line { margin: 2px 0; }
.tp-od-line span { margin-inline-end: 6px; color: var(--tp-us-text-soft); }

/* Link boxed field */
.tp-od-linkline { margin-top: 6px; }
.tp-od-linkline .tp-linkbox {
  width: 100%;
  padding: 6px 8px;
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-md);
  background: var(--tp-us-surface-2);
}
.tp-od-linkline span:first-child { white-space: nowrap; }

.tp-linkbox { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; }
.tp-linkbox__text {
  display: block;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  white-space: nowrap;
  text-overflow: unset;
  scrollbar-width: thin;
}
.tp-linkbox__text a { display: inline-block; }

/* Activity icons */
.tp-iconbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-md);
  background: var(--tp-us-surface);
  color: var(--tp-us-text);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  transition: background var(--tp-us-transition), border-color var(--tp-us-transition),
              transform var(--tp-us-transition);
}
.tp-iconbtn:hover { transform: translateY(-1px); border-color: var(--tp-us-border-strong); }
.tp-iconbtn.is-disabled { opacity: .65; cursor: not-allowed; transform: none; }

/* Order number cell: the number and its compact copy button on one line. */
.tp-ot-id { display: flex; align-items: center; gap: 4px; }
.tp-ot-idnum { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.tp-copy-id { width: 22px; height: 22px; padding: 0; font-size: 11px; }

/* Copy feedback. The markup (.tp-copy[data-copy] + .is-copied) is produced by
   toral-userside-orders.js and toral-userside-api.js — unchanged. */
.tp-copy.is-copied {
  border-color: var(--tp-us-green);
  color: var(--tp-us-green);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--tp-us-green) 20%, transparent);
}

/* Colourised actions */
.tp-act-refill { color: var(--tp-us-blue);  border-color: color-mix(in srgb, var(--tp-us-blue) 35%, transparent); }
.tp-act-cancel { color: var(--tp-us-red);   border-color: color-mix(in srgb, var(--tp-us-red) 35%, transparent); }
.tp-act-ticket { color: var(--tp-us-accent);border-color: color-mix(in srgb, var(--tp-us-accent) 35%, transparent); }

.tp-act-refill:not(.is-disabled):hover { background: color-mix(in srgb, var(--tp-us-blue) 10%, transparent); }
.tp-act-cancel:not(.is-disabled):hover { background: color-mix(in srgb, var(--tp-us-red) 10%, transparent); }
.tp-act-ticket:hover                   { background: var(--tp-us-accent-soft); }

/* Date on two lines */
.tp-date2 { display: inline-block; line-height: 1.25; white-space: normal; }
.tp-date2 .tp-date { display: block; }
.tp-date2 .tp-time { display: block; margin-top: 2px; color: var(--tp-us-text-soft); font-size: 12px; }

/* States */
.tp-loading, .tp-empty, .tp-error {
  padding: 14px 10px;
  font-size: 13px;
  color: var(--tp-us-text-soft);
}
.tp-error { color: var(--tp-us-red); }

/* Tablet */
@media (max-width: 1024px) {
  .tp-ot-head, .tp-ot-row { grid-template-columns: 3% 34% 8% 12% 13% 13% 17%; }
}

/* Desktop: hide mobile labels */
.tp-widget-orders .tp-m-label { display: none; }
.tp-widget-orders .tp-m-val { display: inline; }

/* Mobile card layout */
@media (max-width: 860px) {
  .tp-widget-orders .tp-m-label { display: inline-block; }
  .tp-widget-orders .tp-m-val { display: inline-block; }
  .tp-ot-head { display: none; }

  .tp-ot-row {
    display: block;
    gap: 10px;
    margin-bottom: 10px;
    border: 1px solid var(--tp-us-border);
    border-radius: var(--tp-us-radius-lg);
  }

  .tp-ot-col { display: block; white-space: normal; margin-top: 8px; }
  .tp-ot-col:first-child { margin-top: 0; }
  .tp-ot-col .tp-m-label {
    display: inline-block;
    min-width: 72px;
    font-weight: 600;
    color: var(--tp-us-text-soft);
  }
  .tp-ot-col .tp-m-val { display: inline-block; vertical-align: middle; }

  .tp-m-label {
    display: inline-block;
    min-width: 72px;
    font-weight: 600;
    color: var(--tp-us-text-soft);
  }
  .tp-m-val { display: inline-block; }

  .tp-ot-id { font-weight: 700; }
  .tp-ot-act { display: flex; gap: 8px; }

  .tp-linkbox__text {
    white-space: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    max-height: none;
    word-break: normal;
    -webkit-overflow-scrolling: touch;
  }

  .tp-statusbar { display: flex; flex-wrap: wrap; gap: 8px; }
  .tp-statusbtn { width: calc(50% - 4px); justify-content: center; }

  .tp-widget-orders .tp-orders-toprow { flex-wrap: wrap; justify-content: flex-start; }
  .tp-widget-orders .tp-orders-toprow .tp-filter-status { width: 100%; }

  /* Phone: search box on its own line, then the mode select, the search button
     and the sort control share the next line. The sort select keeps the select
     and its direction button side by side — they must never stack. */
  .tp-widget-orders .tp-orders-searchrow .tp-orders-search { flex: 1 0 100%; min-width: 0; }
  .tp-widget-orders .tp-orders-searchrow .tp-orders-search-mode { flex: 1 1 auto; min-width: 0; }
  .tp-widget-orders .tp-filter-sort { flex: 1 1 auto; min-width: 0; margin-inline-start: 0; }
  .tp-widget-orders .tp-filter-sort .tp-select { flex: 1 1 auto; width: auto; min-width: 0; }

  .tp-od-statline { gap: 4px 12px; }
  .tp-progress { flex: 1 0 100%; max-width: none; }
}

/* Filter card: kept deliberately compact — the status chips and the search row
   used to be separated by a full row gap plus the sort row, which made the
   card taller than the first two order rows put together. */
.tp-widget-orders .tp-filters-card { padding: 10px 12px; }
.tp-widget-orders .tp-orders-filters { margin-bottom: 0; }

.tp-widget-orders .tp-orders-toprow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.tp-widget-orders .tp-orders-searchrow {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  flex-wrap: wrap;
}
/* `.tp-select` is width:100% by default, which pushed the search box onto a
   second line. The mode select only needs room for its longest option. */
.tp-widget-orders .tp-orders-searchrow .tp-orders-search-mode {
  flex: none;
  width: auto;
  min-width: 130px;
}
.tp-widget-orders .tp-orders-searchrow .tp-orders-search { flex: 1 1 160px; width: auto; min-width: 140px; }

/* Sort control pinned to the far end of the search row, with the direction
   button immediately beside its select rather than underneath it. */
.tp-widget-orders .tp-filter-sort {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
  margin-inline-start: auto;
}
.tp-widget-orders .tp-filter-sort .tp-select { width: auto; min-width: 120px; }
.tp-widget-orders .tp-filter-sort .tp-orders-order {
  flex: none;
  width: 38px;
  min-width: 38px;
  padding-inline: 0;
}

.tp-widget-orders .tp-orders-search-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0 14px;
  border: 1px solid var(--tp-us-accent);
  border-radius: var(--tp-us-radius-sm);
  background: var(--tp-us-accent);
  color: var(--tp-us-on-accent);
}
.tp-widget-orders .tp-orders-search-btn:hover { filter: brightness(.95); }
.tp-widget-orders .tp-orders-search-btn .tp-icon-search { display: block; fill: currentColor; }

.tp-widget-orders .tp-orders-searchrow select,
.tp-widget-orders .tp-orders-searchrow input[type="search"],
.tp-widget-orders .tp-orders-searchrow .tp-btn,
.tp-widget-orders .tp-orders-search-btn {
  border-radius: var(--tp-us-radius-sm);
  height: 38px;
  line-height: 38px;
  padding-block: 0;
  min-height: 0;
}

/* --------------------------------------------------------------------------
   16. Shared form and button classes
   -------------------------------------------------------------------------- */
.tp-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
.tp-label { font-weight: 600; font-size: 13px; color: var(--tp-us-text-soft); }
.tp-required { color: var(--tp-us-red); }

.tp-input, .tp-select {
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-md);
  background: var(--tp-us-surface-2);
  color: var(--tp-us-text);
  font-family: inherit;
  font-size: 14px;
  transition: border-color var(--tp-us-transition), box-shadow var(--tp-us-transition);
}
.tp-input:focus, .tp-select:focus {
  outline: none;
  border-color: var(--tp-us-accent);
  box-shadow: 0 0 0 3px var(--tp-us-accent-soft);
}
/* Sapphire: blue border with a soft yellow halo on focus. */
[data-tp-us-theme="sapphire"] .tp-input:focus,
[data-tp-us-theme="sapphire"] .tp-select:focus { box-shadow: 0 0 0 3px var(--tp-us-pop-soft); }
.tp-input::placeholder { color: var(--tp-us-text-dim); }
textarea.tp-input { resize: vertical; min-height: 80px; }

.tp-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px 14px;
  border: 1px solid transparent;
  border-radius: var(--tp-us-radius-md);
  background: var(--tp-us-accent);
  color: var(--tp-us-on-accent);
  font-family: inherit;
  font-weight: 600;
  font-size: 13px;
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--tp-us-transition), box-shadow var(--tp-us-transition),
              background var(--tp-us-transition);
}
.tp-btn:hover { transform: translateY(-1px); box-shadow: var(--tp-us-shadow); text-decoration: none; }
.tp-btn:active { transform: translateY(0); }

/*
 * The recurring "button label is the same colour as the button" bug.
 *
 * Its source is the widget link rule at the top of this file, and that is
 * where it is actually fixed — see the note there. Buttons are almost always
 * <a> elements, so any rule that paints descendant links in the accent colour
 * repaints a filled button's own label in its own background colour.
 *
 * Belt and braces here: the button colours are pinned so a theme- or
 * Elementor-level `a { color: … }` (printed after the plugin's stylesheets,
 * and therefore winning any equal-weight contest) cannot bring it back. This
 * is the one place `!important` is the right tool — it says the panel paints
 * its own buttons, the page does not. The VALUES are still theme variables,
 * so the per-widget Elementor style controls keep working unchanged.
 *
 * Icons get the same treatment through `stroke`: the glyphs are stroke-drawn
 * from `currentColor`, so a repainted colour makes them vanish. Nothing
 * outside the panel styles `stroke`.
 */
.tp-btn.tp-btn-primary,
a.tp-btn-primary {
  background: var(--tp-us-accent);
  color: var(--tp-us-on-accent) !important;
  border-color: var(--tp-us-accent);
}
/* The ghost button reads its three colours from variables.
   The !important above is what keeps a theme or Elementor `a { color }` from
   repainting a button label - but it also beat every legitimate override the
   panel itself wanted to make, which is how "Sign in" ended up dark-on-dark on
   the public header bar. A context that puts a ghost button somewhere other
   than a content surface now re-declares these three variables on its own
   container (see .tp-pub-actions in toral-userside-auth.css) instead of
   fighting the !important, which stays exactly as strong as it was. */
.tp-btn.tp-btn-ghost,
a.tp-btn-ghost {
  background: var(--tp-us-btn-ghost-bg, var(--tp-us-surface));
  color: var(--tp-us-btn-ghost-text, var(--tp-us-text)) !important;
  border-color: var(--tp-us-btn-ghost-line, var(--tp-us-border));
}
.tp-btn.tp-btn-ghost:hover,
a.tp-btn-ghost:hover { background: var(--tp-us-btn-ghost-hover, var(--tp-us-surface-3)); }
.tp-btn.tp-btn-danger,
a.tp-btn-danger { background: var(--tp-us-red); color: #fff !important; border-color: var(--tp-us-red); }

/* The glyph follows the label, whatever the page tried to do to the colour. */
.tp-btn svg { stroke: currentColor; }
.tp-btn.tp-btn-primary svg, a.tp-btn-primary svg { stroke: var(--tp-us-on-accent); }
.tp-btn.tp-btn-ghost svg,   a.tp-btn-ghost svg   { stroke: var(--tp-us-btn-ghost-text, var(--tp-us-text)); }
.tp-btn.tp-btn-danger svg,  a.tp-btn-danger svg  { stroke: #fff; }

/* Compact size, same name panel-admin already uses for its small buttons. */
.tp-btn.tp-btn-sm { padding: 6px 11px; font-size: 12.5px; }

.tp-help { color: var(--tp-us-text-soft); font-size: 12.5px; }

/* --------------------------------------------------------------------------
   17. Alerts
   -------------------------------------------------------------------------- */
.tp-alert {
  padding: 10px 12px;
  border: 1px solid var(--tp-us-border);
  border-inline-start: 3px solid var(--tp-us-text-dim);
  border-radius: var(--tp-us-radius-md);
  background: var(--tp-us-surface-2);
  font-size: 13px;
}
.tp-alert-error {
  border-inline-start-color: var(--tp-us-red);
  background: color-mix(in srgb, var(--tp-us-red) 9%, var(--tp-us-surface));
  color: var(--tp-us-red);
}
.tp-alert-success {
  border-inline-start-color: var(--tp-us-green);
  background: color-mix(in srgb, var(--tp-us-green) 9%, var(--tp-us-surface));
}
.tp-alert-warning {
  border-inline-start-color: var(--tp-us-amber);
  background: color-mix(in srgb, var(--tp-us-amber) 9%, var(--tp-us-surface));
}
.tp-alert-info {
  border-inline-start-color: var(--tp-us-blue);
  background: color-mix(in srgb, var(--tp-us-blue) 9%, var(--tp-us-surface));
}

/*
 * "Verify now", appended to a warning by window.tpVerifyGate()
 * (assets/js/toral-userside.js).
 *
 * Every place the panel tells an unverified customer they cannot do something
 * also has to offer the way out of it, so this button turns up inside gate
 * messages on New Order, Wallet and anywhere else that calls tpVerifyGate.
 */
.tp-verify-cta {
  margin-inline-start: 8px;
  vertical-align: middle;
}

/* --------------------------------------------------------------------------
   18. Status pill
   -------------------------------------------------------------------------- */
.tp-pill {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding-inline: 10px;
  border-radius: var(--tp-us-radius-pill);
  font-size: 11.5px;
  font-weight: 700;
}
.tp-pill--completed   { background: color-mix(in srgb, var(--tp-us-green) 18%, transparent); color: var(--tp-us-green); }
.tp-pill--processing,
.tp-pill--in_progress { background: color-mix(in srgb, var(--tp-us-blue) 18%, transparent);  color: var(--tp-us-blue); }
.tp-pill--pending     { background: color-mix(in srgb, var(--tp-us-amber) 20%, transparent); color: var(--tp-us-amber); }
.tp-pill--partial     { background: color-mix(in srgb, var(--tp-us-cyan) 18%, transparent);  color: var(--tp-us-cyan); }
.tp-pill--canceled    { background: color-mix(in srgb, var(--tp-us-red) 18%, transparent);   color: var(--tp-us-red); }

/* "Subscription" next to a service name in the services list, the same
   marker the admin services table shows. */
.tp-pill--sub {
  height: 20px;
  padding-inline: 8px;
  margin-inline-start: 4px;
  vertical-align: middle;
  white-space: nowrap;
  background: color-mix(in srgb, var(--tp-us-accent) 16%, transparent);
  color: var(--tp-us-accent);
}

/* --------------------------------------------------------------------------
   19. Order delivery progress bar
   Fed by the quantity/remains pair the orders payload already carries.

   The whole track is colour-coded, not only the filled part, so the state of
   an order reads at a glance:
     - the track colour says what the UNDELIVERED share means
       (neutral = still coming, red = cancelled),
     - the bar colour says what was actually delivered (green).
   -------------------------------------------------------------------------- */
.tp-progress {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 1 1 110px;
  min-width: 90px;
  max-width: 190px;
}

.tp-progress__track {
  flex: 1;
  height: 5px;
  border-radius: var(--tp-us-radius-pill);
  background: var(--tp-us-surface-3);
  overflow: hidden;
}
/* `display: block` is load-bearing, not cosmetic: this is a <span>, and an
   inline box ignores both `width` and `height`. Without it the percentage fill
   never rendered at all — only the track colour showed, which is why a running
   order looked like it had no bar and a partial one looked entirely red. */
.tp-progress__bar {
  display: block;
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: var(--tp-us-green);
  transition: width 600ms cubic-bezier(.4, 0, .2, 1);
}

/* Completed: full green track (the bar is already at 100%). */
.tp-progress--completed .tp-progress__track { background: var(--tp-us-green); }

/* Running, but the provider has not reported `remains` yet: a moving bar
   rather than an invisible 0% one. */
.tp-progress.is-indeterminate .tp-progress__bar {
  width: 40%;
  background: var(--tp-us-green);
  animation: tp-progress-slide 1.5s ease-in-out infinite;
  transition: none;
}
@keyframes tp-progress-slide {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}
.tp-progress.is-indeterminate .tp-progress__label { letter-spacing: 1px; }

/* Canceled: nothing was delivered, so the whole track is red. */
.tp-progress--canceled .tp-progress__track { background: var(--tp-us-red); }
.tp-progress--canceled .tp-progress__bar   { background: transparent; }
.tp-progress--canceled .tp-progress__label { color: var(--tp-us-red); }

/* Partial: the delivered share stays green, the remainder turns red because
   it was cancelled rather than left pending. */
.tp-progress--partial .tp-progress__track { background: var(--tp-us-red); }
.tp-progress--partial .tp-progress__bar   { background: var(--tp-us-green); }

.tp-progress__label {
  flex: none;
  color: var(--tp-us-text-soft);
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Start / Remains / progress bar on one line, so the order row stays short. */
.tp-od-statline {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin-top: 6px;
}
/* `.tp-od-line span` above paints every span soft-grey and adds a trailing
   margin; that rule is meant for the "Start:" style labels, so the two
   wrappers on this line opt out and only their inner label keeps it. */
.tp-od-statline > .tp-od-stat,
.tp-od-statline > .tp-progress {
  margin-inline-end: 0;
  color: var(--tp-us-text);
}
.tp-od-stat { white-space: nowrap; font-size: 12.5px; }

/* What an order of a special service type is made of (its comment list,
   usernames, hashtag …). Folded away, so a hundred comments never stretch the
   row; a Default order never renders this at all. */
.tp-pill--type {
  background: color-mix(in srgb, var(--tp-us-accent) 16%, transparent);
  color: var(--tp-us-accent);
  cursor: pointer;
}
.tp-od-type { margin-top: 6px; }
.tp-od-type > summary { list-style: none; cursor: pointer; }
.tp-od-type > summary::-webkit-details-marker { display: none; }
.tp-od-typerow { margin-top: 6px; }
.tp-od-typek {
  display: block;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--tp-us-text-soft);
}
.tp-od-typev {
  margin: 2px 0 0;
  max-height: 160px;
  overflow: auto;
  padding: 6px 8px;
  border: 1px solid var(--tp-us-border);
  border-radius: 8px;
  background: var(--tp-us-surface-2, transparent);
  font-family: inherit;
  font-size: 12.5px;
  line-height: 1.7;
  white-space: pre-wrap;
  word-break: break-word;
}

/* --------------------------------------------------------------------------
   20. Skeleton loading
   Rendered by the widget scripts in place of the old plain-text "loading".
   -------------------------------------------------------------------------- */
.tp-skeleton { display: grid; gap: 10px; padding: 4px 0; }

.tp-skeleton__row {
  height: 15px;
  border-radius: var(--tp-us-radius-sm);
  background: linear-gradient(
    90deg,
    var(--tp-us-surface-2) 0%,
    var(--tp-us-surface-3) 40%,
    var(--tp-us-surface-2) 80%
  );
  background-size: 300% 100%;
  animation: tp-shimmer 1.4s ease-in-out infinite;
}
.tp-skeleton__row--wide  { width: 100%; }
.tp-skeleton__row--half  { width: 55%; }
.tp-skeleton__row--short { width: 32%; }
.tp-skeleton__row--tall  { height: 46px; }

@keyframes tp-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}

/* --------------------------------------------------------------------------
   21. Empty state
   -------------------------------------------------------------------------- */
.tp-empty-state {
  display: grid;
  justify-items: center;
  gap: 6px;
  padding: 34px 20px;
  text-align: center;
}
.tp-empty-state__icon {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  margin-bottom: 4px;
  border-radius: var(--tp-us-radius-pill);
  background: var(--tp-us-accent-soft);
  color: var(--tp-us-accent);
}
.tp-empty-state__title { font-size: 14.5px; font-weight: 700; }
.tp-empty-state__text  { max-width: 320px; color: var(--tp-us-text-soft); font-size: 13px; }
.tp-empty-state .tp-btn { margin-top: 10px; }

/* --------------------------------------------------------------------------
   22. Overflow guards
   Any block that can receive wide content must be allowed to shrink, or it
   pushes the whole page sideways on a phone (flex/grid items default to
   min-width: auto).
   -------------------------------------------------------------------------- */
.tp-card,
.tp-group,
.tp-dash-card,
.tp-dash-card__body,
.tp-dash-stat,
.tp-dash-notice { min-width: 0; }

@media (prefers-reduced-motion: reduce) {
  .tp-skeleton__row { animation: none; }
  .tp-progress__bar { transition: none; }
  /* The indeterminate bar still has to be visible, so it parks in the middle
     of the track instead of animating. */
  .tp-progress.is-indeterminate .tp-progress__bar { animation: none; transform: translateX(75%); }
}

/* --------------------------------------------------------------------------
   23. Info dialog ("i" button)
   The little "i" that opens a short explanation, and the centred dialog it
   opens. Open/close/Escape live in assets/js/toral-userside-chrome.js, which
   every Userside page loads, so a widget needs no script of its own for it.

   It started life on the Dashboard level card (hence the .tp-dash-tip name,
   kept so no markup had to change) and lived in toral-userside-dashboard.css.
   The Rewards wheel needs the same control, and that page does not load the
   dashboard stylesheet, so the shared half of the component moved here rather
   than being copied. The level-table rows inside it (.tp-dash-tip__table and
   below) are still the Dashboard's own and stayed there.
   -------------------------------------------------------------------------- */
.tp-dash-tip { position: relative; display: inline-flex; }

.tp-dash-tip__btn {
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: var(--tp-us-radius-pill);
  background: transparent;
  color: var(--tp-us-text-dim);
  cursor: pointer;
  transition: color var(--tp-us-transition), background var(--tp-us-transition);
}
.tp-dash-tip__btn:hover,
.tp-dash-tip__btn:focus-visible { color: var(--tp-us-accent); background: var(--tp-us-surface-3); }

/* A dialog, not a tooltip.
   Anchored to the icon it could not fit: the panel is 420px wide and the icon
   sits near the edge of the content area, so it hung off the page on desktop
   and off both sides on a phone. Fixed to the viewport and centred, its size
   no longer depends on where the icon is. z-index clears the widget modals
   (.tp-modal = 9999, new-order = 100000) the same way the confirm dialog in
   toral-userside-chrome.css does. */
.tp-dash-tip__modal {
  position: fixed;
  inset: 0;
  z-index: 100005;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  font-weight: 400;
}
.tp-dash-tip__modal[hidden] { display: none; }

.tp-dash-tip__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, .5);
  backdrop-filter: blur(2px);
}

.tp-dash-tip__pop {
  position: relative;
  display: grid;
  gap: 10px;
  width: min(520px, 100%);
  max-height: calc(100dvh - 32px);
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 18px 20px;
  border: 1px solid var(--tp-us-border);
  border-radius: var(--tp-us-radius-xl);
  background: var(--tp-us-surface);
  box-shadow: var(--tp-us-shadow-lg);
  text-align: start;
  animation: tp-dash-tip-in 180ms cubic-bezier(.4, 0, .2, 1);
}

@keyframes tp-dash-tip-in {
  from { opacity: 0; transform: translateY(8px) scale(.98); }
  to   { opacity: 1; transform: none; }
}

.tp-dash-tip__close {
  position: absolute;
  top: 10px;
  inset-inline-end: 10px;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: var(--tp-us-radius-sm);
  background: transparent;
  color: var(--tp-us-text-dim);
  cursor: pointer;
  transition: background var(--tp-us-transition), color var(--tp-us-transition);
}
.tp-dash-tip__close:hover { background: var(--tp-us-surface-3); color: var(--tp-us-text); }

.tp-dash-tip__title { padding-inline-end: 30px; font-size: 14.5px; font-weight: 700; }
.tp-dash-tip__text { color: var(--tp-us-text-soft); font-size: 12.5px; line-height: 1.9; }
