/* ==========================================================================
   Blue Line Crafts — stylesheet
   Dark theme, blue accent. All colors are custom properties in :root, so
   retheming the whole site is a single-block edit.

   Every blue below is derived from the logo: hue 222, saturation 100%, with
   only the lightness varied. Sampled from BLC Logo 3.png, where the
   dominant blue is #0046E6.

   Why more than one blue: #0046E6 scores 7.23:1 with white on top, so it is
   a good button fill, but only 2.69:1 against the page background, so it
   fails as link text. Same hue lightened to #5C8DFF reaches 6.22:1 on the
   page and handles text. Fills use the brand blue exactly; text uses the
   lightened version.
   ========================================================================== */

:root {
  /* Tells the browser to render scrollbars, date pickers, and other native
     controls in their dark variants. */
  color-scheme: dark;

  /* Brand blues — all hue 222, sat 100%, sampled from BLC Logo 3.png */
  --blue:         #0046E6;  /* THE brand blue, straight off the logo.
                               Button and badge fills. White on it = 7.23:1 */
  --blue-hover:   #1F63FF;  /* one step lighter. White on it = 4.90:1 */
  --blue-bright:  #5C8DFF;  /* links, eyebrows, accents on dark = 6.22:1 */
  --blue-tint:    #0A1940;  /* blue-tinted panel behind card icons */
  --blue-deep:    #06143B;  /* CTA band background */

  /* The logo artwork uses pure #010102 for its splatter, which reads as a
     dull rectangle against the #0B0D10 page. This gradient pools true black
     behind the artwork and feathers it out, so the splatter has something to
     dissolve into instead of ending on a hard edge. Applied as a background
     on the img itself — it shows through the transparent pixels. */
  --logo-pool: radial-gradient(ellipse 54% 52% at 50% 50%,
                 #010102 0%,
                 #010102 44%,
                 rgba(1,1,2,0.80) 60%,
                 rgba(1,1,2,0.45) 74%,
                 rgba(1,1,2,0.15) 86%,
                 transparent 95%);

  /* Wider, softer version for the large home page logo, where a tight fade
     reads as a padded box rather than a glow. */
  --logo-pool-lg: radial-gradient(ellipse 62% 58% at 50% 50%,
                    #010102 0%,
                    #010102 30%,
                    rgba(1,1,2,0.92) 44%,
                    rgba(1,1,2,0.70) 56%,
                    rgba(1,1,2,0.44) 68%,
                    rgba(1,1,2,0.22) 79%,
                    rgba(1,1,2,0.08) 89%,
                    transparent 97%);

  /* Dark neutrals, darkest to lightest */
  --n-900:        #0B0D10;  /* page background */
  --n-850:        #101317;  /* alternating sections */
  --n-800:        #15181D;  /* cards, inputs */
  --n-750:        #1B1F25;  /* raised / hover */
  --n-700:        #242931;  /* borders */
  --n-600:        #333941;  /* stronger borders */
  --n-400:        #6E7885;  /* disabled, faint text */
  --n-300:        #98A2AE;  /* muted text */
  --n-100:        #C9D1DA;  /* secondary text */
  --n-000:        #EEF2F6;  /* primary text */
  --white:        #FFFFFF;

  /* Feedback */
  --danger:       #FF6B6B;
  --danger-bg:    #2A1416;

  /* Semantic */
  --bg:           var(--n-900);
  --bg-alt:       var(--n-850);
  --surface:      var(--n-800);
  --surface-2:    var(--n-750);
  --text:         var(--n-000);
  --text-soft:    var(--n-100);
  --text-muted:   var(--n-300);
  --border:       var(--n-700);
  --border-2:     var(--n-600);

  /* Type */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;

  /* Spacing scale */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.5rem;
  --s-6: 2rem;
  --s-7: 3rem;
  --s-8: 4rem;
  --s-9: 6rem;

  /* Other */
  --radius:    8px;
  --radius-lg: 14px;
  --shadow:    0 1px 3px rgba(0,0,0,0.5), 0 4px 12px rgba(0,0,0,0.35);
  --shadow-lg: 0 4px 14px rgba(0,0,0,0.55), 0 18px 44px rgba(0,0,0,0.45);
  --maxw:      1180px;
  --ease:      cubic-bezier(0.4, 0, 0.2, 1);
}

/* --------------------------------------------------------------- Reset -- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  background: var(--bg);
}

body {
  margin: 0;
  font-family: var(--font);
  font-size: 17px;
  line-height: 1.65;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }

h1, h2, h3, h4 {
  margin: 0 0 var(--s-4);
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text);
}

/* Both a size down. At 3.25rem an h1 filled a third of the first screen, and
   an h2 at 2.35rem inside a 720px .section-head wrapped into two or three
   short lines - large and narrow at the same time, which is the worst
   combination because the size draws the eye to something hard to read.
   balance splits any wrap that IS needed evenly instead of orphaning a word;
   it never forces one. */
h1 { font-size: clamp(1.9rem, 4vw, 2.75rem); text-wrap: balance; }
h2 { font-size: clamp(1.5rem, 3vw, 2rem); text-wrap: balance; }
h3 { font-size: 1.25rem; }
h4 { font-size: 1.05rem; }

p { margin: 0 0 var(--s-4); }

a { color: var(--blue-bright); text-decoration: none; }
a:hover { text-decoration: underline; }

ul, ol { margin: 0 0 var(--s-4); padding-left: var(--s-5); }
li { margin-bottom: var(--s-2); }

strong { color: var(--text); font-weight: 700; }

code {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.1em 0.4em;
  font-size: 0.9em;
  color: var(--text-soft);
}

::selection { background: var(--blue); color: var(--white); }

/* Visible keyboard focus everywhere */
:focus-visible {
  outline: 3px solid var(--blue-bright);
  outline-offset: 2px;
  border-radius: 3px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--blue);
  color: var(--white);
  padding: var(--s-3) var(--s-4);
  z-index: 200;
}
.skip-link:focus { left: 0; }

/* ------------------------------------------------------------- Layout -- */

.container {
  width: 100%;
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 var(--s-5);
}

.section { padding: var(--s-8) 0; }
.section--tight { padding: var(--s-7) 0; }
.section--alt { background: var(--bg-alt); }

/* 860 rather than 720. The old width was tuned for the intro paragraph, but
   the heading shares the box - and a heading has far fewer characters per
   line, so it hit the edge long before the paragraph did. */
.section-head {
  max-width: 860px;
  margin: 0 auto var(--s-7);
  text-align: center;
}
.section-head p { max-width: 68ch; margin-inline: auto; }
.section-head p {
  font-size: 1.1rem;
  color: var(--text-muted);
  margin: 0;
}

/* An aside with some personality. Deliberately smaller and set apart from
   the line above so it reads as a wink rather than another sales claim. */
.quip {
  margin-top: var(--s-3) !important;
  font-size: 0.95rem;
  color: var(--blue-bright);
}


.eyebrow {
  display: inline-block;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--blue-bright);
  margin-bottom: var(--s-3);
}

.grid { display: grid; gap: var(--s-5); }
.grid--2 { grid-template-columns: repeat(2, 1fr); }
.grid--3 { grid-template-columns: repeat(3, 1fr); }
.grid--4 { grid-template-columns: repeat(4, 1fr); }

.text-center { text-align: center; }
.muted { color: var(--text-muted); }

/* ------------------------------------------------------- Blue line rule -- */

.blue-line {
  height: 4px;
  width: 60px;
  background: var(--blue-bright);
  border: 0;
  margin: 0 0 var(--s-5);
}
.blue-line--center { margin-left: auto; margin-right: auto; }

/* ------------------------------------------------------------ Buttons -- */

.btn {
  display: inline-block;
  padding: var(--s-3) var(--s-6);
  font-size: 1rem;
  font-weight: 600;
  font-family: inherit;
  line-height: 1.4;
  text-align: center;
  text-decoration: none;
  border: 2px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  transition: background 0.18s var(--ease), color 0.18s var(--ease),
              border-color 0.18s var(--ease), transform 0.12s var(--ease);
}
.btn:hover { text-decoration: none; transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn--primary {
  background: var(--blue);
  color: var(--white);
  border-color: var(--blue);
}
.btn--primary:hover { background: var(--blue-hover); border-color: var(--blue-hover); }

.btn--outline {
  background: transparent;
  color: var(--blue-bright);
  border-color: var(--blue);
}
.btn--outline:hover { background: var(--blue); color: var(--white); border-color: var(--blue); }

.btn--ghost {
  background: transparent;
  color: var(--text);
  border-color: var(--border-2);
}
.btn--ghost:hover { border-color: var(--blue-bright); color: var(--blue-bright); }

.btn--lg { padding: var(--s-4) var(--s-7); font-size: 1.08rem; }
.btn--block { display: block; width: 100%; }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}
.btn-row--center { justify-content: center; }

/* ------------------------------------------------------------- Header -- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(11,13,16,0.92);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--border);
}
/* The thin blue line under the header — brand cue */
.site-header::after {
  content: "";
  display: block;
  height: 3px;
  background: var(--blue-bright);
}

/* Taller than a standard header because the logo is a stacked two-line
   wordmark rather than a horizontal one. At 72px the artwork could only be
   about 46px tall, which is not enough for a two-line mark to read. */
.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  min-height: 88px;
}

.logo {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-weight: 800;
  font-size: 1.05rem;
  letter-spacing: -0.02em;
  color: var(--text);
  text-decoration: none;
  flex-shrink: 0;
}
.logo:hover { text-decoration: none; color: var(--blue-bright); }

/* The BLC mark. The full lockup is unreadable below about 90px tall, so the
   header uses a crop of just the letterforms and lets the text beside it
   carry the full company name. */
/* The logo IS the wordmark, so there is no text lockup beside it — that
   only repeated the company name back at itself. Sized by height, since
   the header's vertical space is the fixed dimension and the width of a
   stacked mark follows from it. box-sizing is border-box, so 70px here is
   62px of artwork plus 4px of fade above and below. */
.logo-mark {
  height: 70px;
  width: auto;
  flex-shrink: 0;
  padding: 4px 10px;
  background: var(--logo-pool);
}

/* Name and trade beside the mark. The parent link carries an aria-label,
   which overrides its contents for screen readers, so this does not get
   announced twice alongside the image alt text. */
.logo-text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  line-height: 1.15;
  letter-spacing: -0.01em;
}
.logo-text small {
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-top: 3px;
}


.nav { display: flex; align-items: center; gap: var(--s-2); }

.nav a {
  padding: var(--s-2) var(--s-4);
  font-size: 0.96rem;
  font-weight: 500;
  color: var(--text-soft);
  border-radius: var(--radius);
  text-decoration: none;
  /* "Team stores" is the only two-word link, so it is the only one that can
     break onto a second line when the bar gets tight. That made its row
     taller than the rest. Nothing in a nav should ever wrap. */
  white-space: nowrap;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}
.nav a:hover { color: var(--blue-bright); background: var(--blue-tint); text-decoration: none; }
.nav a.is-active { color: var(--blue-bright); font-weight: 600; }

.nav .btn { margin-left: var(--s-3); color: var(--white); }
.nav .btn:hover { background: var(--blue-hover); color: var(--white); }

/* ------------------------------------------------- Nav dropdowns -- */
/* Seven top-level links had run the horizontal bar out of room, and every
   new page made it worse. Grouping into two dropdowns leaves three items
   plus the quote button, with space to grow.

   Opened by click rather than hover. Hover menus are unusable on a
   touchscreen and awkward for anyone who does not have steady pointer
   control, and a click works identically on both. */

.nav-item { position: relative; }

.nav-sub-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: var(--s-2) var(--s-4);
  font-size: 0.96rem;
  font-family: inherit;
  font-weight: 500;
  color: var(--text-soft);
  background: none;
  border: 0;
  border-radius: var(--radius);
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}
.nav-sub-toggle:hover { color: var(--blue-bright); background: var(--blue-tint); }
.nav-sub-toggle[aria-expanded="true"] { color: var(--blue-bright); background: var(--blue-tint); }

/* Chevron drawn in CSS rather than shipped as an icon file */
.nav-sub-toggle::after {
  content: "";
  width: 6px;
  height: 6px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-1px, -2px);
  transition: transform 0.18s var(--ease);
}
.nav-sub-toggle[aria-expanded="true"]::after {
  transform: rotate(-135deg) translate(-2px, -1px);
}

/* Centred under its trigger rather than left-aligned to it. Left alignment
   is fine for the first item in a nav and looks increasingly wrong the
   further right the trigger sits - by the last one the panel is hanging off
   toward the page edge. */
.nav-sub {
  position: absolute;
  top: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  min-width: 210px;
  padding: var(--s-2);
  background: var(--n-850);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  z-index: 120;
}
.nav-sub[hidden] { display: none; }
.nav-sub a {
  display: block;
  padding: var(--s-3) var(--s-4);
  font-size: 0.94rem;
  border-radius: var(--radius);
  white-space: nowrap;
}

.nav-toggle {
  display: none;
  background: none;
  border: 1px solid var(--border-2);
  border-radius: var(--radius);
  padding: var(--s-2) var(--s-3);
  cursor: pointer;
  font-size: 1.2rem;
  line-height: 1;
  color: var(--text);
}

/* --------------------------------------------------------------- Hero -- */

.hero {
  padding: var(--s-9) 0;
  background:
    radial-gradient(ellipse 90% 70% at 12% 0%, rgba(0,70,230,0.22) 0%, transparent 62%),
    var(--bg);
  border-bottom: 1px solid var(--border);
}

.hero-inner {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: var(--s-8);
  align-items: center;
}

.hero h1 { margin-bottom: var(--s-5); }
.hero h1 .accent { color: var(--blue-bright); }

.hero-lede {
  font-size: 1.18rem;
  color: var(--text-soft);
  max-width: 54ch;
  margin-bottom: var(--s-6);
}

/* Grid rather than flex-wrap. Wrapping flex items break wherever the text
   happens to run out, which leaves ragged rows. A grid keeps the columns
   aligned, and with six items it divides evenly at one, two, or three
   columns, so no row is ever left short.

   The 200px minimum is deliberate. Three columns inside the hero text
   column leaves roughly 144px per label, which is narrow enough that five
   of the six bullets wrap onto a second line while the short one does not
   — that mismatch is what reads as ragged. At 200px the grid settles on
   two columns of about 256px, wide enough that every label fits on one
   line and all six sit level. */
.hero-points {
  list-style: none;
  padding: 0;
  margin: var(--s-6) 0 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--s-3) var(--s-5);
}
.hero-points li {
  margin: 0;
  padding-left: var(--s-5);
  position: relative;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--text-soft);
}
.hero-points li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 12px;
  height: 3px;
  background: var(--blue-bright);
}

/* Home page: the full logo lockup sits opposite the headline. No card or
   border around it — the artwork already has its own splatter edge, and a
   frame would fight with it. */
