/* ==========================================================================
   Admin area — extracted inline styles from the UI/ prototype (admin-login.html,
   admin-tfa.html, admin-dashboard.html, ...). Mandatory rule 2 forbids inline
   style="" attributes; these classes reproduce the exact same values so the
   ported pages render pixel-for-pixel identically to the prototype.
   ========================================================================== */

.u-mt-10 { margin-top: 10px; }
.u-mt-14 { margin-top: 14px; }
.u-mt-16 { margin-top: 16px; }
.u-mt-18 { margin-top: 18px; }
.u-mt-20 { margin-top: 20px; }
.u-mt-24 { margin-top: 24px; }
.u-mb-6 { margin-bottom: 6px; }
.u-mb-8 { margin-bottom: 8px; }
.u-mb-10 { margin-bottom: 10px; }
.u-mb-12 { margin-bottom: 12px; }
.u-mb-14 { margin-bottom: 14px; }
.u-mb-16 { margin-bottom: 16px; }
.u-mb-18 { margin-bottom: 18px; }
.u-mb-20 { margin-bottom: 20px; }
.u-mb-24 { margin-bottom: 24px; }

/* Banner/Delivery image-preview containers (Home Banners, Recent Deliveries) —
   was inline style="max-width:…" on the "current image" panel already in this
   file before item 8/10's preview work; pulled out to a class per rule 2. */
.u-maxw-220 { max-width: 220px; }
.u-maxw-200 { max-width: 200px; }
.u-maxw-180 { max-width: 180px; } /* Vendor Company profile's logo preview (UI/vendor-profile.html) */

/* A new listing renders its photo grid empty and lets JS fill it, whereas an
   existing one omits the grid until it has photos. Without this the empty
   container's own top margin left the Add screen with a gap the Edit screen
   doesn't have. */
.media-grid:empty { margin-top: 0; }

/* Panel sub-heading — the smaller <h3> inside a panel (Inquiry "Message",
   breach "What happened", error-log "Stack trace"). The prototype writes it as
   `.panel__title` with an inline `font-size:15px`; these call sites previously
   reused `.u-small`, which is 13px — 2px too small. */
.panel__title--sub { font-size: 15px; }

/* Numeric column headers. The prototype's PortalTable right-aligns a numeric
   column's <th> with an inline `style="text-align:right"` (js/portal.js), but
   app.css only ships `.table-st td.num` — so a ported `<th class="num">` was
   left-aligned over right-aligned figures. Rule 2 forbids the inline style, so
   the matching header rule lives here. */
.table-st th.num { text-align: right; }

/* admin-audit-logs.html: failure/rate-limit rows are tinted red inline
   (`style="color:var(--red)"`) in the prototype's own JS — reproduced as a
   utility class per mandatory rule 2 instead of an inline style. */
.u-danger { color: var(--red); }

/* Equipment editor's photo-count line tints its lead-in by how close the listing
   is to the recommended 4 photos — the prototype does this with inline
   `style="color:var(--red|--amber|--green)"`; rule 2 says class, not attribute. */
.u-warn { color: var(--amber); }
.u-ok { color: var(--green); }

/* admin-categories.html's spec-template textarea */
.textarea-spec { min-height: 90px; }

/* Freeform text block (Inquiry message, breach description, error log
   message/stack trace) — was `style="padding:16px;background:var(--surface);
   border-radius:12px"` inline in Inquiries/Details.cshtml and
   Pdpa/BreachDetails.cshtml; moved here per mandatory rule 2. */
.text-block { padding: 16px; background: var(--surface); border-radius: 12px; }
.text-block--code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  white-space: pre-wrap;
  word-break: break-word;
  overflow-x: auto;
}

.topbar-left-flex {
  display: flex;
  align-items: center;
  gap: 14px;
}

.grid--align-start { align-items: start; }

/* admin-tfa.html's six-digit code input */
.tfa-code-input {
  letter-spacing: .4em;
  font-size: 20px;
  text-align: center;
}

.link-blue { color: var(--blue); }

/* ---------------------------------------------------------------------
   Global busy-state feedback (js/admin/loading.js). Every submit button
   spins + disables on click; every same-origin nav link starts a slim
   top progress bar. Applies to any .btn-st with no per-view markup.
   --------------------------------------------------------------------- */
