/* ==========================================================================
   Defender Hub — V2 design system
   Served at /static/ocs/css/v2.css (STATICFILES_DIRS maps "ocs" -> static_assets/ocs).

   Extracted from the original standalone index_v2.html so every V2 page shares
   one stylesheet. EVERY colour is a token: dark mode is nothing more than a
   second set of values for the same names, so a hardcoded hex anywhere in here
   is a bug that will show up as a light patch on a dark page.

   Theme is switched by [data-theme] on <html>, set before first paint by the
   inline script in layouts/v2_base.html.

   The accent is blue in both themes but not the same blue: light is navy, dark
   is the Transcal azure taken from the /MANUFACTURE slash in the lockup (navy
   at azure's brightness would glare on a near-black page, azure at navy's would
   vanish on white). That is why these tokens are named --accent and not by a
   hue. Success keeps its own --ok tokens so a confirmation stays green in both
   themes — a blue "added to your order" reads as information, not as success.

   ONE GREEN SURFACE REMAINS in light mode, deliberately: the hero banner is a
   green-graded photograph and --hero-fill is sampled from it. Also note
   frontend/main.html hardcodes the sub-page strip as #99b9a1, which is NOT a
   token and will not follow anything here.
   ========================================================================== */

:root {
  /* brand — navy. Green is no longer the accent in EITHER theme; it survives
     only as --ok below. Worth knowing before "restoring" it somewhere: the
     Transcal lockup is orange, blue and red slashes with no green in it, so the
     green was never brand-derived. */
  --accent:       #1e3a5f;
  --accent-deep:  #16304f;
  --accent-soft:  #e8edf4;
  --accent-on:    #fff;          /* text on a solid accent fill */

  /* Accent for anything sitting ON photography. Pinned to Transcal azure in
     BOTH themes because the light accent is navy, and navy on a scrimmed photo
     is a dark shape on a dark ground. */
  --on-image:     #1e9ad6;

  /* Label ink for the collection tiles, which carry no scrim. Light mode's
     plates are pale sage, so the ink is the page's own dark; the halo is a
     white bloom that lifts it off linework and shadow. */
  --tile-ink:      #172130;
  --tile-ink-soft: rgba(31, 39, 51, .74);
  --tile-halo:     0 1px 10px rgba(255, 255, 255, .8);
  /* A soft bloom behind the label, not a panel-wide wash: it fades out well
     before the card edge so the photograph keeps its corners. Sized by
     measurement — the darkest 12% of the pixels behind the label on the leather
     plate give 1.9:1 against this ink bare, and 6:1 under this. */
  --tile-veil:     radial-gradient(ellipse 82% 48% at 50% 50%,
                     rgba(255, 255, 255, .46) 0%, rgba(255, 255, 255, .30) 52%, rgba(255, 255, 255, 0) 100%);

  /* success. Green in BOTH themes now, and no longer equal to the light accent:
     a navy confirmation reads as information, the same reason dark keeps it. */
  --ok:           #5f7a45;
  --ok-deep:      #42552f;
  --ok-soft:      #eef2e9;

  /* surfaces — cooled off the old green-tinted paper to sit under navy */
  --bg:           #f2f4f7;
  --surface:      #fff;
  --surface-sunk: #f2f4f7;       /* image wells, code pills */
  --line:         #e2e6ec;

  /* text */
  --text:         #1f2733;
  --text-strong:  #172130;
  --text-soft:    #48525f;
  --text-dim:     #6a7482;
  --muted:        #868f9c;
  --nav-link:     #33404f;
  --brand-word:   #172130;

  /* top utility bar */
  --ink:          #16233a;
  --ink-text:     #c6d1e0;
  --ink-text-dim: #98a6ba;

  /* hero. --hero-fill is sampled from the left edge of hero-defender.jpg so the
     flat fill and the image's own padding are indistinguishable. It stays SAGE
     on purpose: the light banner is still a green-graded photograph, so this
     must keep matching the artwork, not the navy palette. Change it only when a
     navy-graded hero-defender.jpg replaces the current one, and resample it from
     that file rather than picking a colour. */
  --hero-fill:    #ced8cd;
  --crumb-sep:    #a9b4c2;

  /* effects */
  --shadow-card:  0 .55rem 1.3rem rgba(23,33,48,.10);
  --shadow-row:   0 .35rem .9rem rgba(23,33,48,.07);
  --shadow-pop:   0 .6rem 1.6rem rgba(23,33,48,.14);
  --shadow-float: 0 .8rem 2.2rem rgba(23,33,48,.10);
  --code-chip-bg: rgba(255,255,255,.94);
  --sk-base:      #e8ebf0;
  --sk-shine:     #f4f6f9;

  --font-display: "Saira Condensed", "Saira", sans-serif;
  --font-body:    "Saira", system-ui, sans-serif;
}