.hero-logo {
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Generous padding so the black has somewhere to go. Because box-sizing is
   border-box, the artwork itself lands around 430px inside a 620px box. */
.hero-logo img {
  width: 100%;
  max-width: 620px;
  height: auto;
  padding: 15% 15%;
  background: var(--logo-pool-lg);
}

/* Retired: the About page placeholder box. Removed once the real Parma
   Police photo landed. Restore from git if another placeholder is ever
   needed.
.hero-visual {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  aspect-ratio: 4 / 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  font-size: 0.9rem;
  text-align: center;
  padding: var(--s-5);
  box-shadow: var(--shadow);
}
*/

/* ---------------------------------------------------------- Our story -- */
/* Long-form prose. Capped near 70 characters per line, which is where
   reading comfort sits, and centered so it does not run the full container
   width like the card grids do. */

.story {
  max-width: 760px;
  margin: 0 auto;
}
.story p { color: var(--text-soft); }
.story h3 {
  margin-top: var(--s-7);
  margin-bottom: var(--s-4);
  color: var(--blue-bright);
  font-size: 1.3rem;
}
/* The story now opens on a subhead, so the first one loses the top margin
   that separates the later ones — the section padding already provides it. */
.story > h3:first-child { margin-top: 0; }
/* Photos inside the story. Capped at the image's own pixel width so it is
   never upscaled — an enlarged photo looks softer than a smaller sharp one.
   Centered, since it is narrower than the text column. */
.story-figure {
  margin: var(--s-7) auto;
  max-width: 640px;
}
.story-figure img {
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  display: block;
}
.story-figure figcaption {
  margin-top: var(--s-3);
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--text-muted);
  text-align: center;
}

/* The last line of the story. Given weight because it is the line the
   whole page has been building toward. */
.story-close {
  margin-top: var(--s-6);
  padding-left: var(--s-5);
  border-left: 3px solid var(--blue-bright);
  border-radius: 0;
  font-size: 1.18rem;
  line-height: 1.55;
  color: var(--text) !important;
}

/* Signs the story off. A named person at the end of a first-person page
   does more for trust than another paragraph would. */
.story-signature {
  margin-top: var(--s-6);
  padding-top: var(--s-5);
  border-top: 1px solid var(--border);
  font-size: 0.95rem;
  color: var(--text-muted) !important;
  line-height: 1.5;
}
.story-signature-name {
  display: block;
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.01em;
}

/* --------------------------------------------------- Page header (sub) -- */

/* Centred, and a size down. At clamp(2rem, 5vw, 3.25rem) the heading was
   taking a third of the first screen on a laptop before anything useful
   appeared, and left-aligned it sat against the edge while every section
   below it is centred. */
/* 1.5rem top and bottom, down from 3 and 2. The band was carrying 5rem of
   padding around two lines of text, and the .section below it adds another
   4rem - so nine-tenths of the first screen was empty before anything you
   could act on. */
.page-head {
  padding: var(--s-5) 0;
  background: var(--bg-alt);
  border-bottom: 1px solid var(--border);
  text-align: center;
}
.page-head h1 {
  margin-bottom: var(--s-2);
  font-size: clamp(1.7rem, 3.2vw, 2.35rem);
  max-width: 22ch;
  margin-inline: auto;
}
.page-head p {
  font-size: 1.06rem;
  color: var(--text-soft);
  max-width: 70ch;
  margin-inline: auto;
  margin-block: 0;
  /* balance keeps a two-line tagline evenly split instead of leaving one
     orphaned word, and text-wrap:pretty is the fallback where it is not
     supported. Neither forces a wrap that is not needed. */
  text-wrap: balance;
}
/* The intro paragraph carries the page. Slightly larger, and because ch
   scales with font-size it also runs physically wider than the ones under
   it, so a multi-paragraph header tapers instead of ending in a hard edge
   well short of the page. */
.page-head p:first-of-type {
  font-size: 1.14rem;
  line-height: 1.5;
  color: var(--text);
  max-width: 68ch;
}
/* .page-head p sets margin:0, which killed the global paragraph spacing.
   Pages with more than one intro paragraph were stacking with no gap. */
.page-head p + p { margin-top: var(--s-4); }
.page-head p + .page-head-line { margin-top: var(--s-5) !important; }

@media (max-width: 700px) {
  .page-head p:first-of-type { font-size: 1.14rem; }
  .page-head p { font-size: 1.04rem; }
}

/* Split page header. Heading, lead and buttons on the left; supporting
   lines on the right behind a blue rule. Only worth using where there is
   enough copy to fill the second column - otherwise the right side reads
   as an empty gap. Pages with a single intro paragraph keep the default. */
.page-head--split .page-head-split {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: var(--s-7);
  align-items: start;
}
.page-head--split .page-head-main p:first-of-type { max-width: 46ch; }
.page-head--split .page-head-aside {
  border-left: 3px solid var(--blue);
  padding-left: var(--s-5);
}
.page-head--split .page-head-aside p {
  font-size: 1.02rem;
  line-height: 1.7;
  max-width: none;
}
.page-head--split .page-head-aside p:first-of-type {
  font-size: 1.02rem;
  line-height: 1.7;
  color: var(--text-soft);
  max-width: none;
}
.page-head--split .btn-row { margin-top: var(--s-6); }

@media (max-width: 880px) {
  .page-head--split .page-head-split {
    grid-template-columns: 1fr;
    gap: var(--s-6);
  }
  .page-head--split .page-head-main p:first-of-type { max-width: 64ch; }
  .page-head--split .btn-row { margin-top: var(--s-5) !important; }
}

/* A closing statement inside the page header rather than the body. Sits
   below the intro with a blue rule, so it reads as the point being made
   rather than the first line of the article underneath. */
.page-head-line {
  margin-top: var(--s-5) !important;
  padding-left: var(--s-5);
  border-left: 3px solid var(--blue-bright);
  font-size: 1.22rem !important;
  line-height: 1.5;
  font-weight: 600;
  color: var(--text) !important;
}

/* ------------------------------------------------- Section variety -- */

/* Four alternatives to another grid of cards. Used so no two adjacent
   sections share a treatment. */

/* 1. Spec table. For genuinely tabular content - two things compared
   across the same set of attributes. */
.spec-table-wrap { max-width: 900px; margin: 0 auto; overflow-x: auto; }
.spec-table {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
}
.spec-table th,
.spec-table td {
  padding: var(--s-4) var(--s-4) var(--s-4) 0;
  vertical-align: top;
  border-top: 1px solid var(--border);
}
.spec-table thead th { border-top: 0; padding-top: 0; }
.spec-table thead th:first-child { width: 22%; }
.spec-table thead th + th { width: 39%; }
.spec-table th[scope="col"] {
  font-size: 1.2rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text);
}
.spec-table th[scope="row"] {
  font-weight: 600;
  color: var(--text-muted);
  padding-right: var(--s-5);
}
.spec-table td { color: var(--text-soft); }
.spec-table-note {
  max-width: 900px;
  margin: var(--s-5) auto 0;
  color: var(--text-muted);
}
@media (max-width: 720px) {
  .spec-table { min-width: 540px; }
}

/* 2. Numbered list. Cards without the chrome - a rule between rows
   instead of a border around each. */
.numbered-list { max-width: 860px; margin: 0 auto; display: grid; gap: var(--s-6); }
.numbered-list > li {
  display: grid;
  grid-template-columns: 3.25rem 1fr;
  gap: var(--s-5);
  align-items: start;
  list-style: none;
}
.numbered-list > li + li { border-top: 1px solid var(--border); padding-top: var(--s-6); }
.numbered-list .num {
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1;
  color: var(--blue-bright);
  font-variant-numeric: tabular-nums;
}
.numbered-list h3 { margin-bottom: var(--s-2); }
.numbered-list p { margin-bottom: 0; color: var(--text-soft); }
.numbered-list p + p { margin-top: var(--s-3); }
@media (max-width: 560px) {
  .numbered-list > li { grid-template-columns: 1fr; gap: var(--s-2); }
}

/* 3. Tag list. A long enumeration reads better as pills than as three
   cards each holding a sentence that is really just a list. */
.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  padding: 0;
  margin: 0;
  list-style: none;
}
.tag-list li {
  border: 1px solid var(--border-2);
  border-radius: 999px;
  padding: var(--s-2) var(--s-5);
  color: var(--text);
  font-size: 0.98rem;
}

/* 4. Media rows. Alternating text and image. The image is a background
   on a div rather than an <img> so a not-yet-shot photo degrades to a
   flat panel instead of a broken-image icon. */
.media-rows { display: grid; gap: var(--s-8); }
.media-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-7);
  align-items: center;
}
.media-row:nth-child(even) .media-row-text { order: 2; }
.media-row .eyebrow { display: block; margin-bottom: var(--s-2); }
.media-row h3 { font-size: 1.45rem; margin-bottom: var(--s-3); }
.media-row p { color: var(--text-soft); margin-bottom: 0; }
.media-row p + p { margin-top: var(--s-3); }
.media-shot {
  background-color: var(--surface);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  aspect-ratio: 4 / 3;
}
@media (max-width: 820px) {
  .media-rows { gap: var(--s-7); }
  .media-row { grid-template-columns: 1fr; gap: var(--s-5); }
  .media-row:nth-child(even) .media-row-text { order: 0; }
  .media-shot { aspect-ratio: 16 / 9; }
}

/* -------------------------------------------------------------- Cards -- */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--s-6);
  transition: transform 0.18s var(--ease), box-shadow 0.18s var(--ease),
              border-color 0.18s var(--ease), background 0.18s var(--ease);
}
.card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lg);
  border-color: var(--border-2);
  background: var(--surface-2);
}
.card h3 { margin-bottom: var(--s-3); }
.card p { color: var(--text-soft); }
.card p:last-child { margin-bottom: 0; }

.card-icon {
  width: 44px;
  height: 44px;
  border-radius: var(--radius);
  background: var(--blue-tint);
  color: var(--blue-bright);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
  font-weight: 700;
  margin-bottom: var(--s-4);
}

/* ------------------------------------------------- Contact layout -- */
/* The quote form beside the contact details.

   This was previously an inline style on the element, which is why it never
   collapsed on a phone — the responsive rules in this file cannot override an
   inline grid-template-columns, so the two columns stayed side by side at
   every width and both ended up too narrow to use.

   Now it is a class, so the stacking rule below actually applies. */
.contact-layout {
  display: grid;
  grid-template-columns: 1.5fr 1fr;
  gap: 3rem;
}

@media (max-width: 900px) {
  .contact-layout { grid-template-columns: 1fr; gap: var(--s-7); }
}

/* ----------------------------------------------------------- Tabs -- */
/* Category tabs over a product list. Five stacked sections made the page
   very long; tabs put one category on screen at a time without hiding
   anything from search engines, since every panel is in the HTML and only
   the display switches. */

.tablist {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  justify-content: center;
  margin-bottom: var(--s-6);
}

.tab {
  padding: var(--s-3) var(--s-5);
  font-size: 0.95rem;
  font-family: inherit;
  font-weight: 600;
  color: var(--text-soft);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  transition: all 0.15s var(--ease);
}
.tab:hover { border-color: var(--blue-bright); color: var(--blue-bright); }
.tab[aria-selected="true"] {
  background: var(--blue);
  border-color: var(--blue);
  color: var(--white);
}

.tabpanel[hidden] { display: none; }

/* ------------------------------------------------- Product rows -- */
/* A single column of products, each with an optional example photo to the
   right. The photo column only exists when there is an image — a row with
   no photo lets its text run the full width rather than leaving a hole. */

.prodlist { list-style: none; padding: 0; margin: 0; }

.prod {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-5);
  align-items: center;
  padding: var(--s-5) 0;
  border-bottom: 1px solid var(--border);
  margin: 0;
}
.prod:last-child { border-bottom: 0; }
.prod:has(.prod-img) { grid-template-columns: 1fr 200px; }

.prod h3 { margin-bottom: var(--s-2); font-size: 1.15rem; }
.prod p { margin-bottom: var(--s-2); }
.prod p:last-child { margin-bottom: 0; }
.prod-spec {
  font-size: 0.86rem;
  color: var(--blue-bright);
  font-weight: 600;
  margin-bottom: var(--s-2) !important;
}
.prod ul { margin: var(--s-2) 0 0; padding-left: var(--s-5); }
.prod ul li { font-size: 0.92rem; color: var(--text-muted); margin-bottom: var(--s-1); }

.prod-img {
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--n-850);
}
.prod-img img { width: 100%; height: auto; display: block; }

@media (max-width: 700px) {
  .prod:has(.prod-img) { grid-template-columns: 1fr; }
  .prod-img { max-width: 240px; }
}

/* ------------------------------------------------------ Brand grid -- */
/* Modelled on the tile grid wholesalers use. Logos are trademarks, so each
   one comes from the brand or distributor's own dealer asset library rather
   than being pulled off the open web. Two grids by logo shape - wide marks
   and roughly square marks - because one grid sized for both leaves the
   square logos looking tiny in an oversized box. Tiles carry an alt text
   name so the filter can match on text and search engines can read them. */

.brand-tools {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-5);
  margin-bottom: var(--s-6);
}

.brand-search { display: block; width: 100%; max-width: 380px; }
.brand-search input {
  width: 100%;
  padding: var(--s-3) var(--s-4);
  font-size: 1rem;
  font-family: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-2);
  border-radius: var(--radius);
}
.brand-search input::placeholder { color: var(--n-400); }
.brand-search input:focus {
  outline: none;
  background: var(--surface-2);
  border-color: var(--blue-bright);
  box-shadow: 0 0 0 3px rgba(92,141,255,0.25);
}

.brand-count { margin: 0; font-size: 0.88rem; color: var(--text-muted); }

/* auto-fill rather than auto-fit: with auto-fit the last row stretches its
   few remaining tiles to full width, which looks like a mistake. auto-fill
   keeps every tile the same size and simply leaves the gap. */
/* One uniform grid, single alphabetical run.

   Two other layouts were tried and rejected: wide logos spanning two columns
   (uneven rhythm, read as broken), and separate grids grouped by logo shape
   (still left the square marks looking small).

   The remaining variable is the logos themselves — several arrive with
   whitespace baked into the file, which no amount of CSS can undo, since the
   browser can only scale what it is given. Bob is cropping those. Once each
   file is tight to its artwork, a single tile size works for all of them. */
.brand-grid {
  display: grid;
  gap: var(--s-3);
}

/* Wide logos: long horizontal lockups, 24 of them. */
.brand-grid--wide {
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
}
.brand-grid--wide .brand { min-height: 104px; }
.brand-grid--wide .brand img { max-height: 64px; }

/* Square and near-square marks, 14 of them. Narrower tile, taller box, so
   they fill it rather than sitting small in a wide frame. */
.brand-grid--square {
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  margin-top: var(--s-5);
}
.brand-grid--square .brand { min-height: 148px; }
.brand-grid--square .brand img { max-height: 104px; }

/* Public safety, where there are exactly eight brands rather than forty.
   Fixed four columns instead of auto-fill, so it lands on two clean rows of
   four at any width rather than reflowing to five and three. Tiles and marks
   are smaller than the apparel page uses: eight logos at apparel size looked
   like the whole section was the brand list, and here the brands are a
   supporting note under the services above them. */