.btn-st { position: relative; }

.btn-st.is-loading {
  pointer-events: none;
  opacity: .85;
}

.btn-st.is-loading .btn-st__label { visibility: hidden; }

.btn-st.is-loading::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  animation: st-spin .6s linear infinite;
}

@keyframes st-spin {
  to { transform: rotate(360deg); }
}

.page-loading-bar {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  width: 0;
  background: var(--blue);
  z-index: 2000;
  opacity: 0;
  transition: opacity .2s ease;
}

.page-loading-bar.is-active {
  opacity: 1;
  width: 70%;
  transition: width 4s cubic-bezier(.1, .6, .2, 1), opacity .2s ease;
}

/* ---------------------------------------------------------------------
   Equipment approval queue's inline "Photos & documents" review row
   (admin-equipment-pending.html) — a <details> expander rather than a
   modal, so it opens without leaving the queue and needs no JS.
   --------------------------------------------------------------------- */
.table-media-row {
  background: var(--surface);
  padding: 14px 16px 18px;
  border-bottom: 1px solid var(--line);
}

.table-media-row summary {
  cursor: pointer;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--navy);
}

/* ---------------------------------------------------------------------
   Equipment editor (admin-equipment-edit.html) — Sale/Lease reveal and
   choice-card highlight, done with :has() instead of the prototype's
   JS listeners, so it works correctly on first render (server already
   sets the matching radio's checked attribute) with no script needed.
   --------------------------------------------------------------------- */
[data-lease-only] { display: none; }
.panel:has(input[name="ListingType"][value="Lease"]:checked) [data-lease-only] { display: block; }
.choice:has(input:checked) { border-color: var(--yellow); background: rgba(255, 227, 0, .10); }

/* Newsletters composer (admin-newsletters.html) — Schedule reveal, same
   :has()-over-JS precedent as the Equipment Sale/Lease reveal above. */
[data-schedule-only] { display: none; }
.panel:has(input[name="Form.SendOption"][value="schedule"]:checked) [data-schedule-only] { display: block; }

/* Native <input type="file"> dropzone: the file input itself is the
   click/keyboard/drag-and-drop target — it's positioned to cover its
   parent .dropzone label so no JS is needed for any of those. */
.dropzone { position: relative; }
.dropzone__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}
.dropzone:has(.dropzone__input:focus-visible) { outline: 3px solid var(--yellow); outline-offset: 2px; }

/* portal__brand is now a link to the public site; portal__user is now a
   dropdown-toggle <button> (Phase 7 header menu, see below) — both need
   default <a>/<button> chrome suppressed since they reuse existing classes
   not designed for those elements. */
.portal__brand a,
.portal__user.portal__user { text-decoration: none; color: inherit; }
.portal__user.portal__user { background: none; border: 0; cursor: pointer; }

/* Phase 7 — sidebar nav-group collapse/expand. .portal__navlabel keeps its
   existing app.css text styling (padding/font-size/letter-spacing/color);
   these rules only add the button reset + row layout for the +/– sign.
   .portal__nav's default app.css display:flex is overridden to display:none
   here so a collapsed group's links don't take up sidebar space, matching
   the show/hide (no animation) behavior of the existing .acc__panel toggle
   in ui.js. */
.portal__navtoggle {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
}
.portal__navtoggle:hover { color: rgba(255, 255, 255, .7); }
.portal__navsign { font-size: 13px; flex: none; }
.portal__nav { display: none; }
.portal__nav.is-open { display: flex; }

/* Dashboard is a top-level sidebar link rather than a collapsible group of one
   (it has no .portal__navgroup wrapper to supply the group's own top margin). */
.portal__nav--top { margin-top: 14px; }

/* Sidebar collapse/expand on desktop. Below 1200px app.css already turns the
   sidebar into an off-canvas drawer driven by the same [data-portal-burger]
   button (.portal__side.is-open); above it the burger instead collapses the
   grid's first column, toggled by js/{admin,vendor}/nav-collapse.js. */