[data-theme="dark"] {
  /* Transcal azure, lifted off the brand hex (#1e9ad6) far enough to hold up
     against a near-black page. --accent-deep is the LIGHT tint used in the text
     role; --accent-on is dark because the accent fill itself is bright. */
  --accent:       #43abe2;
  --accent-deep:  #93d2f1;
  --accent-soft:  #15242f;
  --accent-on:    #08131a;
  --on-image:     #43abe2;       /* same lift as --accent: photos here are dark too */
  --tile-ink:      #fff;         /* dark plates, so the tile ink flips with them */
  --tile-ink-soft: rgba(255, 255, 255, .76);
  --tile-halo:     0 1px 12px rgba(4, 8, 12, .9);
  /* Mirrored, and needed for one plate in particular: the order sheet is bright
     white exactly where the label sits, giving white-on-white at 2.2:1 bare.
     5.4:1 under this. */
  --tile-veil:     radial-gradient(ellipse 82% 48% at 50% 50%,
                     rgba(8, 12, 16, .44) 0%, rgba(8, 12, 16, .28) 52%, rgba(8, 12, 16, 0) 100%);

  /* Success stays green on dark — these are the accent values this theme used
     before it went blue. */
  --ok:           #86a862;
  --ok-deep:      #b3cd93;
  --ok-soft:      #1f2a19;

  /* The neutrals are graded blue-black rather than the green-black they were
     under the green accent, so nothing reads as an olive cast next to azure.
     This also settles the page against --hero-fill, which was already blue:
     it is sampled from the dark banner artwork and did not change. */
  --bg:           #0c1014;
  --surface:      #141a20;
  --surface-sunk: #1a222a;
  --line:         #28323c;

  --text:         #dfe6ed;
  --text-strong:  #eef3f8;
  --text-soft:    #b2bdc8;
  --text-dim:     #929daa;
  --muted:        #85909c;
  --nav-link:     #c7d2dd;
  --brand-word:   #e9eff5;

  --ink:          #070a0d;
  --ink-text:     #b4bfca;
  --ink-text-dim: #8a95a1;

  /* A properly dark-graded banner, not the light one dimmed by a filter: a
     filter crushes the sky and the vehicle together, where the real grade keeps
     the headlights and chrome alive. --hero-fill is sampled from THIS image's
     left edge so the padded region and the CSS fill still match exactly. */
  --hero-fill:    #0d1518;
  --crumb-sep:    #5a6672;

  --shadow-card:  0 .55rem 1.3rem rgba(0,0,0,.45);
  --shadow-row:   0 .35rem .9rem rgba(0,0,0,.38);
  --shadow-pop:   0 .6rem 1.6rem rgba(0,0,0,.55);
  --shadow-float: 0 .8rem 2.2rem rgba(0,0,0,.5);
  --code-chip-bg: rgba(20,26,32,.92);
  --sk-base:      #1b222a;
  --sk-shine:     #262f38;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
}
a { color: inherit; text-decoration: none; }

/* Sections are full-bleed (backgrounds reach the viewport edges); only the inner
   content is capped + centred via .container-x. */
.v2-wrap { width: 100%; display: flex; flex-direction: column; min-height: 100vh; }
.v2-main { flex: 1; }
/* width:100% is load-bearing, not belt-and-braces. .v2-wrap is a column flex
   container (sticky footer), and `margin-inline: auto` on a FLEX ITEM makes it
   shrink to its content and centre, rather than fill. Without this every
   direct-child container renders narrower than the page. */
.container-x {
  width: 100%;
  max-width: 1760px; margin-left: auto; margin-right: auto;
  padding-left: clamp(1rem, 4vw, 3.5rem); padding-right: clamp(1rem, 4vw, 3.5rem);
}

/* --- utility top bar --------------------------------------------------- */
.v2-topbar {
  background: var(--ink); color: var(--ink-text); font-size: .72rem;
  letter-spacing: .14em; text-transform: uppercase; font-weight: 600;
}
.v2-topbar .container-x { display: flex; justify-content: space-between; align-items: center; height: 36px; }
.v2-topbar .dot { color: var(--accent); margin: 0 .5rem; }
.v2-topbar .right { color: var(--ink-text-dim); }

/* --- navbar ------------------------------------------------------------ */
.v2-nav { background: var(--surface); border-bottom: 1px solid var(--line); }
.v2-nav .container-x { display: flex; align-items: center; justify-content: space-between; height: 74px; gap: 1rem; }
/* Brand lockup, left-aligned. The DESIGN/ENGINEER/MANUFACTURE line is cropped
   out of the artwork (the topbar already says it), leaving TRANSCAL over
   DEFENDER HUB. The gap between the two lines is opened up to 139px in the
   artwork (from the original 69px) rather than done with CSS, since it's one
   image. Aspect is 4.543:1, so the width below is derived from the height
   rather than guessed — changing one without the other would squash it. */
.v2-brand {
  display: block; flex: none;
  height: 42px; width: 191px;
  background-image: var(--logo);
  background-position: left center;
  background-size: contain;
  background-repeat: no-repeat;
}
[data-theme="dark"] .v2-brand { background-image: var(--logo-dark); }
@media (max-width: 900px) { .v2-brand { height: 33px; width: 150px; } }
.v2-nav-links { display: flex; align-items: center; gap: .4rem; }
.v2-nav-links a {
  display: inline-flex; align-items: center; gap: .5rem; padding: .55rem .9rem;
  border-radius: 10px; font-weight: 600; font-size: .92rem; color: var(--nav-link);
}
.v2-nav-links a:hover { background: var(--accent-soft); }
.v2-nav-links a.active { background: var(--accent-soft); color: var(--accent-deep); }
/* Collections dropdown. The trigger is a <button>, not the section link:
   clicking a link that also opens a menu is ambiguous on touch, so the menu
   carries its own "All collections" entry to reach the landing page. */