/* max-width WITH auto margins. A max-width on its own leaves the grid pinned
   to the left of a centered container, which is what it was doing - the row
   of four sat off to one side rather than under the middle of the text.

   660px is sized to the tiles rather than to the page: four columns and
   three 12px gaps leaves 156px each, which is a sane frame for a mark capped
   at 52px. At 860 the tiles were 206px wide and the row read as though it
   had been spaced for five and come up one short. */
.brand-grid--compact {
  grid-template-columns: repeat(4, 1fr);
  max-width: 660px;
  margin-left: auto;
  margin-right: auto;
}
.brand-grid--compact .brand { min-height: 84px; padding: var(--s-3); }
.brand-grid--compact .brand img { max-height: 52px; }

@media (max-width: 760px) {
  .brand-grid--compact { grid-template-columns: repeat(2, 1fr); max-width: 340px; }
}

/* Light tiles, not dark ones.

   Brand logos are overwhelmingly black or dark-on-transparent, because they
   are designed for print and for white web pages. Dropped onto a #15181D
   card most of them would be nearly invisible, and knockout white versions
   simply do not exist for every brand.

   So the tile carries the light background the logos expect. Slightly off
   white rather than pure #FFF — 58 pure-white rectangles on a near-black
   page is a lot of glare, and the small step down takes the edge off
   without affecting how any logo reads.

   Text and logo tiles share these rules, so the grid stays visually
   consistent while logos are being collected a few at a time. */
.brand {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  /* Tall enough for square logos to breathe.

     Brand marks in this grid range from about 0.8:1 (LAT, taller than it is
     wide) to nearly 8:1 (Flexfit). A wide logo is limited by the tile's
     width and fills it easily. A square one is limited by the height cap,
     so with a short tile it stops at a third the size of its neighbours and
     looks like a mistake.

     Raising the tile height and the cap together is what lets those grow.
     Very wide logos then sit in a tile with space above and below, which is
     the unavoidable cost of showing mixed aspect ratios at a consistent
     optical size — and much less noticeable than the alternative. */
  min-height: 110px;
  /* Tight horizontal padding, because every pixel of it is taken directly
     out of the logo's usable width. */
  padding: var(--s-3) var(--s-2);
  /* Pure white, not off-white.
     Seven of the supplied logos have no alpha channel — the white is baked
     into the image. On a #EEF1F4 tile each of those showed a faint white
     rectangle, which reads as a mistake rather than a design choice. Matching
     the tile to the logos' own background makes those edges disappear, and
     costs only a little extra brightness. */
  background: #FFFFFF;
  border: 1px solid #D6DBE1;
  border-radius: var(--radius);
  font-size: 0.95rem;
  font-weight: 700;
  color: #1A1D21;
  line-height: 1.25;
  overflow: hidden;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease),
              transform 0.15s var(--ease);
}
.brand:hover {
  background: #FFFFFF;
  border-color: var(--blue-bright);
  transform: translateY(-2px);
}
.brand[hidden] { display: none; }
/* Most brand tiles are now links to Bob's catalog stores. They inherit the
   tile's own color, so all that is left is to kill the underline. */
a.brand { text-decoration: none; cursor: pointer; }
/* Sits above the search box, so it is read before anyone starts clicking. */
.brand-note--cta { margin-top: calc(var(--s-4) * -1); margin-bottom: var(--s-6); }
.brand-note {
  max-width: 62ch;
  margin: 0 auto var(--s-5);
  text-align: center;
  color: var(--text-soft);
  font-size: 0.95rem;
  line-height: 1.6;
}
a.brand:focus-visible {
  outline: 3px solid var(--blue-bright);
  outline-offset: 2px;
}

/* Logos are constrained by height rather than width. Brand marks vary wildly
   in aspect ratio — adidas is nearly square, Independent Trading Company is
   a long horizontal lockup — and capping width alone would let the tall ones
   overflow the tile. max-width keeps the wide ones inside it. */
.brand img {
  max-height: 86px;
  max-width: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
}

.brand-empty {
  margin: var(--s-6) auto 0;
  max-width: 52ch;
  text-align: center;
  color: var(--text-muted);
}

/* Visually hidden but still announced by screen readers. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 520px) {
  .brand-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
  .brand { min-height: 88px; font-size: 0.88rem; }
  .brand img { max-height: 58px; }
}

/* ------------------------------------------------- Announcement bar -- */
/* Sits above the hero so it is the first thing read, but kept to a single
   line. A full announcement block here would push the headline and the
   quote buttons about 500px down the page, which means a first-time
   visitor reads about features they cannot use yet before learning what
   the business actually does.

   The whole bar is one link down to the detail further down the page, so
   the cost up top stays at roughly 50px. */

.announce {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-3);
  padding: var(--s-3) var(--s-5);
  background: var(--blue-deep);
  border-bottom: 1px solid var(--border);
  color: var(--text-soft);
  font-size: 0.92rem;
  line-height: 1.4;
  text-align: center;
  text-decoration: none;
  transition: background 0.18s var(--ease);
}
.announce:hover { background: var(--blue-tint); text-decoration: none; }
.announce:hover .announce-more { color: var(--white); }

.announce-tag {
  flex-shrink: 0;
  padding: 2px var(--s-3);
  border-radius: 999px;
  background: var(--blue);
  color: var(--white);
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.announce-more {
  flex-shrink: 0;
  font-weight: 600;
  color: var(--blue-bright);
  transition: color 0.18s var(--ease);
}

/* On a phone the full sentence plus the prompt runs to four or five lines,
   which is no longer a bar. The sentence carries the message on its own. */
@media (max-width: 620px) {
  .announce { font-size: 0.86rem; padding: var(--s-3) var(--s-4); }
  .announce-more { display: none; }
}

/* Marks a feature that is announced but not built yet. Deliberately a quiet
   outline rather than a solid blue fill: a badge that shouts competes with
   the heading beneath it, and this has to read as an honest label rather
   than a promotion. Sitting above the heading means it is read first, so
   nobody gets halfway through a description before learning they cannot
   use the thing yet. */
.soon {
  display: inline-block;
  margin-bottom: var(--s-3);
  padding: 3px var(--s-3);
  border: 1px solid var(--blue);
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--blue-bright);
  background: var(--blue-tint);
}

.card--flat { background: var(--bg-alt); border: 1px solid var(--border); }
.card--flat:hover { transform: none; box-shadow: none; background: var(--surface); }

/* Photo behind a service card, set per card with --card-img.

   Two stacked layers rather than one. ::before holds the photo at low
   opacity; ::after lays a dark gradient over it. The gradient is what keeps
   the text readable — opacity alone is not enough, because a bright patch
   in a photo (a white jersey, sky) stays bright at any opacity and text
   crossing it loses contrast even when the average looks fine.

   The two numbers have to be read together, which is the mistake made here
   first time around: the photo was set to 18% and then covered by a gradient
   running 55% to 94%. Those multiply. What actually survived to the screen
   was 18% x 45% = 8% of the photo at the top of the card and 1% at the
   bottom, which is indistinguishable from no photo at all.

   So the photo layer is now high — 85% — and the gradient above it does all
   the real work of protecting the text. Effective photo strength lands
   around 24% at the top and 10% at the bottom. */
.card--photo {
  position: relative;
  overflow: hidden;

  /* The twelve card photos come to about 1.3MB, and every one of them was
     being fetched on page load — including the eight or nine below the fold
     that most visitors never scroll to. A background image cannot carry
     loading="lazy", so the usual fix does not apply.

     content-visibility: auto tells the browser it may skip all rendering
     work for this element while it is off screen, and skipped subtrees do
     not fetch their background images until they are approached. The images
     still arrive, just as the reader reaches them.

     contain-intrinsic-size is what makes this safe. Without it a skipped
     card reports zero height, the page collapses, and the scrollbar jumps
     as cards render. The value is a placeholder height held until the card
     has been laid out once; the auto keyword then remembers the real height
     and reuses it, so the guess only ever matters on first paint.

     Unsupported browsers ignore both lines and load exactly as before. */
  content-visibility: auto;
  contain-intrinsic-size: auto 420px;
}
.card--photo::before {
  content: "";
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0.85;
  transition: opacity 0.25s var(--ease), transform 0.4s var(--ease);
}

/* Paths live here rather than in a --card-img custom property on the HTML.
   url() inside a custom property does not resolve reliably — some browsers
   resolve it against the stylesheet rather than the document, which turns
   assets/img/... into assets/css/assets/img/... and silently loads nothing.
   Declared here, the paths are relative to this file and unambiguous. */
.card--apparel::before   { background-image: url('../img/gallery/apparel-champ-hoodies.webp'); }
.card--embroidery::before{ background-image: url('../img/gallery/cap-snake.webp'); }
.card--uniforms::before  { background-image: url('../img/gallery/uniforms-venom.webp'); }
.card--promo::before     { background-image: url('../img/gallery/promo-challenge-coin.webp'); }
.card--print::before     { background-image: url('../img/gallery/print-business-cards.webp'); }
/* Sponsor banner at a golf outing. Better here than the vehicle wrap: it is
   unmistakably large format, it is outdoors in weather, and Blue Line Crafts
   is one of the logos on it — the shop's own name on its own work. The
   vehicle photo stays in the gallery. */
.card--signage::before   { background-image: url('../img/gallery/signage-banner-golf.webp'); }
.card--labels::before    { background-image: url('../img/gallery/print-roll-labels.webp?v=2'); }

/* The team stores source has a navy "TEAM STORES" banner across the top,
   which would sit directly behind the card's own heading and make both
   unreadable. Cropped out here with background-position rather than by
   editing the file: pushing the frame down past 100% keeps only the lower
   part of the image, which is the device mockup. */
/* Bob recropped the source with the TEAM STORES banner removed, so the CSS
   crop that was here is gone. Cropping in the browser was always the weaker
   option — it scaled the image up to push the banner out of frame, costing
   sharpness and cutting the sides, and it had to be retuned for every card
   shape. A correctly cropped file needs none of that.

   Different filename, so it also sidesteps the poisoned cache entries
   entirely rather than relying on a version string. */
.card--stores::before    { background-image: url('../img/gallery/stores-team-stores-cropped.webp'); }

/* Home page. These three carried numbered blue squares, left over from when
   the services page did. The numbers implied an order that does not exist —
   they are three offerings, not three steps, and the How it works section
   directly below is the part that is actually sequential. Removed. */
.card--home-uniforms::before { background-image: url('../img/gallery/uniforms-jersey-sample.webp?v=2'); }
.card--home-spirit::before   { background-image: url('../img/gallery/spiritwear-cardinals.webp'); }
.card--home-stores::before   { background-image: url('../img/gallery/stores-team-stores-cropped.webp'); }

/* Uniform accessories */
.card--hats::before      { background-image: url('../img/gallery/cap-chief.webp'); }
.card--socks::before     { background-image: url('../img/gallery/acc-socks.webp'); }
.card--belts::before     { background-image: url('../img/gallery/acc-belt.webp'); }
.card--bags::before      { background-image: url('../img/gallery/acc-bat-bag.webp?v=2'); }
/* Lightest at the top, where the heading is — a bright heading can take it.
   Darkens toward the bottom, where the small muted list text lives.

   Checked against the worst case rather than the average: a pure white pixel
   in a photo. Even there the darkest text on the card clears 5.5:1, so no
   photo can push any card below AA no matter what gets swapped in later. */
.card--photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(160deg,
              rgba(21,24,29,0.72) 0%,
              rgba(21,24,29,0.80) 45%,
              rgba(21,24,29,0.88) 100%);
}
/* Everything in the card has to sit above both layers */
.card--photo > * { position: relative; z-index: 1; }

/* The bullet lists use --text-muted elsewhere, which is deliberately faint.
   Over a photo that faintness is what fails contrast first and it is the
   single thing capping how visible the photo is allowed to be. One step
   brighter costs nothing on a plain card and buys the photo real presence. */
.card--photo ul li { color: var(--text-soft); }

.card--photo:hover::before {
  opacity: 1;
  transform: scale(1.04);
}

.card ul { margin: var(--s-4) 0 0; padding-left: var(--s-5); }
.card ul li { font-size: 0.94rem; color: var(--text-muted); }

/* --------------------------------------------------------- Steps (1-2-3) -- */

.steps { counter-reset: step; }
.step { position: relative; padding-left: var(--s-8); }
.step::before {
  counter-increment: step;
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--blue);
  color: var(--white);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 1.05rem;
}
.step h3 { margin-bottom: var(--s-2); font-size: 1.12rem; }
.step p { margin: 0; color: var(--text-muted); font-size: 0.96rem; }

/* ------------------------------------------------------------ Gallery -- */

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  justify-content: center;
  margin-bottom: var(--s-7);
}
.filter-btn {
  padding: var(--s-2) var(--s-5);
  font-size: 0.92rem;
  font-family: inherit;
  font-weight: 500;
  color: var(--text-soft);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  transition: all 0.15s var(--ease);
}
.filter-btn:hover { border-color: var(--blue-bright); color: var(--blue-bright); }
.filter-btn.is-active {
  background: var(--blue);
  border-color: var(--blue);
  color: var(--white);
}

.gallery-item {
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--surface);
}
.gallery-item[hidden] { display: none; }

/* Masonry, via CSS columns.

   The work here is mixed orientation — badges and tumblers are tall, caps
   and banners are wide. Any single fixed tile ratio crops one group or the
   other badly: a square cut the eagle off the badge, and 4:5 would slice
   the peak off every cap. Letting each tile keep its own proportions means
   nothing is ever cropped, and an uneven grid reads as a portfolio rather
   than a spreadsheet.

   columns rather than grid because CSS grid still has no real masonry in
   shipping browsers. The tradeoff is that columns order top-to-bottom
   within each column instead of left-to-right, which does not matter for
   a gallery with no reading order. */
.gallery-masonry {
  columns: 3;
  column-gap: var(--s-5);
}
.gallery-masonry .gallery-item {
  break-inside: avoid;
  margin: 0 0 var(--s-5);
  display: inline-block;
  width: 100%;
}

.gallery-thumb {
  background: var(--n-850);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  font-size: 0.85rem;
  text-align: center;
  overflow: hidden;
}
/* Placeholders have no image to set their height, so they need a ratio */
.gallery-thumb--empty {
  aspect-ratio: 4 / 5;
  padding: var(--s-4);
}

.gallery-thumb img {
  width: 100%;
  height: auto;
  display: block;
  transition: transform 0.4s var(--ease);
}
.gallery-item:hover .gallery-thumb img { transform: scale(1.03); }

@media (max-width: 900px) { .gallery-masonry { columns: 2; } }
@media (max-width: 620px) { .gallery-masonry { columns: 1; } }

.gallery-meta { padding: var(--s-4) var(--s-5); }
.gallery-meta h4 { margin: 0 0 var(--s-1); font-size: 1rem; }
.gallery-meta span { font-size: 0.85rem; color: var(--text-muted); }

/* --------------------------------------------------------------- Forms -- */

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-5);
}

.field { display: flex; flex-direction: column; }
.field--full { grid-column: 1 / -1; }