@media (min-width: 1200px) {
  .portal__burger { display: block; }
  /* One track, not `0 1fr`: hiding .portal__side takes it out of the grid
     entirely, so auto-placement would drop .portal__main into the *first*
     track. With `0 1fr` that track is 0px wide and — because .portal__main
     carries min-width:0 (app.css) — it stays 0px, collapsing every child to
     min-content. Declaring a single 1fr track leaves exactly one column for
     the one remaining item. .portal__main is also pinned explicitly so the
     layout survives anything else being added back into the grid later. */
  .portal.is-nav-collapsed { grid-template-columns: 1fr; }
  .portal.is-nav-collapsed .portal__side { display: none; }
  .portal.is-nav-collapsed .portal__main { grid-column: 1; }
}

/* Phase 7 — breadcrumbs (new chrome, no prototype equivalent to port). */
.portal__breadcrumbs {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-soft);
  /* Rendered at the top-left of .portal__content (not in the page header), so it
     needs a real gap before the first panel/notice below it. */
  margin-bottom: 16px;
}
.portal__breadcrumbs a { color: var(--ink-soft); text-decoration: none; }
.portal__breadcrumbs a:hover { color: var(--navy); text-decoration: underline; }
.portal__breadcrumbs-current { color: var(--navy); font-weight: 600; }

/* Phase 7 — header user-menu dropdown (Bootstrap dropdown-toggle on the
   existing .portal__user button; dropdown-menu/dropdown-item/dropdown-divider
   are unstyled Bootstrap defaults, already loaded for the confirm modal). */
/* Signed-in identity sits hard against the right edge of the header, so its two
   lines read right-aligned rather than ragged against the dropdown caret. */
.portal__topright { margin-left: auto; }
.portal__topright .portal__username,
.portal__topright .portal__userrole { text-align: right; }

.portal__topright .dropdown-menu { min-width: 180px; }
.portal__topright .dropdown-item form { margin: 0; }
.portal__topright form .dropdown-item { width: 100%; }

/* Bootstrap confirm modal, replacing window.confirm() for every
   data-confirm="..." form (js/admin/loading.js). */
.confirm-modal .modal-body { color: var(--ink-soft); }

/* Rich text editor (js/admin/rich-text-editor.js) — enhances a
   textarea[data-rte] with a toolbar + contenteditable surface styled to match
   .input-st, since the prototype has no real editor to port (admin-pages.html's
   body field is a plain textarea with a "the live CMS uses a rich text editor"
   hint — this build's page bodies actually need one). */