.v2-nav-item { position: relative; }
.v2-nav-item > button {
  display: inline-flex; align-items: center; gap: .5rem; padding: .55rem .9rem;
  border-radius: 10px; font-family: inherit; font-weight: 600; font-size: .92rem;
  color: var(--nav-link); background: none; border: 0; cursor: pointer;
}
.v2-nav-item > button:hover { background: var(--accent-soft); }
.v2-nav-item.is-current > button { background: var(--accent-soft); color: var(--accent-deep); }
.v2-nav-item .caret { font-size: .62rem; transition: transform .15s; }
.v2-nav-item.open .caret { transform: rotate(180deg); }
.v2-dropdown {
  position: absolute; left: 0; top: calc(100% + 8px); z-index: 60; display: none;
  min-width: 220px; padding: .35rem; border-radius: 12px;
  background: var(--surface); border: 1px solid var(--line); box-shadow: var(--shadow-pop);
}
.v2-nav-item.open .v2-dropdown { display: block; }
.v2-dropdown a {
  display: flex; align-items: center; gap: .55rem; padding: .55rem .7rem;
  border-radius: 8px; font-weight: 600; font-size: .9rem; color: var(--nav-link);
}
.v2-dropdown a:hover { background: var(--accent-soft); color: var(--accent-deep); }
.v2-dropdown a i { color: var(--accent); width: 1rem; text-align: center; }
.v2-dropdown hr { border: 0; border-top: 1px solid var(--line); margin: .35rem .4rem; }

.v2-nav-right { display: flex; align-items: center; gap: 1.25rem; }
.v2-cart { position: relative; color: var(--nav-link); font-size: 1.15rem; }
.v2-cart .badge {
  position: absolute; top: -8px; right: -10px; background: var(--accent); color: var(--accent-on);
  border-radius: 999px; font-size: .62rem; font-weight: 700; min-width: 18px; height: 18px;
  display: flex; align-items: center; justify-content: center; padding: 0 4px;
}

/* theme toggle */
.v2-theme {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 10px; cursor: pointer;
  background: none; border: 1px solid var(--line); color: var(--nav-link); font-size: .95rem;
}
.v2-theme:hover { background: var(--accent-soft); color: var(--accent-deep); border-color: var(--accent); }
/* Show the icon for the theme you'd switch TO, not the one you're in. */
.v2-theme .icon-dark { display: inline; }
.v2-theme .icon-light { display: none; }
[data-theme="dark"] .v2-theme .icon-dark { display: none; }
[data-theme="dark"] .v2-theme .icon-light { display: inline; }

.v2-user { position: relative; }
.v2-user-btn {
  display: inline-flex; align-items: center; gap: .6rem; cursor: pointer;
  font-weight: 600; color: var(--text); background: none; border: 0;
}
.v2-user-btn img { width: 34px; height: 34px; border-radius: 50%; object-fit: cover; }
.v2-user-menu {
  position: absolute; right: 0; top: calc(100% + 8px); background: var(--surface);
  border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--shadow-pop);
  padding: .35rem; min-width: 160px; display: none; z-index: 60;
}
.v2-user.open .v2-user-menu { display: block; }
.v2-user-menu a { display: flex; align-items: center; gap: .55rem; padding: .55rem .7rem; border-radius: 8px; font-weight: 600; font-size: .9rem; }
.v2-user-menu a:hover { background: var(--bg); }

/* --- hero --------------------------------------------------------------
   One full-bleed image (static_assets/ui-icons/hero-defender.jpg, 2400x666).
   The source photo is 3:1, far too tall for this slim band — at full width it
   would either stretch the vehicle or crop it to nothing. The asset is
   pre-padded on the left with its own edge colour, taking it to 3.6:1, so
   `cover` fills the width while the vehicle keeps its proportions on the right.
   No clip-path or fade overlay: the diagonal light streaks are baked into the
   photograph. --hero-fill sits on the SAME element as the image so the padded
   region and the flat fill can never separate.

   Light and dark are two SEPARATE graded images. The template passes both as
   inline custom properties (it needs {% static %} to build the URLs), and CSS
   picks between them — an inline `background-image` would beat any stylesheet
   rule and make the dark variant unreachable. */