.field label {
  font-size: 0.9rem;
  font-weight: 600;
  margin-bottom: var(--s-2);
  color: var(--text-soft);
}
.field .req { color: var(--danger); }
.field .hint {
  font-size: 0.82rem;
  color: var(--text-muted);
  font-weight: 400;
  margin-top: var(--s-1);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: var(--s-3) var(--s-4);
  font-size: 1rem;
  font-family: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-2);
  border-radius: var(--radius);
  transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease),
              background 0.15s var(--ease);
}
.field input::placeholder,
.field textarea::placeholder { color: var(--n-400); }

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  background: var(--surface-2);
  border-color: var(--blue-bright);
  box-shadow: 0 0 0 3px rgba(92,141,255,0.25);
}
.field textarea { resize: vertical; min-height: 130px; }

/* Date and file inputs render their own controls — force them light-on-dark */
.field input[type="date"] { color-scheme: dark; }
.field input[type="file"] {
  padding: var(--s-3);
  color: var(--text-muted);
  font-size: 0.92rem;
}
.field input[type="file"]::file-selector-button {
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius: var(--radius);
  padding: var(--s-2) var(--s-4);
  margin-right: var(--s-4);
  cursor: pointer;
}
.field input[type="file"]::file-selector-button:hover {
  border-color: var(--blue-bright);
  color: var(--blue-bright);
}

.field-error {
  font-size: 0.85rem;
  color: var(--danger);
  margin-top: var(--s-2);
  display: none;
}
.field.has-error input,
.field.has-error select,
.field.has-error textarea { border-color: var(--danger); }
.field.has-error .field-error { display: block; }

.form-note {
  font-size: 0.88rem;
  color: var(--text-muted);
  margin-top: var(--s-4);
}

/* Terms shown at the point of submission rather than linked away to a
   separate page. A notice someone had to click through to is far weaker
   than one sitting directly above the thing they are agreeing to. */
.terms-note {
  background: var(--n-850);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--s-4) var(--s-5);
  margin-bottom: var(--s-4);
}
.terms-note h4 {
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-3);
}
.terms-note p {
  font-size: 0.86rem;
  line-height: 1.6;
  color: var(--text-soft);
  margin-bottom: var(--s-3);
}
.terms-note p:last-child { margin-bottom: 0; }

/* Restricted brands. Columns rather than one long stack — eleven names in a
   single file would push the paragraph below it off the screen, and the list
   is meant to be scanned for a name rather than read through. */
.terms-brands {
  columns: 3;
  column-gap: var(--s-5);
  list-style: none;
  padding: 0;
  margin: 0 0 var(--s-4);
}
.terms-brands li {
  margin-bottom: var(--s-2);
  padding-left: var(--s-4);
  position: relative;
  font-size: 0.88rem;
  color: var(--text-soft);
  break-inside: avoid;
}
.terms-brands li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.6em;
  width: 8px;
  height: 2px;
  background: var(--blue-bright);
}

@media (max-width: 640px) { .terms-brands { columns: 2; } }
@media (max-width: 380px) { .terms-brands { columns: 1; } }

/* Checkbox row. The whole label is clickable, and the box does not shrink
   when the text beside it wraps to a second line. */
.check {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  cursor: pointer;
  font-size: 0.94rem;
  font-weight: 500;
  color: var(--text-soft);
  line-height: 1.5;
}
.check input[type="checkbox"] {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  margin: 2px 0 0;
  accent-color: var(--blue);
  cursor: pointer;
}
.field.has-error .check { color: var(--danger); }
.field.has-error .check input[type="checkbox"] { outline: 2px solid var(--danger); outline-offset: 2px; }

.form-status {
  padding: var(--s-4) var(--s-5);
  border-radius: var(--radius);
  margin-bottom: var(--s-5);
  font-weight: 500;
  display: none;
  border: 1px solid transparent;
}
.form-status.is-visible { display: block; }
.form-status--ok {
  background: var(--blue-tint);
  border-color: var(--blue);
  color: var(--blue-bright);
}
.form-status--err {
  background: var(--danger-bg);
  border-color: var(--danger);
  color: var(--danger);
}

/* ------------------------------------------------------- Contact info -- */

.info-list { list-style: none; padding: 0; margin: 0; }
.info-list li {
  display: flex;
  gap: var(--s-4);
  padding: var(--s-4) 0;
  border-bottom: 1px solid var(--border);
  margin: 0;
}
.info-list li:last-child { border-bottom: 0; }
.info-list dt,
.info-list .label {
  font-weight: 600;
  font-size: 0.88rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  min-width: 90px;
  flex-shrink: 0;
}

.hours-table { width: 100%; border-collapse: collapse; font-size: 0.96rem; }
.hours-table th,
.hours-table td {
  text-align: left;
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--border);
}
.hours-table th { font-weight: 600; color: var(--text-soft); }
.hours-table td { color: var(--text-muted); text-align: right; }
.hours-table tr:last-child th,
.hours-table tr:last-child td { border-bottom: 0; }

/* ----------------------------------------------------------- FAQ / details -- */

.faq details {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--s-4) var(--s-5);
  margin-bottom: var(--s-3);
  background: var(--surface);
  transition: border-color 0.15s var(--ease);
}
.faq details:hover { border-color: var(--border-2); }
.faq details[open] { border-color: var(--border-2); background: var(--surface-2); }
.faq summary {
  font-weight: 600;
  cursor: pointer;
  list-style: none;
  position: relative;
  padding-right: var(--s-6);
  color: var(--text);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+";
  position: absolute;
  right: 0;
  top: -2px;
  font-size: 1.35rem;
  font-weight: 400;
  color: var(--blue-bright);
  line-height: 1;
}
.faq details[open] summary::after { content: "\2013"; }
.faq details p { margin: var(--s-4) 0 0; color: var(--text-soft); }
.faq details p:last-child { margin-bottom: 0; }

/* ----------------------------------------------------------- CTA band -- */

.cta-band {
  background: var(--blue-deep);
  color: var(--text);
  padding: var(--s-8) 0;
  text-align: center;
  position: relative;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.cta-band::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 4px;
  background: var(--blue-bright);
}
.cta-band h2 { color: var(--white); }
.cta-band p {
  color: var(--n-100);
  font-size: 1.1rem;
  max-width: 56ch;
  margin: 0 auto var(--s-6);
}
.cta-band .btn--ghost {
  background: transparent;
  color: var(--white);
  border-color: rgba(255,255,255,0.38);
}
.cta-band .btn--ghost:hover {
  background: var(--white);
  color: var(--blue-deep);
  border-color: var(--white);
}

/* ------------------------------------------------------------- Footer -- */

.site-footer {
  background: var(--n-850);
  border-top: 1px solid var(--border);
  padding: var(--s-7) 0 var(--s-5);
  font-size: 0.94rem;
}

.footer-grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr 1fr;
  gap: var(--s-7);
  margin-bottom: var(--s-6);
}

.footer-logo {
  display: inline-block;
  margin-bottom: var(--s-4);
  opacity: 0.9;
  transition: opacity 0.18s var(--ease);
}
.footer-logo:hover { opacity: 1; }
.footer-logo img {
  width: 100%;
  max-width: 280px;
  height: auto;
  padding: 5% 4%;
  background: var(--logo-pool);
}

.site-footer h4 {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  margin-bottom: var(--s-4);
}
.site-footer ul { list-style: none; padding: 0; margin: 0; }
.site-footer li { margin-bottom: var(--s-2); }
.site-footer a { color: var(--text-soft); }
.site-footer a:hover { color: var(--blue-bright); }
.site-footer p { color: var(--text-muted); margin-bottom: var(--s-3); }

.footer-bottom {
  border-top: 1px solid var(--border);
  padding-top: var(--s-5);
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s-3);
  color: var(--text-muted);
  font-size: 0.88rem;
}

/* ---------------------------------------------------------------- 404 -- */

.error-page {
  min-height: 55vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--s-9) var(--s-5);
}
.error-code {
  font-size: 5rem;
  font-weight: 800;
  color: var(--blue-bright);
  line-height: 1;
  margin-bottom: var(--s-4);
}

/* --------------------------------------------------------- Responsive -- */

@media (max-width: 900px) {
  .hero-inner { grid-template-columns: 1fr; gap: var(--s-6); }
  .hero { padding: var(--s-8) 0; }
  .grid--4 { grid-template-columns: repeat(2, 1fr); }
  .footer-grid { grid-template-columns: 1fr 1fr; gap: var(--s-6); }
}

/* The horizontal bar needs roughly 790px of viewport before the links start
   being squeezed, and 768px left a band where the nav was cramped but still
   horizontal — hence 860px.

   Adding Apparel as a seventh item costs another ~80px, which pushed the
   requirement past that threshold and reopened the same cramped band. Raised
   to 940px. If another link is ever added, this number has to move again;
   the honest limit is about six links plus a button before a horizontal bar
   stops working on a laptop. */
@media (max-width: 940px) {
  .nav-toggle { display: block; }

  .site-header { position: relative; }

  .nav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: var(--s-1);
    background: var(--n-850);
    border-bottom: 1px solid var(--border);
    padding: var(--s-3);
    box-shadow: var(--shadow-lg);
  }

  .nav.is-open { display: flex; }

  /* Every row gets identical padding and alignment. Previously the links
     sat flush with gap 0 while the CTA carried its own top margin, and the
     CTA was centered while the links were left aligned, so no two rows lined
     up the same way. */
  .nav a {
    padding: var(--s-3) var(--s-4);
    font-size: 1rem;
    border-radius: var(--radius);
    text-align: left;
  }

  /* The CTA sits below a divider so it reads as separate from the list
     rather than as a misaligned member of it. Its 2px button border would
     otherwise make it taller than every link, so the vertical padding drops
     by that much to match. */
  .nav .btn {
    margin: var(--s-3) 0 0;
    padding: calc(var(--s-3) - 2px) var(--s-4);
    line-height: 1.65;
    text-align: center;
    border-radius: var(--radius);
  }

  /* In the mobile menu a dropdown becomes an inline accordion rather than a
     floating panel — there is nothing to float over, and a panel positioned
     absolutely would sit on top of the links beneath it. */
  .nav-item { width: 100%; }
  .nav-sub-toggle {
    width: 100%;
    justify-content: space-between;
    padding: var(--s-3) var(--s-4);
    font-size: 1rem;
  }
  .nav-sub {
    position: static;
    min-width: 0;
    margin: var(--s-1) 0 var(--s-2) var(--s-4);
    padding: 0 0 0 var(--s-3);
    background: none;
    border: 0;
    border-left: 2px solid var(--border-2);
    border-radius: 0;
    box-shadow: none;
  }
  .nav-sub a { padding: var(--s-3) var(--s-4); font-size: 0.96rem; }

  .nav a.btn { position: relative; }

  .nav a.btn::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: calc(var(--s-3) / -2 - 1px);
    height: 1px;
    background: var(--border);
  }
}

@media (max-width: 768px) {
  body { font-size: 16px; }

  .grid--2, .grid--3, .grid--4 { grid-template-columns: 1fr; }
  .form-grid { grid-template-columns: 1fr; }
  .footer-grid { grid-template-columns: 1fr; }

  .section { padding: var(--s-7) 0; }
  .hero-points { grid-template-columns: repeat(2, 1fr); gap: var(--s-3); }
  .btn-row .btn { width: 100%; }
}

@media (max-width: 560px) {
  /* Below this width the mark plus the text lockup crowds the menu button.
     The mark carries the brand on its own, so the text steps aside. */
  .logo-mark { height: 62px; }
}

/* Below this, two columns leaves under 190px per label and most of them
   wrap. One full-width column is cleaner than two cramped ones. */
@media (max-width: 580px) {
  .hero-points { grid-template-columns: 1fr; gap: var(--s-2); }
}

/* The lockup needs about 316px alongside the hamburger. Everything down to
   a 360px phone clears that; below it the text has to go so the mark and
   the menu button are not squeezed. */
@media (max-width: 350px) {
  .logo-text { display: none; }
}

@media (max-width: 420px) {
  .container { padding: 0 var(--s-4); }
  .logo-mark { height: 56px; }
}

/* -------------------------------------------------------------- Print -- */
/* Force light for print — nobody wants a black page coming out of a printer */

