/*
 * Bridging shim for the China Newswire theme.
 *
 * The theme puts its page class on <body> (`welcome-page`, `account-page`,
 * `outlets-page`). In the App Router only the root layout renders <body>, and
 * a layout cannot read the current path, so each page instead renders that
 * class on a server-rendered wrapper <div>. These rules re-apply the handful
 * of theme declarations that were written against <body> so they still land.
 *
 * Everything else comes from public/theme/css/ unchanged.
 */

/* The page wrapper has to fill the viewport for the theme's dark background
   and its `min-height: calc(100vh - 129px)` main areas to line up. */
.page-shell {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

/* style.css:401 — `.welcome-page { background: var(--navy-color) }` was a
   <body> rule; the login page needs it behind the sticky header and footer. */
body:has(.welcome-page) {
  background: var(--navy-color);
}

/* outlets.css:1 — `.outlets-page { background: #fff }`, likewise. */
body:has(.outlets-page) {
  background: #fff;
}

/* ---------------------------------------------------------------------------
 * Header auth state.
 *
 * The mockup's header predates accounts — it has no logged-in state, so these
 * are additions rather than re-applied theme rules. They borrow the theme's
 * existing tokens and .contact-btn shape so the header still reads as one
 * design.
 * ------------------------------------------------------------------------ */

.nav-user {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 170px;
  color: var(--black-color);
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nav-user i {
  color: var(--gray-color);
  font-size: 11px;
}

/* .contact-btn was written for an <a>; logout is a <button>, which brings its
   own border, font and background that have to be cleared. */
.logout-btn {
  border: 0;
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
}

.logout-btn:disabled {
  opacity: .6;
  cursor: default;
}

/* Only shown when the logout request itself failed, so the user is not left
   believing a session ended when it did not. The header is white on every
   page (style.css:86, with no per-page override), so one colour serves. */
.logout-error {
  color: #dc3545;
  font-size: 11px;
  line-height: 1.3;
}

@media (max-width: 760px) {
  /* Matches responsive.css:72, where .desktop-nav becomes the slide-in menu
     and stacks its children full width. These two are new, so they need the
     same treatment. */
  .desktop-nav .nav-user {
    max-width: 100%;
    padding: 12px 4px;
    font-size: 14px;
  }

  .desktop-nav .logout-btn {
    width: 100%;
    margin-top: 12px;
    text-align: center;
  }
}

/* ---------------------------------------------------------------------------
 * Form validation.
 *
 * style.css:292 sets `.field-error { display: none }` — the mockup reserved
 * the slot but shipped no script to reveal it, so the message never showed.
 * The React forms render a message only when a field actually fails, so the
 * element can simply show whenever it has content. `:empty` keeps the
 * always-rendered placeholders (the ones with no error) collapsed, so nothing
 * shifts on the untouched form.
 * ------------------------------------------------------------------------ */

.field-error:not(:empty) {
  display: block;
}

/* Colour the control itself, not just the message below it — the theme has no
   invalid state of its own, and aria-invalid is already on the failing input
   for assistive tech. */
.input-wrap:has(input[aria-invalid="true"]) {
  border-color: #dc3545;
}

.input-wrap:has(input[aria-invalid="true"]):focus-within {
  box-shadow: 0 0 0 2px rgba(220, 53, 69, .1);
}

/* Submission-level failure (network down, or a server error that maps to no
   single field). Matches .field-error's palette at body copy size. */
.form-error {
  margin-bottom: 12px;
  border: 1px solid rgba(220, 53, 69, .25);
  border-radius: 2px;
  padding: 9px 11px;
  color: #dc3545;
  font-size: 12px;
  line-height: 1.4;
  background: rgba(220, 53, 69, .06);
}

/* The login card sits on dark navy and scopes its own .input-wrap rule
   (style.css:491) above the plain selector's specificity, so the invalid state
   is restated here — in a tint that stays legible on that background. Declared
   after the light-theme rules so it wins on source order as well. */
.login-card .input-wrap:has(input[aria-invalid="true"]) {
  border-color: #ff6b78;
}

.login-card .field-error {
  color: #ff8a94;
}

.login-card .form-error {
  border-color: rgba(255, 107, 120, .32);
  color: #ff8a94;
  background: rgba(255, 107, 120, .1);
}

/* ---------------------------------------------------------------------------
 * Home page — currently an empty shell (header + footer only), pending its
 * design. These two rules keep the footer at the bottom of a short page.
 * ------------------------------------------------------------------------ */

body:has(.home-page) {
  background: #fff;
}

.home-main {
  flex: 1;
}

/* ---------------------------------------------------------------------------
 * Outlets listing — states the static mockup had no markup for, because it
 * shipped four hardcoded cards: the API can also be unreachable, return
 * nothing, or still be in flight. Colours are the theme's own (#e1e7f3
 * borders, #707a87 muted text, the red from `.pagination`'s palette).
 * ------------------------------------------------------------------------ */

.outlet-error,
.outlet-empty {
  max-width: 1110px;
  margin: 12px auto;
  padding: 14px 18px;
  border: 1px solid #e1e7f3;
  border-radius: 8px;
  color: #707a87;
  font-size: 12px;
  text-align: center;
  background: #f8faff;
}

.outlet-error {
  border-color: rgba(214, 69, 82, .3);
  color: #b3313c;
  background: #fdf3f4;
}

.outlet-error i {
  margin-right: 8px;
}

/* Dim the grid while a new page or filter is loading, so a stale result is
   visibly stale rather than looking like the answer to the new query.
   Scoped away from the skeleton case below, which is already a loading
   affordance and must not be faded on top of its own shimmer. */
.outlet-cards[aria-busy="true"]:not(:has(.is-skeleton)) {
  opacity: .55;
  transition: opacity .15s ease;
  pointer-events: none;
}

/* ---------------------------------------------------------------------------
 * Multi-select filter menus.
 *
 * The theme's menu rows were `<a href="#">` (outlets.css:55), which suited a
 * pick-one-and-close control. Each row is now a <label> wrapping a real
 * checkbox, so several values can be applied at once and the row is reachable
 * by keyboard — which means restating the theme's row styling against the new
 * element rather than inheriting it.
 * ------------------------------------------------------------------------ */

.filter-option {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 8px 9px;
  border-radius: 3px;
  color: #dce6f5;
  font-size: 11px;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}

.filter-option:hover {
  color: #fff;
  background: rgba(255, 255, 255, .1);
}

.filter-option.is-active {
  color: #fff;
  background: rgba(255, 255, 255, .14);
}

/* The native control is hidden but still focusable and still the thing being
   toggled — `.filter-box` is only its visual stand-in. Removing it from the
   accessibility tree would cost the row its checked state. */
.filter-option input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Mirrors `.select-check span` on the outlet cards (outlets.css:178) so a
   checkbox reads the same wherever it appears. */
.filter-box {
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  border: 1px solid rgba(178, 195, 220, .5);
  border-radius: 3px;
  transition: .15s;
}

.filter-option input:checked + .filter-box {
  border-color: #1e6ae8;
  background: #1e6ae8;
}

.filter-option input:checked + .filter-box::after {
  content: '✓';
  display: block;
  color: #fff;
  text-align: center;
  font-size: 10px;
  line-height: 12px;
}

/* Keyboard focus has to be visible on the stand-in, since the real input is
   hidden and would otherwise focus invisibly. */
.filter-option input:focus-visible + .filter-box {
  outline: 2px solid #7fb0ff;
  outline-offset: 1px;
}

.filter-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Facet counts. */
.filter-option small {
  margin-left: 14px;
  opacity: .6;
}

/* Resets one filter without closing the menu. Styled apart from the options so
   it does not read as another value to pick. */
.filter-clear {
  display: block;
  width: 100%;
  margin-bottom: 4px;
  padding: 7px 9px;
  border: 0;
  border-bottom: 1px solid rgba(178, 195, 220, .2);
  border-radius: 3px;
  color: #9fb4d4;
  background: transparent;
  font: inherit;
  font-size: 10px;
  text-align: left;
  cursor: pointer;
}

.filter-clear:hover {
  color: #fff;
  background: rgba(255, 255, 255, .08);
}

/* An applied filter stays visible while the menu is shut — otherwise the only
   clue is the pill's label, which reads as a placeholder at a glance. */
.filter-dropdown.has-value .filter-toggle {
  border-color: rgba(127, 176, 255, .65);
  color: #fff;
  background: rgba(30, 106, 232, .18);
}

.filter-toggle:disabled {
  opacity: .5;
  cursor: not-allowed;
}

/* Supplier logos, where one is on file. The theme's tile sizes itself, so the
   image is constrained to it rather than the other way round. */
.outlet-logo img {
  width: 100%;
  height: 100%;
  border-radius: inherit;
  object-fit: contain;
}

/* `.vetted` carries no margin of its own (outlets.css:380) — in the mockup the
   pagination block above it always supplied the gap via its own
   `margin: 20px 0 10px`. The pager is now hidden when the results fit on one
   page, which left the line flush against the last card.

   Scoped to "no pager immediately before it" rather than applied outright:
   `.outlet-results` is a block container, so an unconditional top margin would
   collapse with the pager's 10px bottom margin to 24px and shift the paged
   layout. `:not(.pagination + .vetted)` leaves that case exactly as the theme
   drew it. */
.vetted:not(.pagination + .vetted) {
  margin-top: 12px;
}

/* The sample link replaces the mockup's four grey placeholder bars. */
.article-preview a {
  display: inline-block;
  margin-top: 10px;
  color: var(--gold-color, #c9a227);
  font-size: 11px;
  text-decoration: underline;
}

/* The "…" between distant page numbers. The mockup drew it as another boxed
   cell, so it borrows `.pagination button`'s metrics — but it is a <span>,
   since it has no page to navigate to. */
.pagination .page-gap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 35px;
  height: 34px;
  padding: 0 10px;
  border: 1px solid #e7eaf0;
  border-radius: 4px;
  color: #182131;
  background: #fff;
  font-size: 10px;
  user-select: none;
}

/* Every page button is clickable except the current one, which is where you
   already are. */
.pagination button:not(:disabled):hover {
  border-color: #c9d3e4;
}

.pagination button.active {
  cursor: default;
}

/* ---------------------------------------------------------------------------
 * Loading state.
 *
 * Two distinct signals, because they answer different questions:
 *
 *  - Skeletons, when there is nothing on screen yet (first load, or the first
 *    result after an error). They show the *shape* of what is coming, so the
 *    grid does not jump when it arrives.
 *  - A dim plus a spinner, when rows are already rendered and a new page or
 *    filter is in flight. The stale rows stay readable and in place; replacing
 *    them with placeholders would read as the list breaking and rebuilding.
 * ------------------------------------------------------------------------ */

@keyframes outlet-shimmer {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}

/* The one primitive every placeholder is built from. The gradient is wider
   than the element so the highlight can travel across it. */
.skeleton-bar,
.skeleton-logo,
.skeleton-check {
  display: block;
  border-radius: 4px;
  background: linear-gradient(90deg, #eef1f6 25%, #f7f9fc 50%, #eef1f6 75%);
  background-size: 200% 100%;
  animation: outlet-shimmer 1.4s ease-in-out infinite;
}

.skeleton-bar {
  height: 9px;
}

/* Varied widths stop the placeholder reading as a table of identical blocks —
   real rows have ragged text, and a skeleton that does too is less distracting.
   Named rather than inline so the markup stays declarative. */
.skeleton-bar.w-40 { width: 40%; }
.skeleton-bar.w-50 { width: 50%; }
.skeleton-bar.w-60 { width: 60%; }
.skeleton-bar.w-65 { width: 65%; }
.skeleton-bar.w-75 { width: 75%; }
.skeleton-bar.w-85 { width: 85%; }
.skeleton-bar.w-90 { width: 90%; }

/* Matches `.select-check span` (14px) and `.outlet-logo` (72px) so the
   skeleton occupies its grid columns at full size. */
.skeleton-check {
  width: 14px;
  height: 14px;
  border-radius: 3px;
}

.skeleton-logo {
  width: 72px;
  height: 72px;
  border-radius: 9px;
}

.skeleton-lines {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
}

.skeleton-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
}

.skeleton-actions .skeleton-bar {
  height: 26px;
  border-radius: 4px;
}

/* The skeleton borrows `.outlet-card`'s grid, so it needs none of its own
   layout — only the hover/transition suppressed, since it is not interactive. */
.outlet-card.is-skeleton {
  transition: none;
  pointer-events: none;
}

/* Detail panel placeholder, reusing the panel's own title and stats grids. */
.detail-skeleton .detail-stats div {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.detail-skeleton .skeleton-lines {
  padding-top: 14px;
}

/* A refetch over existing rows is signalled by the dim alone (see the
   `[aria-busy]` opacity rule above). No spinner: on a filter or page change the
   response is fast enough that a spinner mostly flashes, and it competed with
   the dim for the same "working" message. */

/* Honour a reduced-motion preference: the shimmer is decorative, so it resolves
   to a static tint rather than animating. The bars stay visible, so the loading
   state is still communicated. */
@media (prefers-reduced-motion: reduce) {
  .skeleton-bar,
  .skeleton-logo,
  .skeleton-check {
    animation: none;
    background: #eef1f6;
  }
}

/* ---------------------------------------------------------------------------
 * Range filters (price, DA, DR, traffic).
 *
 * The mockup's bar held four pills on one line; the requirements call for nine,
 * which no longer fit at common widths. `.filter-list` is a non-wrapping flex
 * row (outlets.css:47), so it is allowed to wrap here rather than overflow.
 * ------------------------------------------------------------------------ */

.filter-list {
  flex-wrap: wrap;
  row-gap: 10px;
}

/* The bar is `justify-content: space-between` with the search box as the second
   child; once the pills wrap, letting the list take the row it needs keeps the
   search box from being squeezed to nothing. */
.filter-bar .filter-list {
  flex: 1 1 auto;
  min-width: 0;
}

/* `.filter-bar` is `align-items: center` (outlets.css:40), which was right when
   the pills were a single row of the same height as the search box. Now that
   they wrap to two rows, centring floats the search box down beside the gap
   between them. Aligning to the start puts it back on the first row, level with
   the leading pills.

   Bounded to >850px because outlets-responsive.css:26 stacks the bar into a
   column with `align-items: stretch` below that, and this file loads after it —
   unscoped, these two rules would override the stacked layout and cost the
   search box its full width on mobile. */
@media (min-width: 851px) {
  .filter-bar {
    align-items: flex-start;
  }

  /* Hold the box at its 332px (outlets.css:88) instead of letting the flex
     default shrink it once the pill list claims the remaining width. */
  .filter-bar .outlet-search {
    flex: 0 0 auto;
  }
}

.range-menu {
  min-width: 232px;
  padding: 12px;
}

.range-row {
  display: flex;
  gap: 10px;
}

.range-row label {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  color: #9fb4d4;
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: .04em;
}

.range-row input {
  width: 100%;
  min-width: 0;
  height: 32px;
  padding: 0 8px;
  border: 1px solid rgba(178, 195, 220, .35);
  border-radius: 4px;
  color: #eef4ff;
  background: rgba(255, 255, 255, .04);
  font: inherit;
  font-size: 11px;
}

.range-row input:focus {
  outline: 0;
  border-color: rgba(127, 176, 255, .8);
}

.range-row input::placeholder {
  color: #6c819f;
}

/* The spinners are too small to hit at this size and steal width from the
   value; the field is still a number input for keyboard and mobile keypads. */
.range-row input::-webkit-outer-spin-button,
.range-row input::-webkit-inner-spin-button {
  margin: 0;
  -webkit-appearance: none;
  appearance: none;
}

.range-row input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

.range-actions {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-top: 10px;
}

.range-actions button {
  height: 28px;
  padding: 0 12px;
  border: 0;
  border-radius: 4px;
  font: inherit;
  font-size: 10px;
  cursor: pointer;
}

.range-clear {
  color: #9fb4d4;
  background: transparent;
}

.range-clear:hover {
  color: #fff;
  background: rgba(255, 255, 255, .08);
}

.range-apply {
  color: #fff;
  background: #1e6ae8;
}

.range-apply:hover {
  background: #1a5cc8;
}

/* ---------------------------------------------------------------------------
 * Email verification — the "check your inbox" panel, the resend affordance,
 * and the toast stack.
 * ------------------------------------------------------------------------ */

/* Replaces the signup form in place once the account exists, so it inherits
   the card's width rather than needing its own layout. */
.verify-sent {
  padding: 8px 0 4px;
  text-align: center;
}

.verify-sent-icon {
  margin-bottom: 12px;
  color: #2ecc71;
  font-size: 34px;
}

.verify-sent h2 {
  margin: 0 0 10px;
  color: var(--navy-color, #0e2a4d);
  font-size: 18px;
  font-weight: 600;
}

.verify-sent p {
  margin: 0 0 10px;
  color: #5b6b82;
  font-size: 12px;
  line-height: 1.6;
}

.verify-sent strong {
  color: var(--navy-color, #0e2a4d);
  word-break: break-all;
}

.verify-sent-note {
  margin-bottom: 18px !important;
  font-size: 11px !important;
  opacity: .85;
}

/* The signup card renders on white; the login card is on navy and restates
   the two colours that would otherwise disappear into it. */
.login-card .verify-sent h2,
.login-card .verify-sent strong {
  color: var(--white-color, #fff);
}

.login-card .verify-sent p {
  color: #b9c9e0;
}

/* Sits under the email field on the login form, shown only when the sign-in
   failed specifically because the address is unverified. A button, not a link:
   it performs an action rather than navigating. */
.resend-link {
  margin-top: 2px;
  border: 0;
  padding: 0;
  color: #7fb0ff;
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  text-decoration: underline;
  background: transparent;
  cursor: pointer;
}

.resend-link:hover:not(:disabled) {
  color: #a8c9ff;
}

.resend-link:disabled {
  opacity: .6;
  cursor: default;
}

/* --- Toasts ------------------------------------------------------------- */

/* Fixed to the viewport, above everything including the sticky header.
   `pointer-events: none` on the stack lets clicks through the empty gaps;
   each toast re-enables them for itself so its close button still works. */
.toast-stack {
  position: fixed;
  top: 20px;
  right: 20px;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: min(360px, calc(100vw - 40px));
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  border-left: 3px solid #2ecc71;
  border-radius: 4px;
  padding: 12px 14px;
  color: #0e2a4d;
  font-size: 12px;
  line-height: 1.5;
  background: #fff;
  box-shadow: 0 6px 22px rgba(14, 42, 77, .18);
  pointer-events: auto;
  animation: toast-in .22s ease-out;
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateX(16px);
  }
}

/* Users who have asked for less motion get the toast without the slide. */
@media (prefers-reduced-motion: reduce) {
  .toast {
    animation: none;
  }
}

.toast-icon {
  margin-top: 1px;
  font-size: 14px;
}

.toast-success {
  border-left-color: #2ecc71;
}

.toast-success .toast-icon {
  color: #2ecc71;
}

.toast-error {
  border-left-color: #dc3545;
}

.toast-error .toast-icon {
  color: #dc3545;
}

.toast-info {
  border-left-color: #1e6ae8;
}

.toast-info .toast-icon {
  color: #1e6ae8;
}

.toast-message {
  flex: 1;
}

.toast-close {
  border: 0;
  padding: 0 2px;
  color: #8b9bb4;
  font-size: 17px;
  line-height: 1;
  background: transparent;
  cursor: pointer;
}

.toast-close:hover {
  color: #0e2a4d;
}

/* The verify page reuses .login-card, which the theme sizes for a full form;
   this content is much shorter. */
.verify-card {
  text-align: center;
}

.verify-eyebrow-error {
  color: #ff8a94 !important;
}

/* ---------------------------------------------------------------------------
 * Header logo — replaces the theme's circled "C" .brand-mark.
 * ------------------------------------------------------------------------ */

/* Sits where the circled mark did, so .brand's existing flex gap and serif
   type still apply to the "China Newswire" text beside it. Slightly taller
   than the 30px mark because the logo is wide and short. `width: auto`
   preserves the 402x226 aspect ratio against the explicit width/height
   next/image requires for layout stability. */
.brand-logo {
  display: block;
  width: auto;
  height: 40px;
}

@media (max-width: 430px) {
  .brand-logo {
    height: 32px;
  }
}