.v2-hero { position: relative; overflow: hidden; background: var(--hero-fill); }
.v2-hero__art {
  position: absolute; inset: 0;
  background-color: var(--hero-fill);
  background-image: var(--hero-img);
  background-position: right 65%; background-size: cover; background-repeat: no-repeat;
}
[data-theme="dark"] .v2-hero__art { background-image: var(--hero-img-dark, var(--hero-img)); }
.v2-hero .container-x { position: relative; z-index: 2; padding-top: 2.4rem; padding-bottom: 5.2rem; }
.v2-crumb { font-size: .85rem; color: var(--hero-dim, var(--text-dim)); display: flex; align-items: center; gap: .5rem; margin-bottom: 1rem; }
.v2-crumb a { color: inherit; }
.v2-crumb a:hover { color: var(--accent); }
.v2-crumb .sep { color: var(--crumb-sep); }
.v2-hero h1 {
  font-family: var(--font-display); font-weight: 700; font-size: clamp(2.6rem, 5vw, 3.6rem);
  margin: 0; letter-spacing: -.01em; color: var(--hero-title, var(--text-strong));
}
.v2-hero .rule { width: 74px; height: 5px; background: var(--accent); border-radius: 3px; margin: 1rem 0 1.25rem; }
.v2-hero p { max-width: 34rem; color: var(--hero-body, var(--text-soft)); font-size: 1.02rem; line-height: 1.5; margin: 0; }

/* The banner stays a light photograph in dark mode (just dimmed), so hero text
   is pinned light rather than following the page text tokens. */
[data-theme="dark"] .v2-hero { --hero-title: #f4f7f1; --hero-body: #d3dbcb; --hero-dim: #bcc6b2; --crumb-sep: #8d9885; }

/* Landing-page hero: the brand lockup centred in the banner instead of a
   breadcrumb and title. Deeper padding than the section heroes because there's
   no overlapping search card underneath to fill the space. */
.v2-hero--brand .container-x { padding-top: 3.4rem; padding-bottom: 3.4rem; text-align: center; }
/* The hero uses its own artwork: the CENTRE-aligned full lockup, strapline
   included. The nav keeps the left-aligned cropped mark, so the two are
   separate assets rather than one scaled two ways. */
.v2-herologo {
  height: clamp(76px, 11vw, 160px);
  aspect-ratio: 3.384;                 /* matches the artwork; keeps it from squashing */
  margin: 0 auto;
  /* White artwork in BOTH themes: it sits on the banner photograph, not on the
     page surface, so it follows the image rather than the theme. */
  background-image: var(--logo-hero-dark);
  background-position: center;
  background-size: contain;
  background-repeat: no-repeat;
}

/* --- floating card that overlaps the hero ------------------------------- */
.v2-searchwrap { margin-top: -2.6rem; position: relative; z-index: 5; }
.v2-search {
  background: var(--surface); border: 1px solid var(--line); border-radius: 16px;
  box-shadow: var(--shadow-float); padding: 1rem 1.15rem;
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
}
.v2-search__input { position: relative; flex: 1 1 300px; min-width: 0; }
.v2-search__input i { position: absolute; left: 1rem; top: 50%; transform: translateY(-50%); color: var(--muted); }
.v2-search__input input {
  width: 100%; height: 54px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--surface); padding: 0 1rem 0 2.8rem; font-size: 1rem; color: var(--text);
}
.v2-search__input input:focus { outline: 0; border-color: var(--accent); }
.v2-popular { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.v2-popular .lbl { color: var(--text-dim); font-size: .9rem; font-weight: 600; }
.v2-chip {
  border: 1px solid var(--line); background: var(--surface); border-radius: 999px;
  padding: .45rem .9rem; font-size: .85rem; font-weight: 600; color: var(--text-soft);
  cursor: pointer; white-space: nowrap;
}
.v2-chip:hover { border-color: var(--accent); color: var(--accent-deep); background: var(--accent-soft); }
.v2-clear {
  display: inline-flex; align-items: center; gap: .5rem; border: 1px solid var(--line);
  background: var(--surface); border-radius: 12px; height: 44px; padding: 0 1rem;
  font-weight: 600; color: var(--text-soft); cursor: pointer; white-space: nowrap;
}
.v2-clear:hover { border-color: var(--accent); }

/* --- two-column body ---------------------------------------------------- */
.v2-body { display: grid; grid-template-columns: 288px 1fr; gap: 1.6rem; padding-top: 2.2rem; padding-bottom: 4rem; align-items: start; }
@media (max-width: 900px) { .v2-body { grid-template-columns: 1fr; } }
.v2-single { padding-top: 2.2rem; padding-bottom: 4rem; }

/* --- filter rail -------------------------------------------------------- */
.v2-filters { background: var(--surface); border: 1px solid var(--line); border-radius: 16px; padding: 1.35rem; }
.v2-filters__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: .4rem; }
.v2-filters__title {
  display: flex; align-items: center; gap: .55rem; font-family: var(--font-display);
  font-weight: 700; letter-spacing: .06em; font-size: 1.05rem; color: var(--text-strong);
}
.v2-reset { color: var(--accent); font-weight: 600; font-size: .85rem; cursor: pointer; background: none; border: 0; }
.v2-reset:hover { text-decoration: underline; }
.v2-fgroup { margin-top: 1.15rem; }
.v2-fgroup__lbl { display: flex; align-items: center; gap: .5rem; font-weight: 700; font-size: .9rem; margin-bottom: .5rem; color: var(--text); }
.v2-fgroup__lbl i { color: var(--accent); font-size: .9rem; }
.v2-select {
  width: 100%; height: 46px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface); padding: 0 .8rem; color: var(--text); font-weight: 500;
}
.v2-apply {
  width: 100%; margin-top: 1.5rem; height: 48px; border: 1px solid var(--accent);
  background: var(--surface); color: var(--accent-deep); border-radius: 12px; font-weight: 700; cursor: pointer;
}
.v2-apply:hover { background: var(--accent); color: var(--accent-on); }