.rte {
  border: 1px solid var(--field-border);
  border-radius: var(--radius-md);
  background: #fff;
  overflow: hidden;
}
.rte__toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 8px;
  border-bottom: 1px solid var(--field-border);
  background: var(--surface);
}
.rte__btn {
  min-width: 34px;
  height: 30px;
  padding: 0 8px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--navy-deep);
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
}
.rte__btn:hover { background: #fff; border-color: var(--field-border); }
.rte__btn:focus-visible { outline: 3px solid var(--yellow); outline-offset: 1px; }
.rte__surface {
  min-height: 220px;
  max-height: 480px;
  overflow-y: auto;
  padding: 14px;
  font-family: inherit;
  font-size: 14.5px;
  font-weight: 500;
  line-height: 1.6;
  color: var(--navy-deep);
}
.rte__surface:focus-visible { outline: 3px solid var(--yellow); outline-offset: -3px; }
.rte__surface p { margin: 0 0 12px; }
.rte__surface ul, .rte__surface ol { margin: 0 0 12px; padding-left: 24px; }
.rte__surface blockquote {
  margin: 0 0 12px;
  padding-left: 14px;
  border-left: 3px solid var(--field-border);
  color: var(--ink-soft);
}

/* Phase 8 — Dashboard KPI icon badges (§15.1). app.css's .kpi/.kpi__label ship
   with no icon slot at all, so this adds a header row above the existing
   label/value/footer stack rather than touching app.css. 6 cards (was 4) also
   needs a 3-column grid instead of app.css's fixed 4 so they sit in two even
   rows; the same breakpoints as app.css's own .kpis media queries are repeated
   here since a later stylesheet's unconditional rule would otherwise win over
   an earlier stylesheet's media-scoped one at every width. */
.kpis { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 1279.98px) { .kpis { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 767.98px) { .kpis { grid-template-columns: 1fr; } }

.kpi__head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.kpi__icon {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; width: 36px; height: 36px; border-radius: 10px; font-size: 16px;
}
.kpi__icon--blue { background: rgba(37, 99, 235, .12); color: #2563eb; }
.kpi__icon--amber { background: rgba(217, 119, 6, .12); color: #d97706; }
.kpi__icon--green { background: rgba(5, 150, 105, .12); color: #059669; }
.kpi__icon--purple { background: rgba(124, 58, 237, .12); color: #7c3aed; }
.kpi__icon--teal { background: rgba(13, 148, 136, .12); color: #0d9488; }
.kpi__icon--indigo { background: rgba(79, 70, 229, .12); color: #4f46e5; }

/* Phase 8 — Quick Links panel (§15.7, new chrome, no prototype equivalent —
   same precedent as Phase 7's breadcrumbs/dropdown). */
.quicklinks { display: flex; flex-direction: column; gap: 8px; }
.quicklink {
  display: flex; align-items: center; gap: 12px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-md);
  color: var(--navy); font-weight: 600; font-size: 14px; text-decoration: none;
  transition: border-color .15s ease, background .15s ease;
}
.quicklink:hover { border-color: var(--blue); background: rgba(37, 99, 235, .05); }
.quicklink__icon {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; width: 28px; height: 28px; border-radius: 8px; font-size: 14px;
  background: rgba(37, 99, 235, .12); color: #2563eb;
}
.quicklink__label { flex: 1; }
.quicklink__chevron { flex: none; color: var(--ink-soft); }

/* Phase 8 — Marketplace Activity graph (§15.2-15.5). Chart.js draws its own
   legend on the canvas (matches the reference image), so only the period
   filter and the canvas's own box need styling here. */
.chart-periods { display: flex; gap: 6px; }
.chart-period {
  padding: 6px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: #fff; color: var(--ink-soft); font-size: 12.5px; font-weight: 600;
  cursor: pointer; transition: border-color .15s ease, background .15s ease, color .15s ease;
}
.chart-period:hover { border-color: var(--blue); color: var(--navy); }
.chart-period.is-on { background: var(--navy); border-color: var(--navy); color: #fff; }
.chart-wrap { position: relative; height: 280px; }

/* Chart + Quick Links row — wide chart, fixed-width links column. app.css's
   .grid--sidebar is close but is fixed narrow-then-wide (290px 1fr); this needs
   the opposite order (wide chart first, narrow links second). */
.dashboard-activity-row { display: grid; grid-template-columns: 1fr 320px; gap: 20px; align-items: start; }
@media (max-width: 1199.98px) { .dashboard-activity-row { grid-template-columns: 1fr; } }

/* .pager only carries a margin-top, so inside a .panel--flush (padding:0) it sits
   flush against the panel's bottom edge — e.g. /Admin/AuditLogs, /Admin/ErrorLogs. */
.panel--flush > .pager { padding-bottom: 24px; }

/* Equipment row thumbnails show the whole machine rather than a centre crop.
   app.css's .thumb-sm sets object-fit:cover, which on a 44x34 box reduced a
   wide photo to an unrecognisable middle slice. `contain` letterboxes it
   against the grey the class already carries. Shared by the Admin "All
   equipment" list and the Vendor "My listings" list (the Vendor layout loads
   this file), so both are covered by the one override — app.css is the
   prototype's own file and is never edited. */
.thumb-sm { object-fit: contain; }
/* Ensure admin auth page artwork keeps the logo visible without scrolling.
   Mirrors the vendor auth fix so both auth shells behave the same. */
.authpage {
  display: flex;
  min-height: 100vh;
  align-items: stretch;
  gap: 0;
}

.authpage__art {
  flex: 0 0 52%;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: flex-start;
  padding: 40px;
  box-sizing: border-box;
  overflow: hidden;
  background-size: cover;
  background-position: center;
}

.authpage__art > img {
  display: block;
  max-width: 240px;
  width: auto;
  height: auto;
}

.authpage__art h2,
.authpage__art p {
  margin-top: 18px;
  color: var(--white);
}

.authpage__form {
  flex: 1 1 48%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  box-sizing: border-box;
  overflow: auto;
}

.authpage__inner {
  width: 100%;
  max-width: 520px;
}

@media (max-width: 991.98px) {
  .authpage {
    flex-direction: column;
  }
  .authpage__art,
  .authpage__form {
    flex: none;
    width: 100%;
    padding: 24px;
  }
  .authpage__art > img { max-width: 200px; }
}