@media print {
  html, body { background: #FFF; color: #000; }
  .site-header, .site-footer, .cta-band, .nav-toggle, .btn { display: none; }
  body { font-size: 12pt; }
  h1, h2, h3, h4, p, li, td, th, summary { color: #000 !important; }
  .card, .faq details { background: #FFF; border: 1px solid #CCC; }
  a { color: #000; text-decoration: underline; }
}

/* ------------------------------------------------- Reduced motion -- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------------------------------------------------------- Design lab -- */

.dl-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: var(--s-6);
  align-items: start;
}
@media (max-width: 1000px) {
  .dl-layout { grid-template-columns: 1fr; gap: var(--s-6); }
}

/* Stage sticks while the long right-hand panel scrolls past it. */
.dl-stage { position: sticky; top: 90px; }
@media (max-width: 1000px) { .dl-stage { position: static; } }

.dl-canvas-wrap {
  background: #FFFFFF;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  line-height: 0;
}
#dl-canvas {
  width: 100%; height: auto; display: block;
  touch-action: none;                 /* let us handle drag on touch */
  cursor: grab;
}
#dl-canvas.is-dragging { cursor: grabbing; }
#dl-canvas.is-loading { opacity: 0.45; }
#dl-canvas.is-drop { outline: 3px dashed var(--blue); outline-offset: -6px; }

.dl-status { min-height: 1.2em; margin: var(--s-3) 0 0; color: var(--danger); font-size: 0.95rem; }
.dl-hint { font-size: 0.92rem; margin: var(--s-2) 0 0; }

.dl-stage-tools {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s-4); margin-top: var(--s-4);
}
.dl-sides { display: inline-flex; border: 1px solid var(--border-2); border-radius: var(--radius); overflow: hidden; }
.dl-sides button {
  appearance: none; border: 0; background: transparent; color: var(--text-soft);
  font: inherit; font-size: 0.95rem; font-weight: 600; padding: var(--s-2) var(--s-5); cursor: pointer;
}
.dl-sides button.is-active { background: var(--blue); color: #FFFFFF; }
.dl-check { display: inline-flex; align-items: center; gap: var(--s-2); font-size: 0.95rem; color: var(--text-soft); cursor: pointer; }

.dl-panel { display: grid; gap: var(--s-6); }
.dl-block {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--s-5) var(--s-5) var(--s-6);
}
.dl-block h2 { font-size: 1.12rem; margin-bottom: var(--s-4); }

/* [hidden] MUST WIN.
   The browser's own rule is `[hidden] { display: none }`, an attribute
   selector, which loses to any class that sets display. Several blocks here
   do - .dl-products and .dl-filters are both `display: grid` - so setting
   el.products.hidden = true ran, set the attribute, and changed nothing on
   screen. Single-garment mode looked broken: it showed the garment name AND
   an empty Category/Fit filter pair AND the product card underneath.
   The JS was correct the whole time. This is the fix, and it is global
   because the same trap is set for every class in this file that sets
   display and is ever toggled with .hidden. */
[hidden] { display: none !important; }

.dl-products { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: var(--s-3); }
.dl-product {
  appearance: none; text-align: left; cursor: pointer; font: inherit;
  background: var(--surface-2); border: 2px solid transparent; border-radius: var(--radius);
  padding: var(--s-3); display: grid; gap: 2px;
}
.dl-product:hover { border-color: var(--border-2); }
.dl-product.is-active { border-color: var(--blue); }
.dl-product-img {
  display: block; aspect-ratio: 1; border-radius: 6px; background: #FFFFFF center/contain no-repeat;
  margin-bottom: var(--s-2);
}
.dl-product-name { font-size: 0.9rem; font-weight: 600; color: var(--text); line-height: 1.3; }
.dl-product-meta { font-size: 0.78rem; color: var(--text-muted); }

.dl-color-name { font-size: 0.95rem; color: var(--text-soft); margin-bottom: var(--s-3); min-height: 1.2em; }
.dl-swatches { display: flex; flex-wrap: wrap; gap: 7px; max-height: 210px; overflow-y: auto; }
.dl-swatch {
  width: 30px; height: 30px; border-radius: 50%; cursor: pointer; padding: 0;
  border: 2px solid var(--border-2); background: #CCC center/cover no-repeat;
}
.dl-swatch:hover { border-color: var(--text-muted); }
.dl-swatch.is-active { border-color: var(--blue-bright); box-shadow: 0 0 0 2px var(--blue); }

.dl-note {
  margin: var(--s-4) 0 0; padding: var(--s-3) var(--s-4);
  background: var(--surface-2); border-left: 3px solid var(--blue);
  border-radius: 0; font-size: 0.92rem; color: var(--text-soft);
}

.dl-upload {
  display: grid; gap: 3px; justify-items: center; text-align: center; cursor: pointer;
  padding: var(--s-6) var(--s-4);
  border: 2px dashed var(--border-2); border-radius: var(--radius);
  transition: border-color 0.18s var(--ease), background 0.18s var(--ease);
}
.dl-upload:hover { border-color: var(--blue); background: var(--surface-2); }
.dl-upload strong { color: var(--blue-bright); }
.dl-upload span { font-size: 0.92rem; color: var(--text-soft); }
.dl-upload small { font-size: 0.82rem; color: var(--text-muted); }

.dl-art { margin-top: var(--s-5); display: grid; gap: var(--s-4); }
.dl-art-name { font-size: 0.88rem; color: var(--text-muted); margin: 0; word-break: break-all; }
.dl-slider { display: grid; grid-template-columns: 74px 1fr; align-items: center; gap: var(--s-3); font-size: 0.92rem; color: var(--text-soft); }
.dl-slider input[type="range"] { width: 100%; }
.dl-art-actions { display: flex; flex-wrap: wrap; gap: var(--s-2); }

.dl-sizes { display: grid; grid-template-columns: repeat(auto-fill, minmax(74px, 1fr)); gap: var(--s-3); }
.dl-size { display: grid; gap: 4px; font-size: 0.86rem; color: var(--text-muted); text-align: center; }
.dl-size input {
  width: 100%; text-align: center; padding: var(--s-2);
  background: var(--surface-2); border: 1px solid var(--border-2);
  border-radius: 6px; color: var(--text); font: inherit;
}
.dl-size input:focus { outline: 2px solid var(--blue); outline-offset: 1px; }
.dl-qty { margin: var(--s-4) 0 0; font-weight: 600; color: var(--text); }

.btn--sm { padding: var(--s-2) var(--s-4); font-size: 0.9rem; }
.dl-placement-note { margin: var(--s-3) 0 0; font-size: 0.95rem; font-weight: 600; color: var(--blue-bright); }

.dl-filters { display: grid; gap: var(--s-3); margin-bottom: var(--s-4); }
.dl-filter-row { display: block; }
.dl-filter-label { display: block; margin-bottom: 6px;
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-muted); }
.dl-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.dl-chip {
  appearance: none; cursor: pointer; font: inherit; font-size: 0.86rem; font-weight: 600;
  padding: 5px 12px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border-2); color: var(--text-soft);
}
.dl-chip:hover { border-color: var(--text-muted); color: var(--text); }
.dl-chip.is-active { background: var(--blue); border-color: var(--blue); color: #FFFFFF; }
.dl-count { font-size: 0.86rem; color: var(--text-muted); margin: 0 0 var(--s-3); }

/* Shown instead of the picker when the lab was opened on one style from the
   shop. It replaces a scrolling list, so it can afford to be a block. */
.dl-single { display: grid; gap: 2px; margin: 0; }
.dl-single-brand {
  font-size: 0.74rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-muted);
}
.dl-single-name { font-size: 1.02rem; font-weight: 650; line-height: 1.25; }
.dl-single-style { font-size: 0.84rem; color: var(--text-muted); }
.dl-single-back { font-size: 0.84rem; margin-top: 6px; justify-self: start; }

.dl-art-list { display: grid; gap: 6px; }
.dl-art-row {
  display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: 8px;
  padding: 6px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface-2);
}
.dl-art-row.is-active { border-color: var(--blue); }
.dl-art-pick {
  appearance: none; border: 0; background: transparent; cursor: pointer; font: inherit;
  display: grid; grid-template-columns: 34px 1fr; align-items: center; gap: 8px;
  text-align: left; padding: 0; min-width: 0;
}
.dl-art-thumb {
  width: 34px; height: 34px; border-radius: 5px;
  background: #FFFFFF center/contain no-repeat; border: 1px solid var(--border-2);
}
.dl-art-label { min-width: 0; }
.dl-art-label strong {
  display: block; font-size: 0.86rem; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dl-art-label small { font-size: 0.76rem; color: var(--text-muted); text-transform: capitalize; }
.dl-art-place {
  font: inherit; font-size: 0.82rem; padding: 4px 6px; border-radius: 6px;
  background: var(--surface); border: 1px solid var(--border-2); color: var(--text);
}
.dl-art-del {
  appearance: none; cursor: pointer; font: inherit; font-size: 1.1rem; line-height: 1;
  width: 26px; height: 26px; border-radius: 50%;
  background: transparent; border: 1px solid var(--border-2); color: var(--text-muted);
}
.dl-art-del:hover { border-color: var(--danger); color: var(--danger); }
.dl-slider { grid-template-columns: 74px 1fr 46px; }
.dl-slider output { font-size: 0.86rem; color: var(--text); text-align: right; font-variant-numeric: tabular-nums; }
.dl-slider-hint { font-size: 0.8rem; color: var(--text-muted); margin: -4px 0 0 78px; }
.dl-art-actions { align-items: center; }
.dl-sides button.has-upcharge::after { content: ' +'; font-weight: 700; opacity: 0.75; }

.dl-gate {
  margin-top: var(--s-4); padding: var(--s-5);
  background: var(--surface); border: 2px solid var(--blue);
  border-radius: var(--radius-lg);
}
.dl-gate h3 { font-size: 1.05rem; margin-bottom: var(--s-3); }
.dl-gate p { font-size: 0.95rem; color: var(--text-soft); margin-bottom: var(--s-4); }
.dl-gate .check { margin-bottom: var(--s-3); }

.dl-warn {
  margin-top: var(--s-4); padding: var(--s-4);
  background: var(--danger-bg); border-left: 3px solid var(--danger);
  display: grid; gap: var(--s-3); justify-items: start;
}
.dl-warn p { margin: 0; font-size: 0.95rem; color: var(--text); }

/* ---------------------------------------------------------------- shop --
   The full catalog under our own roof. Tiles are buttons rather than links
   because the detail opens in place - 3,000 real URLs would be nice for SEO
   but the data is a vendor feed we rebuild, not pages we own. */

/* The grid gap is the ONLY spacing between these three rows. Their own
   margins were adding to it - 1rem of gap plus 0.75rem of margin on each of
   the count row and the filter row - so every gap was running at nearly
   twice what it looked like it should be. */
.shop-tools { display: grid; gap: var(--s-2); margin-bottom: var(--s-4); }
.shop-search { display: block; width: 100%; max-width: 460px; margin: 0 auto; }
.shop-search input {
  width: 100%; padding: var(--s-3) var(--s-4);
  font-size: 1rem; font-family: inherit; color: var(--text);
  background: var(--surface); border: 1px solid var(--border-2);
  border-radius: var(--radius);
}
.shop-search input:focus {
  outline: none; background: var(--surface-2); border-color: var(--blue-bright);
  box-shadow: 0 0 0 3px rgba(92,141,255,0.25);
}
.shop-filter-rows { display: grid; gap: var(--s-3); }
.shop-filter-row { display: flex; align-items: flex-start; gap: var(--s-3); flex-wrap: wrap; }
.shop-filter-label {
  flex: 0 0 auto; min-width: 68px; padding-top: 5px;
  font-size: 0.78rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-muted); font-weight: 700;
}
.shop-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.shop-chip {
  appearance: none; cursor: pointer; font: inherit; font-size: 0.88rem;
  padding: 5px 12px; border-radius: 999px;
  border: 1px solid var(--border-2); background: var(--surface-2);
  color: var(--text-soft);
}
.shop-chip:hover { border-color: var(--text-muted); }
.shop-chip.is-active { background: var(--blue); border-color: var(--blue); color: #FFF; }
.shop-select {
  font: inherit; font-size: 0.9rem; padding: 5px 10px;
  color: var(--text); background: var(--surface-2);
  border: 1px solid var(--border-2); border-radius: var(--radius);
}
.shop-count { font-size: 0.9rem; color: var(--text-muted); text-align: center; margin: 0; }

.shop-grid {
  display: grid; gap: var(--s-4);
  grid-template-columns: repeat(auto-fill, minmax(178px, 1fr));
}
.shop-tile {
  appearance: none; font: inherit; text-align: left; cursor: pointer;
  display: grid; gap: 3px; align-content: start; padding: var(--s-3);
  background: var(--surface-2); border: 2px solid transparent;
  border-radius: var(--radius); color: var(--text);
}
.shop-tile:hover { border-color: var(--blue); }
.shop-tile-img {
  display: block; aspect-ratio: 1; border-radius: 6px; overflow: hidden;
  background: #FFF; margin-bottom: var(--s-2);
}
.shop-tile-img img { width: 100%; height: 100%; object-fit: contain; display: block; }
.shop-tile-name { font-size: 0.9rem; font-weight: 600; line-height: 1.3; }
.shop-tile-meta { font-size: 0.78rem; color: var(--text-muted); }
.shop-empty, .shop-loading { text-align: center; color: var(--text-muted); padding: var(--s-6) 0; }
.shop-more-wrap { display: flex; justify-content: center; margin-top: var(--s-6); }

.shop-detail {
  position: fixed; inset: 0; z-index: 200; display: grid; place-items: center;
  background: rgba(6,8,11,0.82); padding: var(--s-4); overflow-y: auto;
}
.shop-detail[hidden] { display: none; }
.shop-detail-inner {
  /* Wide enough that a full size run with four-digit quantities stays on one
     line - at 980px an 8-size garment with high stock wrapped. */
  position: relative; width: min(1140px, 100%); max-height: 92vh; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border-2);
  border-radius: var(--radius); padding: var(--s-6);
}
.shop-close {
  position: absolute; top: 8px; right: 12px; appearance: none; border: 0;
  background: none; color: var(--text-muted); font-size: 1.9rem; line-height: 1;
  cursor: pointer;
}
.shop-close:hover { color: var(--text); }
/* align-items:start matters more than it looks. Grid stretches both columns
   to the tallest, so the white photo panel grew to match the info column and
   a small cap photo ended up floating in four times its own area of white.
   Now the panel is only as tall as the picture in it. */
.shop-detail-grid { display: grid; gap: var(--s-6); grid-template-columns: 1fr; align-items: start; }
@media (min-width: 760px) { .shop-detail-grid { grid-template-columns: 330px 1fr; } }
.shop-detail-media { background: #FFF; border-radius: var(--radius); padding: var(--s-4); }
.shop-detail-media img {
  width: 100%; height: auto; display: block;
  max-height: 58vh; object-fit: contain;
}
.shop-detail-meta { font-size: 0.9rem; color: var(--text-muted); margin: 2px 0 var(--s-3); }
.shop-detail-desc { font-size: 0.95rem; line-height: 1.6; color: var(--text-soft); }
.shop-sub {
  font-size: 0.78rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-muted); margin: var(--s-5) 0 var(--s-2);
}
.shop-dim { font-size: 0.82rem; color: var(--text-muted); font-weight: 400;
  text-transform: none; letter-spacing: 0; }
.shop-swatches { display: flex; flex-wrap: wrap; gap: 7px; }
.shop-swatch {
  width: 30px; height: 30px; border-radius: 50%; padding: 0; cursor: pointer;
  border: 2px solid var(--border-2); background: #CCC center/cover no-repeat;
}
.shop-swatch:hover { border-color: var(--text-muted); }
.shop-swatch.is-active { border-color: var(--blue-bright); box-shadow: 0 0 0 2px var(--blue); }
.shop-sizes { display: flex; flex-wrap: wrap; gap: 6px; }
.shop-size {
  font-size: 0.85rem; padding: 4px 10px; border-radius: var(--radius);
  background: var(--surface-2); border: 1px solid var(--border-2); color: var(--text-soft);
}
/* Amber for low, struck through for out - color alone is not enough. */
.shop-size.is-low { border-color: #B45309; color: #F59E0B; }
.shop-size.is-out { opacity: 0.45; text-decoration: line-through; }
.shop-key { display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-size: 0.8rem; color: var(--text-muted); margin: var(--s-2) 0 0; }
.shop-cta { display: flex; flex-wrap: wrap; gap: var(--s-3); margin: var(--s-5) 0 var(--s-3); }
body.is-locked { overflow: hidden; }
/* Size chips carry an on-hand count under the label. */
.shop-size { display: inline-grid; justify-items: center; line-height: 1.2; }
.shop-size em { font-style: normal; font-size: 0.72rem; color: var(--text-muted); }
.shop-size.is-low em { color: #F59E0B; }
/* Legend chips: same styling as a real size box so the meaning is obvious,
   minus the quantity line that would make them taller than the text. */
.shop-size--key { padding: 2px 7px; font-size: 0.78rem; }
.shop-size--key em { display: none; }
.shop-key-item { display: inline-flex; align-items: center; gap: 5px; margin-left: var(--s-3); }

/* Product panel: brand leads, style number is a thing people quote back to
   us on the phone so it is set large, and the category rides on the photo
   rather than trailing the meta line. */
.shop-detail-brand {
  font-size: 0.8rem; font-weight: 800; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--blue-bright); margin: 0 0 2px;
}
.shop-detail-style {
  font-size: 1.15rem; font-weight: 700; color: var(--text);
  margin: var(--s-3) 0 0;
}
.shop-detail-style span {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-muted);
  display: block; margin-bottom: 1px;
}
.shop-detail-media { position: relative; }
.shop-detail-cat {
  font-size: 0.9rem; color: var(--text-muted); margin: 2px 0 var(--s-3);
}

/* Grid tiles mirror the product panel: brand leads, style number is set
   large enough to read at a glance and quote back to us. */
.shop-tile-brand {
  font-size: 0.72rem; font-weight: 800; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--blue-bright);
}
.shop-tile-style {
  font-size: 0.95rem; font-weight: 700; color: var(--text); margin-top: 1px;
}