/* --- results header ----------------------------------------------------- */
.v2-rhead { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.3rem; }
.v2-rhead__left { display: flex; align-items: center; gap: .9rem; }
.v2-rhead__icon {
  width: 46px; height: 46px; border-radius: 12px; background: var(--accent-soft); color: var(--accent);
  display: flex; align-items: center; justify-content: center; font-size: 1.2rem; flex: none;
}
.v2-rhead__count { font-family: var(--font-display); font-weight: 700; font-size: 1.4rem; color: var(--text); line-height: 1.1; }
.v2-rhead__sub { color: var(--muted); font-size: .88rem; }
.v2-rhead__right { display: flex; align-items: center; gap: 1rem; }
.v2-sortby { display: flex; align-items: center; gap: .6rem; color: var(--text-soft); font-weight: 600; font-size: .9rem; }
.v2-sortby select {
  height: 46px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface);
  padding: 0 2rem 0 .9rem; font-weight: 600; color: var(--text); cursor: pointer;
}
.v2-view { display: inline-flex; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.v2-view button { width: 46px; height: 46px; background: var(--surface); border: 0; color: var(--text-soft); cursor: pointer; font-size: 1rem; }
.v2-view button.active { background: var(--accent); color: var(--accent-on); }

/* --- cards -------------------------------------------------------------- */
.v2-grid { display: grid; gap: 1.1rem; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
.v2-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px; overflow: hidden;
  display: flex; flex-direction: column; color: inherit;
  transition: border-color .14s, box-shadow .14s, transform .14s;
}
.v2-card:hover { border-color: var(--accent); box-shadow: var(--shadow-card); transform: translateY(-3px); }
.v2-card__top { position: relative; }
.v2-card__code {
  position: absolute; top: .6rem; left: .6rem; z-index: 2; background: var(--code-chip-bg);
  border: 1px solid var(--line); border-radius: .4rem; padding: .2rem .5rem;
  font-size: .7rem; font-weight: 700; color: var(--text-soft);
}
.v2-card__img { height: 170px; background: var(--surface-sunk); overflow: hidden; }
.v2-card__img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.v2-card__body { padding: .85rem .9rem 1rem; display: flex; flex-direction: column; gap: .65rem; flex: 1; }
.v2-card__name { font-weight: 700; line-height: 1.3; font-size: .92rem; color: var(--text); }
.v2-badge {
  display: inline-flex; align-items: center; gap: .4rem; align-self: flex-start; color: var(--accent-deep);
  font-size: .68rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
}
.v2-badge i { color: var(--accent); }

/* --- list rows ---------------------------------------------------------- */
.v2-list { display: flex; flex-direction: column; gap: .7rem; }
.v2-row {
  display: flex; align-items: center; gap: 1rem; background: var(--surface);
  border: 1px solid var(--line); border-radius: 12px; padding: .7rem .9rem; color: inherit;
  transition: border-color .14s, box-shadow .14s;
}
.v2-row:hover { border-color: var(--accent); box-shadow: var(--shadow-row); }
.v2-row__img { width: 74px; height: 56px; border-radius: 8px; overflow: hidden; background: var(--surface-sunk); flex: none; }
.v2-row__img img { width: 100%; height: 100%; object-fit: cover; }
.v2-row__main { flex: 1; min-width: 0; }
.v2-row__name { font-weight: 700; font-size: .95rem; color: var(--text); }
.v2-row__meta { display: flex; gap: .9rem; align-items: center; margin-top: .2rem; }
.v2-row__code { font-size: .72rem; font-weight: 700; color: var(--text-dim); background: var(--surface-sunk); border-radius: .35rem; padding: .12rem .45rem; }
.v2-row__go {
  width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center; color: var(--text-soft); flex: none;
}
.v2-row:hover .v2-row__go { background: var(--accent); border-color: var(--accent); color: var(--accent-on); }

.v2-empty { color: var(--muted); padding: 3rem 0; text-align: center; }

/* --- loading skeleton --------------------------------------------------- */
@keyframes v2Shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
.v2-sk {
  background: linear-gradient(90deg, var(--sk-base) 25%, var(--sk-shine) 50%, var(--sk-base) 75%);
  background-size: 200% 100%; animation: v2Shimmer 1.2s ease-in-out infinite; border-radius: .4rem;
}

/* --- panels ------------------------------------------------------------- */
.v2-panel { background: var(--surface); border: 1px solid var(--line); border-radius: 16px; overflow: hidden; }
.v2-panel__head {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 1rem 1.25rem; border-bottom: 1px solid var(--line);
}
.v2-panel__title {
  font-family: var(--font-display); font-weight: 700; letter-spacing: .04em;
  font-size: 1.05rem; color: var(--text-strong);
}
.v2-panel__body { padding: 1.25rem; }

/* --- tiles (collections landing) ---------------------------------------
   Photographic cards: the art fills the card and the label sits centred on it,
   with no scrim. That works because the two plate sets are graded to opposite
   ends — light mode's are pale sage, dark mode's are near-black — so the ink
   can simply follow the theme and land on its opposite. The halo is what
   carries the few busy spots (the blueprint linework, the embossed mark),
   since there is no tint to sit on. Art is a background-image driven by
   --tile-img / --tile-img-dark set per tile in the template, so only the
   active theme's file is ever fetched. */
.v2-tiles { display: grid; gap: 1.1rem; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
.v2-tile {
  position: relative; display: block; overflow: hidden; isolation: isolate;
  aspect-ratio: 3 / 4; color: inherit;
  background: var(--surface); border: 1px solid var(--line); border-radius: 16px;
  transition: border-color .14s, box-shadow .14s, transform .14s;
}
.v2-tile:hover { border-color: var(--accent); box-shadow: var(--shadow-card); transform: translateY(-3px); }
.v2-tile__art {
  position: absolute; inset: 0; z-index: 0;
  background-image: var(--tile-img); background-size: cover; background-position: center;
  transition: transform .5s cubic-bezier(.2, .7, .3, 1);
}
[data-theme="dark"] .v2-tile__art { background-image: var(--tile-img-dark, var(--tile-img)); }
.v2-tile:hover .v2-tile__art { transform: scale(1.06); }
.v2-tile__body {
  position: absolute; inset: 0; z-index: 1;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .3rem; padding: 1rem; text-align: center;
  background: var(--tile-veil);
}
.v2-tile__name {
  font-family: var(--font-display); font-weight: 700; font-size: .95rem;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--tile-ink); text-shadow: var(--tile-halo);
}
/* Two lines' worth whether the copy needs it or not, so a one-line and a
   two-line card still centre their names on the same row. */
.v2-tile__meta {
  font-size: .74rem; line-height: 1.35; min-height: 2.7em;
  color: var(--tile-ink-soft); text-shadow: var(--tile-halo);
}

/* Fallback card for a category with no photography — the pre-V2 glyph tile. */
.v2-tile--glyph {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .9rem;
  aspect-ratio: auto; min-height: 190px; padding: 1.75rem 1.25rem; text-align: center;
}
.v2-tile--glyph .v2-tile__name { color: var(--text); }
.v2-tile__icon {
  display: flex; align-items: center; justify-content: center;
  width: 72px; height: 72px; border-radius: 18px; background: var(--accent-soft); color: var(--accent);
  font-size: 1.6rem;
}
/* Category artwork is usually dark line-art on transparent, so it needs a
   consistent plate rather than inheriting the panel colour in dark mode. */
.v2-tile__icon img { max-width: 44px; max-height: 44px; object-fit: contain; }

/* --- forms -------------------------------------------------------------
   Styled by ELEMENT, not class, so django-crispy-forms output (which emits
   Bootstrap classes this stylesheet doesn't carry) picks the styling up
   without needing Bootstrap loaded alongside. */
.v2-form label { display: block; font-weight: 700; font-size: .82rem; color: var(--text-soft); margin-bottom: .35rem; }
.v2-form input, .v2-form select, .v2-form textarea {
  width: 100%; min-height: 46px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface); padding: .6rem .8rem; color: var(--text);
  font-family: inherit; font-size: .95rem;
}
.v2-form textarea { min-height: 110px; resize: vertical; }
.v2-form input:focus, .v2-form select:focus, .v2-form textarea:focus { outline: 0; border-color: var(--accent); }
.v2-form input[type="checkbox"], .v2-form input[type="radio"] { width: auto; min-height: 0; }
.v2-form .form-group, .v2-form .mb-3 { margin-bottom: 1rem; }
.v2-form small, .v2-form .form-text, .v2-form .helptext { display: block; margin-top: .3rem; font-size: .78rem; color: var(--muted); }
.v2-form .invalid-feedback, .v2-form .errorlist { color: #c0392b; font-size: .8rem; list-style: none; padding: 0; margin: .3rem 0 0; }
.v2-grid2 { display: grid; gap: 1rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 700px) { .v2-grid2 { grid-template-columns: 1fr; } }

/* --- buttons ------------------------------------------------------------ */
.v2-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  height: 46px; padding: 0 1.25rem; border-radius: 12px; cursor: pointer;
  font-family: inherit; font-weight: 700; font-size: .92rem;
  border: 1px solid var(--line); background: var(--surface); color: var(--text);
  transition: background .14s, border-color .14s, color .14s;
}
.v2-btn:hover {
  border-color: var(--accent); color: var(--accent-deep); background: var(--accent-soft);
}
.v2-btn--primary { background: var(--accent); border-color: var(--accent); color: var(--accent-on); }
.v2-btn--primary:hover { filter: brightness(1.08); background: var(--accent); color: var(--accent-on); }
.v2-btn--block { width: 100%; }
/* The secondary size, used inside cards and panel heads. It was five copies of
   an inline height/font-size override before, which drifted and left every
   in-card button a different width from its neighbour. */