/* Grid: broad category on the photo, amber outline where no color can be
   had in a full core size run. Amber rather than hidden - Bob would rather a
   customer see the style and skip it than never see it. */
.shop-tile-img { position: relative; }
/* Plain black on the white photo. A filled chip drew more attention than a
   category label deserves, and the photos all have white margin anyway. */
.shop-tile-img .shop-badge {
  position: absolute; top: 5px; left: 6px; z-index: 1;
  background: none; color: #1A1D21;
  font-size: 0.58rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; padding: 0;
}
.shop-tile.is-thin { border-color: #B45309; }
.shop-tile.is-thin:hover { border-color: #F59E0B; }
.shop-tile-warn {
  font-size: 0.72rem; font-weight: 700; color: #F59E0B;
  text-transform: uppercase; letter-spacing: 0.05em;
}

/* Photo swatches, for suppliers with no flat color chip. Larger and square
   so a two-tone cap is actually legible - a 30px circle of a whole cap is a
   smudge. */
.shop-swatch--photo {
  width: 52px; height: 52px; border-radius: 6px;
  background-color: #FFF; background-size: contain;
  background-position: center; background-repeat: no-repeat;
}

/* ------------------------------------------------- print configurator -- */

/* Trade printer order-screen layout: the product on the left, the stack of
   decisions on the right. Below 900px the image goes on top, because a
   configurator is a form and a form wants the full width. */
.pp-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: var(--s-7);
  align-items: start;
}
.pp-crumb { font-size: 0.86rem; color: var(--text-muted); margin-bottom: var(--s-5); }
.pp-media {
  background: #FFFFFF;
  border: 1px solid #D6DBE1;
  border-radius: var(--radius);
  padding: var(--s-5);
  display: flex; align-items: center; justify-content: center;
  position: sticky; top: var(--s-5);
}
.pp-media img { max-width: 100%; height: auto; display: block; }
.pp-config h1 { margin-bottom: 4px; }
.pp-spec { font-size: 0.9rem; color: var(--text-muted); margin-bottom: var(--s-3); }
.pp-blurb { margin-bottom: var(--s-6); }

.pp-opt { margin-bottom: var(--s-6); }
.pp-opt-label {
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-muted);
  margin-bottom: var(--s-3);
}
.pp-choices { display: flex; flex-wrap: wrap; gap: 8px; }
.pp-choice {
  font: inherit; font-size: 0.9rem; cursor: pointer;
  padding: 9px 14px; border-radius: var(--radius);
  border: 1px solid var(--line); background: transparent; color: var(--text);
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}
.pp-choice:hover { border-color: var(--text-muted); }
.pp-choice.is-active {
  background: var(--blue); border-color: var(--blue); color: #FFFFFF; font-weight: 600;
}
/* Sizes are a long list of similar-width chips, so they get a tighter grid
   than the two- and three-way option rows. */
.pp-choices--sizes .pp-choice { min-width: 104px; text-align: center; }

.pp-select, .pp-qty {
  font: inherit; font-size: 0.95rem;
  padding: 9px 12px; border-radius: var(--radius);
  border: 1px solid var(--line); background: var(--bg); color: var(--text);
  max-width: 320px; width: 100%;
}
.pp-qty { max-width: 140px; }
.pp-check { display: flex; align-items: center; gap: 9px; font-size: 0.95rem; cursor: pointer; }
.pp-note { font-size: 0.84rem; color: var(--text-muted); margin-top: 8px; line-height: 1.45; }

.pp-custom { display: flex; gap: var(--s-4); margin-top: var(--s-4); flex-wrap: wrap; }
.pp-custom label { display: flex; align-items: center; gap: 8px; font-size: 0.9rem; }
.pp-custom span { color: var(--text-muted); }
.pp-custom input {
  font: inherit; width: 96px; padding: 8px 10px;
  border-radius: var(--radius); border: 1px solid var(--line);
  background: var(--bg); color: var(--text);
}
.pp-custom em { font-style: normal; color: var(--text-muted); font-size: 0.86rem; }

.pp-summary {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--s-5); margin-top: var(--s-7);
}
.pp-summary h2 { font-size: 1.02rem; margin-bottom: var(--s-4); }
.pp-summary dl { margin: 0 0 var(--s-5); }
.pp-sum-row {
  display: flex; justify-content: space-between; gap: var(--s-4);
  padding: 7px 0; border-bottom: 1px solid var(--line); font-size: 0.9rem;
}
.pp-sum-row:last-child { border-bottom: 0; }
.pp-sum-row dt { color: var(--text-muted); }
.pp-sum-row dd { margin: 0; text-align: right; font-weight: 600; }

@media (max-width: 900px) {
  .pp-layout { grid-template-columns: 1fr; gap: var(--s-6); }
  .pp-media { position: static; }
}
.prod-cta { margin-top: var(--s-4); }

/* Order minimum. Deliberately loud and deliberately early — above the size
   chooser, so nobody configures a job for two minutes and then meets the
   floor at checkout. */
.pp-min {
  display: grid; gap: 3px;
  border-left: 3px solid var(--blue);
  background: rgba(0, 70, 230, 0.06);
  padding: var(--s-4) var(--s-5);
  border-radius: 0 var(--radius) var(--radius) 0;
  margin-bottom: var(--s-6);
}
.pp-min strong { font-size: 1rem; }
.pp-min span { font-size: 0.86rem; color: var(--text-muted); line-height: 1.45; }

/* Live price. Sits under the spec summary, above the send button. */
.pp-price {
  display: grid; gap: 2px;
  padding: var(--s-4) 0 var(--s-5);
  border-top: 1px solid var(--line);
  margin-bottom: var(--s-4);
}
.pp-price-total { font-size: 1.9rem; font-weight: 700; line-height: 1.1; }
.pp-price-each { font-size: 0.9rem; color: var(--text-muted); }
.pp-price-note { font-size: 0.8rem; color: var(--text-muted); line-height: 1.45; }
.pp-price-none { font-size: 0.9rem; color: var(--text-muted); line-height: 1.5; }

/* ---------------------------------------------------------- our picks -- */
/* Six recommended garments at the top of the apparel page. Deliberately not
   the brand-tile treatment: a logo is a filter, these are a recommendation,
   and they should not look like the same kind of thing. */

.section--tight { padding-bottom: var(--s-4); }

.picks-head { text-align: center; margin-bottom: var(--s-2); }
.picks-intro {
  text-align: center;
  max-width: 60ch;
  margin: 0 auto var(--s-6);
  color: var(--text-muted);
}

/* One row of six. At six across each card is narrow, so the body text has to
   shrink with it or every card becomes a column of two-word lines. */
/* Four across, two rows. Was six across when there were six picks; at eight
   that left two stranded on a second line, which is the same unbalanced look
   Bob flagged on the brand grid. Six divides twelve evenly. REVISIT THIS
   NUMBER whenever a pick is added or removed - a stranded card on its own
   line is the thing to avoid, and it is easy to forget. */
.picks-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--s-4);
}

.pick {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.pick:hover { border-color: var(--blue); transform: translateY(-2px); }

/* White product photos on a dark card need their own light plate, or the
   garment floats on a shape that does not belong to it. */
.pick-img {
  display: block;
  background: #fff;
  aspect-ratio: 1 / 1;
}
.pick-img img { width: 100%; height: 100%; object-fit: contain; }

.pick-body { padding: var(--s-3); display: flex; flex-direction: column; gap: 4px; }

.pick-tag {
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--blue-bright);
  font-weight: 600;
  margin: 0;
}
.pick-body h3 { font-size: 0.9rem; margin: 0; line-height: 1.25; }
.pick-body h3 a { color: inherit; text-decoration: none; }
.pick-body h3 a:hover { text-decoration: underline; }

/* The style number is what a customer quotes back on the phone, so it is
   monospaced and selectable rather than decorative. */
.pick-sku {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.72rem;
  color: var(--text-muted);
  margin: 0;
}
.pick-body p:last-child {
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--text-muted);
  margin: 2px 0 0;
}

.picks-foot {
  text-align: center;
  max-width: 62ch;
  margin: var(--s-6) auto 0;
  font-size: 0.9rem;
  color: var(--text-muted);
}

/* Six across only survives on a wide screen. Stepping down 6 > 3 > 2 keeps
   whole rows rather than leaving one card stranded on its own line. */
/* A LADDER, AND NEVER ONE COLUMN.
   Twelve picks at one card per row is a scroll that never ends - Bob's word
   was "overbearing", and on a phone he was looking at twelve full-width
   cards stacked. Two is the floor at every size.

   The old rule also went from six columns straight to two at 1100px, which
   made each card about 470px wide on a laptop. Twelve divides evenly by
   6, 4, 3 and 2, so every step below lands on full rows. */
@media (max-width: 1100px) {
  .picks-grid { grid-template-columns: repeat(4, 1fr); gap: var(--s-4); }
  .pick-body h3 { font-size: 1rem; }
  .pick-body p:last-child { font-size: 0.85rem; }
}
@media (max-width: 820px) {
  .picks-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 620px) {
  .picks-grid { grid-template-columns: repeat(2, 1fr); gap: var(--s-3); }
}
/* Phone. Two across on a 375px screen leaves roughly 160px per card, so the
   type has to come back down or every description becomes a column of
   two-word lines. */
@media (max-width: 460px) {
  .picks-grid { grid-template-columns: repeat(2, 1fr); gap: 8px; }
  .pick-body { padding: 10px; }
  .pick-body h3 { font-size: 0.82rem; line-height: 1.2; }
  .pick-tag { font-size: 0.6rem; letter-spacing: 0.04em; }
  .pick-sku { font-size: 0.66rem; }
  .pick-body p:last-child { font-size: 0.72rem; line-height: 1.45; }
}

/* --------------------------------------------------- brand groupings -- */
/* The grid is split in two: the lines Bob actually works with, then the
   rest. A heading rather than an unexplained outline - a mark with no stated
   meaning gets read as "on sale" and misleads. */

.brand-group-head {
  text-align: center;
  font-size: 1.05rem;
  margin: var(--s-7) 0 var(--s-2);
}
.brand-group-head:first-of-type { margin-top: var(--s-2); }

.brand-group-note {
  text-align: center;
  max-width: 58ch;
  margin: 0 auto var(--s-5);
  font-size: 0.88rem;
  color: var(--text-muted);
}

/* The second group is quieter, not hidden. These are real brands Bob is happy
   to sell and the page should not sneer at them. */
.brand-group-head--alt { color: var(--text-muted); }

/* One tile size for every brand.
   The grid used to be split in two - a 230px box for wide lockups, a 148px
   square for round marks - which meant each GROUP was two grids and the
   alphabet restarted halfway down it. Cap America to Wink, then back to
   Carhartt. One uniform tile fixes that, and lets the logos scale to fit
   rather than the frame being sized to the logo.
   Smaller than either of the old boxes: 41 brands at the old size read as
   though the brand list was the whole page. */
/* FLEX, NOT GRID, so a short last row centres.
   With 17 and 24 tiles the rows never divide evenly, and a grid leaves the
   remainder hard left - one or two logos stranded under a full row, which
   reads as a mistake rather than a layout. Flex with justify-content:center
   pushes the stragglers to the middle. The trade is that tiles no longer
   stretch to fill the row, which is why the width is fixed rather than 1fr. */
.brand-grid--all {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
}
.brand-grid--all .brand {
  flex: 0 0 132px;
  min-height: 78px;
  padding: var(--s-2);
}
.brand-grid--all .brand img { max-height: 44px; max-width: 100%; object-fit: contain; }

/* The house brands are their own container so the row breaks exactly after
   them, which means the 10px gap inside a container does not apply BETWEEN
   two of them - the two rows sat flush against each other. Matching the
   internal gap makes the break invisible: it reads as one evenly spaced
   block that happens to break where we want it to. */
.brand-grid--all + .brand-grid--all { margin-top: 10px; }

@media (max-width: 600px) {
  .brand-grid--all .brand { flex-basis: 104px; min-height: 66px; }
  .brand-grid--all .brand img { max-height: 36px; }
}

/* ------------------------------------------- design lab, single-item -- */

/* KEEP THE STAGE INSIDE THE VIEWPORT.
   .dl-stage is sticky so the preview stays put while the right column
   scrolls. That only helps if the stage actually fits on screen - the canvas
   is square and was taking the full column width, so on a 1440x900 display
   the sticky block was taller than the window and stuck to nothing.

   Constraining the WRAPPER's width, not the canvas height. The canvas is
   1:1, so capping width caps height, and the element stays fully covered by
   its drawing. Using max-height with object-fit would letterbox it, and
   design-lab.js maps pointer position with
     (clientX - rect.left) * (CANVAS_W / rect.width)
   which reads the element box, not the drawing - so letterboxing would put
   the artwork under the cursor by however wide the bars were. */
.dl-canvas-wrap { max-width: min(100%, 68vh); margin-inline: auto; }

/* The garment name, under the preview, in single-style mode. Reuses the
   .dl-single-* children so brand, name and style keep the type they had in
   the old step-1 block - only the container changed. */
.dl-stage-caption {
  display: grid;
  gap: 2px;
  margin: var(--s-3) 0 0;
  text-align: center;
  justify-items: center;
}
.dl-stage-caption .dl-single-back { justify-self: center; }


/* The placement controls used to live under the canvas. They are an artwork
   decision, so they moved into step 3 beside the sliders - this is the frame
   they sit in there. */
.dl-tool-group {
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--border);
}
.dl-tool-label {
  display: block;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-2);
}
/* In the narrower right column these wrap rather than sitting on one line. */
.dl-tool-group .dl-stage-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  margin: 0;
}
.dl-tool-group .dl-sides { display: flex; flex-wrap: wrap; gap: 6px; }

@media (max-width: 1000px) {
  /* Stacked layout: the stage is no longer sticky, so there is nothing to fit
     and the canvas can have the full width back. */
  .dl-canvas-wrap { max-width: 100%; }
}


/* Pricing disclaimer — apparel page and style detail.
   Deliberately plain: this is the block a customer reads when they are
   deciding whether to trust the number above it, so it should look like
   terms, not like a promotion. */
.price-note {
  margin: var(--s-5) 0 0;
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: 10px;
  background: rgba(255, 255, 255, .02);
}
.price-note h3 { margin: 0 0 var(--s-2); font-size: 1.05rem; }
.price-note p { margin: 0 0 var(--s-3); }
.price-note ul { margin: 0 0 var(--s-3); padding-left: 1.1em; }
.price-note li { margin: 0 0 .35em; }
.price-note-map { font-size: .93rem; opacity: .85; }
.price-note-foot { margin-bottom: 0; font-size: .93rem; }