.v2-btn--sm { height: 38px; padding: 0 .9rem; border-radius: 10px; font-size: .82rem; }
.v2-btn i { font-size: .92em; }
/* An action that belongs to a card fills its width. Ragged, centred buttons of
   three different widths were what made the collection grids look unfinished. */
.v2-swatch__foot .v2-btn, .v2-cardbtn { width: 100%; }
.v2-chip.is-on {
  border-color: var(--accent); color: var(--accent-deep); background: var(--accent-soft);
}

/* --- tables ------------------------------------------------------------- */
.v2-tablewrap { overflow-x: auto; }
.v2-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.v2-table th, .v2-table td { padding: .75rem .85rem; text-align: left; border-bottom: 1px solid var(--line); }
.v2-table th {
  font-size: .74rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-dim); font-weight: 700; white-space: nowrap;
}
.v2-table tbody tr:last-child td { border-bottom: 0; }
.v2-table tbody tr:hover { background: var(--surface-sunk); }
.v2-table img { max-width: 64px; border-radius: 8px; display: block; }

/* --- swatch cards (collection detail) ----------------------------------- */
.v2-swatches { display: grid; gap: 1.1rem; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
.v2-swatch { display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--line); border-radius: 14px; overflow: hidden; }
.v2-swatch__code {
  padding: .5rem .75rem; border-bottom: 1px solid var(--line);
  font-size: .74rem; font-weight: 700; letter-spacing: .06em; color: var(--text-dim); text-align: center;
}
/* White plate: swatch/logo artwork is shot on white, so it must not sit on the
   dark surface in dark mode. */
.v2-swatch__img { height: 150px; background: #fff; padding: .5rem; cursor: zoom-in; border: 0; width: 100%; }
.v2-swatch__img img { width: 100%; height: 100%; object-fit: contain; }
.v2-swatch__foot { padding: .7rem .75rem 1rem; display: flex; flex-direction: column; gap: .6rem; align-items: center; text-align: center; }
.v2-swatch__name { font-size: .82rem; color: var(--text-soft); line-height: 1.35; }
.v2-incart { display: inline-flex; align-items: center; gap: .4rem; font-size: .78rem; font-weight: 700; color: var(--accent-deep); }

/* --- pagination --------------------------------------------------------- */
.v2-pager { display: flex; justify-content: center; gap: .35rem; flex-wrap: wrap; margin-top: 2rem; }
.v2-pager a, .v2-pager span {
  min-width: 40px; height: 40px; padding: 0 .7rem; border-radius: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); background: var(--surface); color: var(--text);
  font-size: .88rem; font-weight: 600;
}
.v2-pager a:hover { border-color: var(--accent); color: var(--accent-deep); }
.v2-pager .is-active { background: var(--accent); border-color: var(--accent); color: var(--accent-on); }
.v2-pager .is-disabled { opacity: .45; }

/* --- lightbox ----------------------------------------------------------
   A ~40-line replacement for the Bootstrap modal the old page used. V2 pages
   deliberately don't load Bootstrap's JS, so the data-toggle modals would have
   been dead clicks. */