.price-breaks {
  width: 100%; max-width: 460px;
  border-collapse: collapse;
  margin: 0 0 var(--s-3);
  font-variant-numeric: tabular-nums;
}
.price-breaks th, .price-breaks td {
  padding: .4em .6em; text-align: right;
  border-bottom: 1px solid var(--line);
}
.price-breaks thead th { text-align: right; font-size: .85rem; opacity: .75; }
.price-breaks tbody th { text-align: left; font-weight: 600; }
.price-breaks tbody tr:last-child th,
.price-breaks tbody tr:last-child td { border-bottom: 0; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Embroidery stitch estimator ------------------------------------------- */
.est-layout {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: var(--s-5); align-items: start; margin-top: var(--s-5);
}
@media (max-width: 820px) { .est-layout { grid-template-columns: 1fr; } }

.est-drop {
  border: 2px dashed var(--line); border-radius: 12px;
  padding: var(--s-6) var(--s-4); text-align: center; cursor: pointer;
  transition: border-color .15s, background .15s;
}
.est-drop:hover, .est-drop:focus-visible, .est-drop.is-over {
  border-color: var(--brand); background: rgba(255, 255, 255, .03); outline: none;
}
.est-drop-main { margin: 0 0 .25em; font-size: 1.1rem; font-weight: 600; }
.est-drop-sub  { margin: 0 0 var(--s-3); opacity: .7; }
.est-drop-note { margin: 0; font-size: .88rem; opacity: .7; }

.est-preview { text-align: center; }
/* Chequerboard, so a "transparent" PNG that is not actually transparent is
   obvious at a glance rather than after the number comes back wrong. */
.est-preview canvas {
  max-width: 100%; height: auto; border-radius: 10px; margin-bottom: var(--s-3);
  background-color: #2a2f37;
  background-image:
    linear-gradient(45deg, #3a4049 25%, transparent 25%, transparent 75%, #3a4049 75%),
    linear-gradient(45deg, #3a4049 25%, transparent 25%, transparent 75%, #3a4049 75%);
  background-size: 20px 20px;
  background-position: 0 0, 10px 10px;
}

.est-field { margin-bottom: var(--s-4); }
.est-field label { display: block; font-weight: 600; margin-bottom: .4em; }
.est-row { display: flex; align-items: center; gap: .6em; }
.est-row input { width: 7em; }
.est-unit { opacity: .7; }
.est-hint { margin: .4em 0 0; font-size: .87rem; opacity: .7; }
.est-field select, .est-row input {
  padding: .5em .6em; border-radius: 8px;
  border: 1px solid var(--line); background: rgba(255,255,255,.04); color: inherit;
}
.est-field select { width: 100%; }

.est-out {
  border: 1px solid var(--line); border-radius: 10px;
  padding: var(--s-4); text-align: center; margin-bottom: var(--s-3);
}
.est-out-label { margin: 0; font-size: .85rem; text-transform: uppercase;
  letter-spacing: .06em; opacity: .65; }
.est-out-big { margin: .15em 0 0; font-size: 2.4rem; font-weight: 700;
  line-height: 1.1; font-variant-numeric: tabular-nums; }
.est-out-range { margin: .3em 0 0; font-size: .9rem; opacity: .7; }

.est-cost p { margin: 0 0 var(--s-2); }
.est-good { color: #6ee7a8; }
.est-over { color: #f4c67a; }
.est-warn {
  color: #f6a6a6; border: 1px solid rgba(246,166,166,.35);
  border-radius: 8px; padding: .7em .8em;
}
.est-tip { font-size: .89rem; opacity: .75; }
.est-cta { margin-top: var(--s-4); }
.est-notes { margin-top: var(--s-6); }

/* "Live now" badge — the counterpart to .soon. Deliberately the brand color
   rather than a green success tint: it is a status, not a congratulation. */
.live {
  display: inline-block; margin-bottom: .6em;
  padding: .18em .6em; border-radius: 999px;
  font-size: .72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em;
  color: var(--brand); border: 1px solid var(--brand);
}
.card--live { border-color: color-mix(in srgb, var(--brand) 40%, var(--line)); }

/* Shop pricing ---------------------------------------------------------- */
.shop-price {
  margin: var(--s-3) 0 .15em;
  font-size: 2rem; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.shop-price-unit {
  margin-left: .4em; font-size: .82rem; font-weight: 500;
  letter-spacing: .04em; text-transform: uppercase; opacity: .6;
}
.shop-price-inc { margin: 0 0 .3em; font-size: .9rem; opacity: .75; }
.shop-price-ext { margin: 0 0 var(--s-3); font-size: .9rem; opacity: .75; }
.shop-price-map {
  margin: 0 0 var(--s-3); font-size: .88rem; opacity: .7;
  border-left: 2px solid var(--line); padding-left: .7em;
}
.shop-price-ask { margin: var(--s-3) 0; font-size: 1.05rem; font-weight: 600; }

/* Collapsed by default: the single price answers most visits, and an open
   five-row table next to it competes with the garment for attention. */
.shop-breaks { margin: 0 0 var(--s-4); }
.shop-breaks summary {
  cursor: pointer; font-size: .9rem; font-weight: 600;
  padding: .3em 0; user-select: none;
}
.shop-breaks summary:hover { color: var(--brand); }
.shop-breaks table {
  border-collapse: collapse; margin: .5em 0 .4em;
  min-width: 190px; font-variant-numeric: tabular-nums;
}
.shop-breaks th, .shop-breaks td {
  padding: .28em .9em .28em 0; text-align: left; font-size: .93rem;
}
.shop-breaks tbody th { font-weight: 600; opacity: .8; }
.shop-breaks td { font-weight: 600; }
.shop-breaks-note { margin: 0; font-size: .82rem; opacity: .65; max-width: 34ch; }

/* The pricing explainer collapses to one line. It sat between the picks and
   the catalog as a wall of text nobody asked for yet - the questions it
   answers only occur to someone once they are looking at a price. Available,
   not imposed. */
details.price-note { padding: 0; background: none; }
/* The marker used to sit in the text flow with a margin, which pushed the
   summary text right by its own width - so the heading and the paragraphs
   under it started at different places and the whole block looked crooked.
   It is on the right now, out of the flow, and both share the same padding. */
details.price-note > summary {
  cursor: pointer;
  padding: var(--s-3) calc(var(--s-4) + 1.4em) var(--s-3) var(--s-4);
  font-weight: 600; user-select: none; list-style: none;
  position: relative;
}
details.price-note > summary::-webkit-details-marker { display: none; }
details.price-note > summary::after {
  content: '＋';
  position: absolute; right: var(--s-4); top: 50%;
  transform: translateY(-50%);
  opacity: .55; font-weight: 400;
}
details.price-note[open] > summary::after { content: '－'; }
details.price-note > summary:hover { color: var(--brand); }
details.price-note > *:not(summary) {
  margin-left: var(--s-4); margin-right: var(--s-4);
}
details.price-note > *:last-child { margin-bottom: var(--s-4); }

/* Catalog tile, revised ------------------------------------------------- */

/* Price on the photo, top left. Uses the card's own surface tone rather than
   black - a black block over a photo reads as a sticker dropped on top of the
   card, not part of it. Slightly translucent so it settles onto the image
   instead of punching a hole in it.

   Size is set per context further down: these chips have to look proportional
   to the card carrying them, and the catalog tile and the pick card are not
   the same size. */
.shop-tile-img { position: relative; }
.shop-tile-price {
  position: absolute; top: 0; left: 0; z-index: 2;
  padding: .28em .5em;
  background: color-mix(in srgb, var(--surface-2) 88%, transparent);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  color: var(--n-000);
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  border-bottom-right-radius: 8px;
  letter-spacing: .01em;
}

/* Brand and category on one line, which is the sentence someone is actually
   reading: what it is, and who makes it. The badge was previously floating
   over the top-left of the photo and looked stranded there. */
.shop-tile-line {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: .5em; width: 100%;
}
.shop-tile-line .shop-badge {
  position: static; flex: none;
  padding: 0; background: none; border: 0;
  font-size: .74rem; text-transform: uppercase; letter-spacing: .06em;
  opacity: .55; font-weight: 600;
}

/* Picks get the same price treatment. Their cards already say what the
   garment is, so no category badge - it would be repeating the heading. */
.pick-img { position: relative; display: block; }

/* Bob: "I thought you said the pick cards were bigger, but they're smaller
   and the price is bigger on those." Correct - the picks run 6 across, the
   catalog tiles are wider. Sized to the card each sits on. */
.shop-tile .shop-tile-price { font-size: .95rem; }
.pick-img .shop-tile-price  { font-size: .82rem; padding: .24em .45em; }

/* Break table gains an extended-size column. */
.shop-breaks thead th {
  font-size: .78rem; text-transform: uppercase; letter-spacing: .05em;
  opacity: .55; font-weight: 600; padding-bottom: .35em;
}
.shop-breaks td { padding-right: 1.2em; }
.shop-breaks td:last-child { padding-right: 0; opacity: .82; }

/* Flip button on the product photo. One control that toggles rather than two
   that label - the garment is the subject, and "Front / Back" as words next
   to it competed for attention. The icon carries the meaning; the word is
   there for anyone the icon does not reach. Bottom right, opposite the price
   chip. Only appears once a back image has loaded and proved not to be
   SanMar's placeholder. */
.shop-view {
  position: absolute; right: var(--s-3); bottom: var(--s-3); z-index: 2;
  display: inline-flex; align-items: center; gap: .4em;
  padding: .38em .7em .38em .55em;
  border: 0; border-radius: 999px; cursor: pointer;
  background: rgba(11, 13, 16, .74);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  color: var(--n-100); font: inherit; font-size: .82rem; font-weight: 600;
  transition: background .15s, color .15s;
}
.shop-view:hover { background: rgba(11, 13, 16, .9); color: #fff; }
.shop-view:active svg { transform: rotate(-180deg); }
.shop-view svg { transition: transform .35s ease; }
.shop-view-label { line-height: 1; }

/* The upcharge note under the included-decoration line. Quiet - it is a
   caveat, not a headline, and shouting it would make the price above look
   less trustworthy than it is. */
.shop-price-extra {
  margin: -.1em 0 var(--s-3);
  font-size: .84rem; opacity: .62;
}

/* Shown in place of the photo when a product has no back image. Sits on the
   same white panel as the product shots so the frame does not jump, and uses
   the watermark logo rather than the full mark - this is a shrug, not a
   branding moment. */
.shop-noimg {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: var(--s-3); padding: var(--s-6) var(--s-4);
  min-height: 240px; text-align: center;
}
.shop-noimg img { width: 190px; height: auto; opacity: .5; }
.shop-noimg p {
  margin: 0; max-width: 26ch;
  font-size: .88rem; color: #6E7885;
}

/* Warning state on a calculated hint - the close date landing on or before
   the open date. Amber rather than red: it is a "this will be tight", not a
   rejection, and the form still submits. */
.hint--warn { color: #f4c67a; }

/* "Not sure" helper on the store request form. Set apart from the fields
   around it - it is a short conversation, not another thing to fill in. */
.store-help {
  border: 1px solid var(--line); border-radius: 10px;
  padding: var(--s-4); background: rgba(255, 255, 255, .02);
}
.store-help-q { margin: 0 0 var(--s-3); font-weight: 600; }
/* The generic .field input rule styles every input as a full-width text box
   - 100% wide with padding - which turned each checkbox into a huge empty
   panel and pushed its label off the row. Checkboxes need it undone. */
.store-help-opt {
  display: flex; align-items: flex-start; gap: .65em;
  padding: .4em 0; cursor: pointer;
  font-size: .95rem; font-weight: 400;
  color: var(--text); margin-bottom: 0;
}
.field .store-help-opt input[type="checkbox"] {
  width: 18px; height: 18px; min-width: 18px;
  margin: .15em 0 0; padding: 0; flex: none;
  border-radius: 4px;
  accent-color: var(--blue);
}
.store-help-opt span { flex: 1; line-height: 1.45; }
.store-help-verdict {
  margin: var(--s-3) 0 0; padding: .7em .9em;
  border-radius: 8px; font-size: .93rem;
  background: rgba(255, 255, 255, .04);
  border-left: 3px solid var(--n-600);
}
.store-help-verdict.is-group { border-left-color: var(--blue-bright); }

/* Shown instead of letting an impossible schedule be submitted. Reads as
   help rather than rejection - the point is to get them to email, not to
   tell them off. */
.store-block {
  margin: 0 0 var(--s-3); padding: var(--s-4);
  border: 1px solid rgba(244, 198, 122, .4);
  border-radius: 10px; background: rgba(244, 198, 122, .07);
  font-size: .93rem; line-height: 1.55;
}
.btn.is-disabled, .btn:disabled { opacity: .45; cursor: not-allowed; }

/* Cart ------------------------------------------------------------------ */
.cart-empty { font-size: 1.05rem; margin: var(--s-5) 0; }
.cart-lines { margin: var(--s-5) 0; }
.cart-line {
  display: grid; grid-template-columns: 90px 1fr auto;
  gap: var(--s-4); align-items: start;
  padding: var(--s-4) 0; border-bottom: 1px solid var(--line);
}
@media (max-width: 620px) {
  .cart-line { grid-template-columns: 64px 1fr; }
  .cart-line-price { grid-column: 1 / -1; text-align: left; }
}
.cart-line-img {
  width: 100%; aspect-ratio: 1; object-fit: contain;
  background: #fff; border-radius: 8px;
}
.cart-line-body h2 { margin: 0 0 .2em; font-size: 1rem; }
.cart-line-meta { margin: 0 0 var(--s-3); font-size: .85rem; opacity: .65; }

/* Size above the box, matching the product page. Side by side, the label and
   the number read as one wide unit and the row wrapped early; stacked, each
   size is a narrow column and a full run of XS-4XL fits on one line. */
.cart-sizes { display: flex; flex-wrap: wrap; gap: .5em .45em; }
.cart-size { display: flex; flex-direction: column; align-items: center; gap: .2em; }
.cart-size-label {
  font-size: .72rem; font-weight: 600; opacity: .65;
  text-transform: uppercase; letter-spacing: .03em;
}
.cart-size input {
  width: 2.6em; padding: .22em .3em; font-size: .9rem;
  text-align: center; color: inherit;
  background: rgba(255,255,255,.04);
  border: 1px solid var(--line); border-radius: 5px;
}
.cart-nudge {
  margin: var(--s-3) 0 0; font-size: .87rem;
  color: var(--blue-bright);
}
/* align-items:flex-end pins Remove to the right edge. text-align does not:
   the button is a flex item, so it shrinks to its own text and sits wherever
   the column puts it - which was under the middle of the price. */
.cart-line-price {
  display: flex; flex-direction: column;
  align-items: flex-end; text-align: right; gap: .2em;
}
.cart-line-price strong { font-size: 1.1rem; font-variant-numeric: tabular-nums; }
.cart-line-price span { font-size: .8rem; opacity: .6; }
.cart-remove {
  margin-top: .3em; background: none; border: 0; padding: 0;
  align-self: flex-end;
  color: var(--n-300); font: inherit; font-size: .82rem;
  text-decoration: underline; cursor: pointer;
}
.cart-remove:hover { color: var(--danger); }

.cart-foot { margin-top: var(--s-5); }
.cart-total {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 1.15rem; padding-bottom: var(--s-3);
  border-bottom: 2px solid var(--line);
}
.cart-total strong { font-size: 1.7rem; font-variant-numeric: tabular-nums; }
.cart-note { margin: var(--s-3) 0; font-size: .87rem; opacity: .68; }
.cart-actions { display: flex; gap: var(--s-3); flex-wrap: wrap; margin: var(--s-4) 0 0; }

/* Add to cart, on the product detail */
.shop-add { margin: var(--s-4) 0; }
.shop-add-sizes { display: flex; flex-wrap: wrap; gap: .5em; margin-bottom: var(--s-3); }
.shop-add-size { display: flex; flex-direction: column; gap: .25em; align-items: center; }
.shop-add-size > span {
  font-size: .74rem; font-weight: 600; opacity: .7; text-transform: uppercase;
}
.shop-add-size input {
  width: 2.6em; padding: .22em .3em; text-align: center; font-size: .92rem;
  color: inherit; background: rgba(255,255,255,.04);
  border: 1px solid var(--line); border-radius: 5px;
}
.shop-add-size.is-out { opacity: .4; }
.shop-add-line { margin: 0 0 var(--s-3); font-size: .89rem; color: var(--blue-bright); min-height: 1.2em; }

/* Cart link and count in the header */
.nav-cart { position: relative; display: inline-flex; align-items: center; gap: .4em; }
.cart-badge {
  display: inline-block; min-width: 1.5em; padding: .1em .4em;
  border-radius: 999px; background: var(--blue); color: #fff;
  font-size: .72rem; font-weight: 700; text-align: center; line-height: 1.5;
}

/* Cart, grouped by style ------------------------------------------------ */
.cart-group { margin-bottom: var(--s-5); }
.cart-group-head {
  display: flex; flex-direction: column; gap: .15em;
  padding: .6em .8em; margin-bottom: .2em;
  background: rgba(255, 255, 255, .03);
  border-left: 3px solid var(--blue-bright);
  border-radius: 6px; font-size: .9rem;
}
.cart-group-note { font-size: .82rem; opacity: .65; }
.cart-group-total {
  margin: .5em 0 0; text-align: right;
  font-size: .88rem; opacity: .75; font-variant-numeric: tabular-nums;
}

.cart-sum { margin-top: var(--s-4); }
.cart-sum-row {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: .35em 0; font-variant-numeric: tabular-nums;
}
.cart-sum-row small { opacity: .55; font-weight: 400; }
.cart-sum-row.cart-total {
  border-top: 2px solid var(--line); border-bottom: 0;
  margin-top: .3em; padding-top: .6em; font-size: 1.15rem;
}

/* Cart link inside the product overlay, where the header is unreachable. */
.shop-add-cart {
  display: inline-block; margin-left: var(--s-3);
  font-size: .9rem; font-weight: 600; color: var(--blue-bright);
}

/* Confirmation after an add, while the product panel stays open. The size
   boxes clear on add, so this is the only thing saying the last colour
   landed - and the running total is what shows the quantity break moving. */
.shop-added {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: .15em .6em;
  margin: 0 0 var(--s-3); padding: .6em .8em;
  border-radius: 8px;
  border: 1px solid rgba(110, 231, 168, .3);
  background: rgba(110, 231, 168, .08);
  font-size: .88rem;
}
.shop-added-tick {
  grid-row: 1 / 3; align-self: center;
  font-size: 1.15rem; color: #6ee7a8; line-height: 1;
}
.shop-added-what { color: var(--n-000); }
.shop-added-run { font-size: .82rem; opacity: .7; }
@keyframes blcAddedFlash {
  from { background: rgba(110, 231, 168, .26); }
  to   { background: rgba(110, 231, 168, .08); }
}
.shop-added.is-new { animation: blcAddedFlash .7s ease-out; }

/* Note above the size boxes. Answers the question before it is asked:
   people expect one trip to the cart per colour. */
.shop-add-hint {
  margin: -.2em 0 var(--s-3);
  font-size: .85rem; opacity: .68; max-width: 46ch;
}

/* Cart line back-links to the product */
.cart-line-link { display: block; }
.cart-line-body h2 a { color: inherit; text-decoration: none; }
.cart-line-body h2 a:hover { color: var(--blue-bright); text-decoration: underline; }


/* Quantity boxes: no spinner arrows.
   type=number is kept rather than switched to text - it still brings up the
   numeric keypad on a phone, which matters more here than the arrows do. The
   arrows themselves are the problem: they inflate the control, sit at an
   awkward size on every browser, and nobody clicking one twelve times is
   having a good day. */
.cart-size input::-webkit-outer-spin-button,
.cart-size input::-webkit-inner-spin-button,
.shop-add-size input::-webkit-outer-spin-button,
.shop-add-size input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}
.cart-size input,
.shop-add-size input {
  -moz-appearance: textfield;
  appearance: textfield;
}

/* Promo code */
.cart-promo { margin: var(--s-4) 0 0; max-width: 22em; }
.cart-promo label {
  display: block; font-size: .85rem; font-weight: 600;
  margin-bottom: .35em; color: var(--text-soft);
}
.cart-promo-row { display: flex; gap: .5em; }
.cart-promo-row input {
  flex: 1; padding: .45em .6em; font-size: .95rem;
  color: inherit; background: rgba(255,255,255,.04);
  border: 1px solid var(--line); border-radius: 6px;
  text-transform: uppercase;
}
.cart-promo-msg { margin: .5em 0 0; font-size: .85rem; opacity: .8; }
.cart-promo-msg.is-ok { color: #6ee7a8; opacity: 1; }
.cart-promo-msg.is-bad { color: #f4c67a; opacity: 1; }
.cart-sum-disc { color: #6ee7a8; }

/* Cart sizes are read-only chips now; editing happens on the product page,
   where stock and colour live. Boxes in a cart are fiddly on a phone and
   easy to nudge by accident. */
.cart-size-chip {
  display: inline-block; padding: .2em .5em; margin: 0 .3em .3em 0;
  font-size: .84rem; border-radius: 5px;
  background: rgba(255,255,255,.05); border: 1px solid var(--line);
  font-variant-numeric: tabular-nums;
}
.cart-size-chip b { font-weight: 700; opacity: .65; margin-right: .25em; }
.cart-edit {
  display: inline-block; margin-top: .5em;
  font-size: .85rem; font-weight: 600; color: var(--blue-bright);
}

/* Three filters on one row ---------------------------------------------- */
.shop-filter-rows {
  display: flex; flex-wrap: wrap; gap: var(--s-3);
  margin: 0;
}
.shop-filter-field {
  display: flex; flex-direction: column; gap: .3em;
  flex: 1 1 12em; min-width: 0;
}
.shop-filter-label {
  font-size: .74rem; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; opacity: .6;
}
.shop-filter-rows .shop-select { width: 100%; }
@media (max-width: 560px) { .shop-filter-field { flex: 1 1 100%; } }

/* Centred, because the search box above it is centred at 460px - a
   left-aligned count underneath had nothing to line up with and read as
   dropped there by accident. */
.shop-count-row {
  display: flex; align-items: baseline; justify-content: center;
  gap: var(--s-3); flex-wrap: wrap; margin: 0;
}
.shop-count-row .shop-count { margin: 0; }
.shop-filter-clear {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-size: .85rem; color: var(--n-300);
  text-decoration: underline;
}
.shop-filter-clear:hover { color: var(--blue-bright); }

/* Our picks sat a long way from the catalog below it - the section padding
   and the grid's own top margin stacked. */
#picks.section--tight { padding-bottom: var(--s-3); }
#picks .picks-foot { margin-bottom: 0; }

/* The pricing explainer ran the full page width while the picks above and
   the catalog below are centred, so it read as belonging to neither. */
#pricing { max-width: 70ch; margin-inline: auto; }


/* Grouped menu panel. Two columns rather than a second level of flyout:
   nested menus are awkward with a mouse and genuinely bad on a phone, and
   this shows everything at once instead of making someone hover to discover
   it. */
.nav-sub--wide {
  display: flex; gap: var(--s-5);
  min-width: 380px; padding: var(--s-4);
}
.nav-col { display: flex; flex-direction: column; min-width: 9.5em; }
.nav-col-head {
  font-size: .7rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--n-400);
  padding: 0 var(--s-2) var(--s-2);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--s-2);
}

/* On a phone the nav is a stacked list, so the panel has to unwind back to
   one column and lose the absolute positioning it uses on desktop. */
/* 880px, matching the breakpoint where the nav becomes a stacked list. At
   900 there was a 20px window where the panel was still absolutely
   positioned inside a nav that had already unwound. */
@media (max-width: 880px) {
  .nav-sub { position: static; transform: none; min-width: 0; }
  .nav-sub--wide { display: block; padding: 0 0 0 var(--s-3); min-width: 0; }
  .nav-col { min-width: 0; }
  .nav-col + .nav-col { margin-top: var(--s-3); }
}

/* Our picks, collapsible ------------------------------------------------- */
.picks-summary {
  cursor: pointer; list-style: none; user-select: none;
  display: flex; align-items: center; justify-content: center; gap: .5em;
  padding: .4em 0; margin-bottom: var(--s-2);
  font-size: clamp(1.3rem, 2.4vw, 1.7rem);
  font-weight: 700; letter-spacing: -0.02em;
  text-align: center;
}
/* One label at a time, chosen by [open] rather than by script - the text
   cannot be briefly wrong while JS loads, or wrong for good if it fails. */
.picks-label--open { display: none; }
.picks-wrap[open] > .picks-summary .picks-label--closed { display: none; }
.picks-wrap[open] > .picks-summary .picks-label--open { display: inline; }
.picks-summary::-webkit-details-marker { display: none; }
.picks-summary::after {
  content: '\25BE'; font-size: .95em; opacity: .75;
  line-height: 1; transition: transform .18s ease;
}
.picks-wrap[open] > .picks-summary::after { transform: rotate(180deg); }
.picks-summary:hover { color: var(--blue-bright); }
.picks-wrap { text-align: left; }
.picks-wrap > .picks-intro { text-align: center; margin-inline: auto; max-width: 62ch; }


/* The section immediately under a page header starts closer to it. A full
   4rem after the band's own padding left the two reading as unrelated. */
.page-head + .section,
.page-head + .section--tight { padding-top: var(--s-5); }

/* Section heads sat 3rem clear of their own content. */
.section-head { margin-bottom: var(--s-5); }

/* Collapsed picks: the section keeps its padding whether or not anything is
   inside it, which left a wide empty band above the search box. With nothing
   showing but a summary line, it does not need a section's worth of room. */
#picks.is-collapsed.section--tight { padding: var(--s-4) 0 var(--s-2); }
#picks.is-collapsed .picks-summary { margin-bottom: 0; }
/* The band was mostly coming from the NEXT section, which carries 4rem of
   top padding of its own. Two sections' padding either side of a single
   summary line is what left the hole. */
#picks.is-collapsed + .section { padding-top: var(--s-3); }
/* And when it is open, the catalog does not need a full 4rem lead-in
   either - the picks grid already ends with plenty of air. */
#picks + .section { padding-top: var(--s-5); }

/* Copy-link button beside the style number */
.shop-copy {
  margin-left: .6em; padding: .12em .5em;
  font: inherit; font-size: .78rem; font-weight: 600;
  color: var(--n-100); cursor: pointer;
  background: rgba(255,255,255,.06);
  border: 1px solid var(--line); border-radius: 5px;
}
.shop-copy:hover { color: #fff; border-color: var(--blue-bright); }

/* Order complete */
.order-tick {
  font-size: 3rem; line-height: 1; margin: 0 0 var(--s-3);
  color: #6ee7a8;
}

/* Checkout ---------------------------------------------------------------
   On the cart page rather than a step of its own: the cart is short, and a
   page change between deciding and paying is where people leave. */
.checkout { margin-top: var(--s-5); padding-top: var(--s-5); border-top: 1px solid var(--line); }
.checkout h2 { font-size: 1.15rem; margin-bottom: var(--s-3); }

.checkout-methods, .checkout-rates { display: grid; gap: .5em; }
.checkout-rates { margin-top: var(--s-3); }
.checkout-method {
  display: flex; align-items: center; gap: .8em;
  padding: .7em .9em; cursor: pointer;
  border: 1px solid var(--line); border-radius: 8px;
  background: rgba(255,255,255,.02);
}
.checkout-method:hover { border-color: var(--n-600); }
.checkout-method:has(input:checked) {
  border-color: var(--blue-bright);
  background: rgba(92,141,255,.08);
}
.field .checkout-method input[type="radio"],
.checkout-method input[type="radio"] {
  width: 18px; height: 18px; min-width: 18px; margin: 0; padding: 0;
  flex: none; accent-color: var(--blue);
}
.checkout-method-body { flex: 1; display: flex; flex-direction: column; }
.checkout-method-body strong { font-size: .95rem; }
.checkout-method-body small { font-size: .82rem; opacity: .65; }
.checkout-method-cost {
  font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap;
}

.checkout-ship { margin-top: var(--s-3); }
.checkout-addr { display: flex; gap: var(--s-3); flex-wrap: wrap; }
.checkout-addr .field { flex: 1 1 9em; }
.checkout-addr .field--sm { flex: 0 0 5em; }
.checkout-addr .field span {
  display: block; font-size: .78rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em; opacity: .6; margin-bottom: .3em;
}
.checkout-addr input {
  width: 100%; padding: .5em .6em; font-size: .95rem; color: inherit;
  background: rgba(255,255,255,.04);
  border: 1px solid var(--line); border-radius: 6px;
}
.checkout-hint { margin: var(--s-3) 0 0; font-size: .87rem; opacity: .7; min-height: 1.2em; }
.checkout-error {
  margin: var(--s-3) 0; padding: .6em .8em; border-radius: 8px;
  font-size: .9rem; color: #f6a6a6;
  border: 1px solid rgba(246,166,166,.35); background: rgba(246,166,166,.07);
}

/* Nearby-customer nudge on the checkout page. Blue rather than a warning
   colour - this is a saving, not a problem. */
.checkout-local-note {
  margin: 0 0 var(--s-3);
  padding: 10px 14px;
  border: 1px solid var(--blue);
  border-left-width: 3px;
  border-radius: 8px;
  background: var(--blue-tint);
  color: var(--n-000);
  font-size: 14px;
  line-height: 1.5;
}

/* Stitch estimator: the background was removed successfully. Informational,
   not a warning - the number it produced is the correct one. */
.est-note {
  margin-top: var(--s-3);
  padding: 10px 14px;
  border-left: 3px solid var(--blue);
  border-radius: 6px;
  background: var(--n-800);
  color: var(--n-100);
  font-size: .9rem;
}

/* Third-party tool disclaimer on the stitch estimator. Quieter than the
   warning above it - this is a standing statement, not an alert - but it
   has to be legible, because it is the part that says we are not
   responsible for someone else's website. */
.est-disclaimer {
  margin-top: var(--s-3);
  padding: 10px 14px;
  border: 1px solid var(--n-700);
  border-radius: 6px;
  color: var(--n-300);
  font-size: .82rem;
  line-height: 1.55;
}
.est-disclaimer a { color: var(--blue-bright); }

/* Design lab: a placement we cannot draw a preview for. Dashed rather than
   dimmed - these are fully orderable, and dimming would read as disabled. */
.dl-sides button.is-nopreview {
  border-style: dashed;
}
.dl-sides button.is-nopreview::after {
  content: " ◦";
  color: var(--n-400);
}
.dl-note--nopreview {
  border-left: 3px solid var(--n-600);
  padding-left: 10px;
  color: var(--n-300);
}