.v2-lightbox {
  position: fixed; inset: 0; z-index: 90; display: none;
  align-items: center; justify-content: center; padding: 2rem;
  background: rgba(12,16,10,.72); backdrop-filter: blur(3px);
}
.v2-lightbox.open { display: flex; }
.v2-lightbox__inner {
  background: var(--surface); border: 1px solid var(--line); border-radius: 16px;
  max-width: min(900px, 100%); max-height: 100%; display: flex; flex-direction: column; overflow: hidden;
}
.v2-lightbox__head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .85rem 1.1rem; border-bottom: 1px solid var(--line); }
.v2-lightbox__code { font-weight: 700; font-size: .9rem; color: var(--text); }
.v2-lightbox__body { background: #fff; padding: 1rem; overflow: auto; }
.v2-lightbox__body img { max-width: 100%; max-height: 65vh; object-fit: contain; display: block; margin: 0 auto; }
.v2-lightbox__foot { display: flex; justify-content: flex-end; gap: .5rem; padding: .8rem 1.1rem; border-top: 1px solid var(--line); }
.v2-lightbox__x { background: none; border: 0; cursor: pointer; color: var(--text-dim); font-size: 1.1rem; }
.v2-lightbox__x:hover { color: var(--text); }

/* --- order page: upload dropzones --------------------------------------
   Class names (.upload-container, .inputfile, .file-label, .upload-text,
   .preview-container) are kept EXACTLY as V1 had them: the drag-and-drop
   script selects on them, so renaming would silently break file uploads. */
.upload-container {
  border: 2px dashed var(--line); border-radius: 12px; padding: 1.25rem;
  text-align: center; cursor: pointer; color: var(--text-dim);
  background: var(--surface-sunk); transition: border-color .14s, background .14s;
}
.upload-container.dragover { border-color: var(--accent); background: var(--accent-soft); }
.inputfile { display: none; }
.file-label {
  cursor: pointer; display: flex; flex-direction: column; justify-content: center;
  align-items: center; gap: .4rem; width: 100%; margin-bottom: 0; min-height: 110px;
}
.upload-icon { font-size: 1.5rem; }
.upload-text { margin-bottom: .2rem; font-size: .86rem; }
.preview-container { display: flex; flex-wrap: wrap; gap: .5rem; justify-content: center; }
.preview-image { max-width: 100%; max-height: 140px; border: 1px dashed var(--line); border-radius: 8px; padding: 4px; background: #fff; }
.preview-pdf { width: 100%; height: 200px; border: 1px dashed var(--line); border-radius: 8px; }

/* --- order page: summary strip ------------------------------------------
   Also styles the AJAX-injected order-summary partial, which reuses these
   class names. */
.v2-summary {
  display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-start;
  border: 1px solid var(--line); border-radius: 12px; background: var(--surface-sunk);
  padding: 1rem; min-height: 96px;
}
.v2-summary:empty::before { content: "Your order is empty"; color: var(--muted); font-size: .88rem; }
.v2-summary .category-section { display: flex; flex-direction: column; gap: .4rem; }
.v2-summary .category-section > strong {
  font-size: .7rem; letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim);
}
.v2-summary .ordersummary {
  max-width: 100%; max-height: 88px; display: block; margin: 0 auto;
  border: 1px solid var(--line); border-radius: 8px; padding: 4px; background: #fff;
}
.v2-summary .upload-text { font-size: .74rem; color: var(--text-soft); text-align: center; }
.v2-summary .p-2 { padding: .25rem; position: relative; }

/* Per-item remove. Until now the only control on this page emptied the whole
   order, so one wrong diagram meant starting again. Sits on the thumbnail to
   keep the summary compact, and stays visible where there is no hover. */
.v2-summary__remove {
  position: absolute; top: -.3rem; right: -.3rem;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 999px; text-decoration: none;
  border: 1px solid var(--line); background: var(--surface); color: var(--text-dim);
  font-size: .95rem; line-height: 1; opacity: 0;
  transition: opacity .15s ease, color .15s ease, border-color .15s ease;
}
.v2-summary__edit {
  position: absolute; left: -.3rem; top: -.3rem;
  display: inline-flex; align-items: center; text-decoration: none;
  padding: .1rem .45rem; border-radius: 999px; font-size: .68rem; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase;
  border: 1px solid var(--line); background: var(--surface); color: var(--text-dim);
  opacity: 0; transition: opacity .15s ease, color .15s ease, border-color .15s ease;
}
.v2-summary .p-2:hover .v2-summary__remove,
.v2-summary .p-2:hover .v2-summary__edit,
.v2-summary__remove:focus-visible,
.v2-summary__edit:focus-visible { opacity: 1; }
.v2-summary__remove:hover { color: #c0392b; border-color: currentColor; }
.v2-summary__edit:hover { color: var(--accent); border-color: currentColor; }
@media (hover: none) {
  .v2-summary__remove, .v2-summary__edit { opacity: 1; }
}

/* --- order page: collection picker buttons ------------------------------ */
.v2-pills { display: flex; flex-wrap: wrap; gap: .45rem; }
.v2-pill {
  display: inline-flex; align-items: center; gap: .4rem; cursor: pointer;
  border: 1px solid var(--line); background: var(--surface); color: var(--text-soft);
  border-radius: 999px; padding: .45rem .9rem; font-size: .84rem; font-weight: 600;
  font-family: inherit;
}
.v2-pill:hover { border-color: var(--accent); color: var(--accent-deep); background: var(--accent-soft); }

/* Wide variant of the lightbox, used to host the collection picker iframe. */
.v2-lightbox--wide .v2-lightbox__inner { width: min(1200px, 100%); }
.v2-lightbox--wide .v2-lightbox__body { padding: 0; background: var(--surface); }
.v2-lightbox--wide iframe { display: block; width: 100%; height: 62vh; border: 0; background: #fff; }

/* --- misc --------------------------------------------------------------- */
.v2-note {
  border: 1px solid var(--line); background: var(--surface-sunk); border-radius: 12px;
  padding: .9rem 1rem; font-size: .88rem; color: var(--text-soft);
}
.v2-messages { display: flex; flex-direction: column; gap: .6rem; margin-bottom: 1.25rem; }
.v2-msg { border-radius: 12px; padding: .8rem 1rem; font-size: .9rem; border: 1px solid var(--line); background: var(--surface); }
/* Success is the one place that does NOT follow the accent: on dark the accent
   is azure, and a blue confirmation reads as information rather than as "that
   worked". It keeps --ok, which is green in both themes. */
.v2-msg--success { border-color: var(--ok); color: var(--ok-deep); background: var(--ok-soft); }
.v2-msg--warning { border-color: #d9a441; color: #8a6116; background: rgba(217,164,65,.12); }
[data-theme="dark"] .v2-msg--warning { color: #e8bd6d; }

/* --- footer ------------------------------------------------------------- */
.v2-foot {
  border-top: 1px solid var(--line); background: var(--surface); color: var(--muted);
  font-size: .85rem; text-align: center; padding: 1.2rem;
}
