/* =====================================================================
   Barcode Datalink — since 1991
   ---------------------------------------------------------------------
   A sibling of the Stocktakes Online stylesheet, not a copy of it. The
   bones are shared on purpose - the same layout primitives, the same
   type scale, the same restraint about when a colour is allowed to
   appear - because the two sites belong to the same business and should
   feel like it.

   What differs is deliberate:

     colour   Red, not amber. The existing site is white with a great
              deal of red, so red is already the company's colour; on
              near-black it stops being a decoration and starts being a
              signal. STO's amber then reads as the product's own
              accent sitting beneath the parent's red.

     type     Big Shoulders Display for headings - condensed, industrial,
              drawn for civic signage. It looks like something stamped on
              a plate, which is literally one of the things sold here.
              STO uses Archivo; the two are cousins, not twins.

     motion   The scan line runs down the page and back up, the same way
              Stocktakes Online's does. It ran left and right for a while,
              on the theory that turning the axis would tell the two sites
              apart while keeping them related. Andrei's answer was that
              up and down simply makes more sense, and he is right: it is
              the direction a beam travels down a rack, the direction you
              sweep a handheld over a shelf, and the direction a printhead
              feeds a label. Left and right was a distinction invented for
              the benefit of the stylesheet rather than anybody looking at
              it. The two sites are meant to look related anyway.

   One stylesheet, no build step, no framework. Same as the other one,
   and for the same reason: in five years it will still open.
   ===================================================================== */

@import url('https://fonts.googleapis.com/css2?family=Big+Shoulders+Display:wght@600;700;900&family=Barlow:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap');

:root {
  --ink:      #0b0c0e;
  --ink-2:    #121417;
  --steel:    #1a1e22;
  --steel-2:  #293037;
  --concrete: #868f99;
  /* The same grey, brighter, for text below body size.
     #868f99 measures 5.97:1 on the ink and passes comfortably at 17px.
     The trouble is that small text and low contrast are two difficulties
     and this site was handing over both at once - 14.5px captions and
     11.5px form labels in the same grey as a full-size paragraph. Pick
     one. Anything under 15px gets this instead, at 8.5:1. */
  --concrete-2: #a3acb6;
  --chalk:    #ccd3da;
  --paper:    #f4f2ee;

  /* The red, lifted off the printed brand so it holds up on near-black.
     Never used for body text - red on black at 17px is a headache. It
     marks things: eyebrows, rules, the beam, one word in a heading. */
  --signal:     #ee2b1c;
  --signal-dim: rgba(238, 43, 28, .16);

  /* Kept for the Stocktakes Online references, so the product keeps its
     own colour when it appears on the parent's site. */
  --sto: #ffb800;

  --display: 'Big Shoulders Display', 'Arial Narrow', sans-serif;
  --body:    'Barlow', system-ui, sans-serif;
  --mono:    'IBM Plex Mono', ui-monospace, monospace;


  /* ---- surfaces and text, named for what they do ----

     Added 25-08-2026, as the first half of a light theme. Every value
     below is the literal that was written in place before, so this
     changed nothing the day it landed. The point is that a second
     palette can now be defined without touching a single rule.

     Not everything is here. The red and its hover, the success green,
     black on red, and the whole .plate-btn block are deliberately
     literal: they are identity or they draw a physical object, and
     neither should follow a theme. */
  --bright:      #fff;      /* headings, emphasis, active nav - the brightest text */
  --lede-text:   #e4e9ee;   /* the standfirst paragraph, one step below bright */
  --edge:        #1e252b;   /* grid gaps and hairline dividers */
  --sunken:      #10161a;   /* inset panels: chips, code blocks, hover rows */
  --sunken-2:    #0d1114;   /* the darkest inset */
  --placeholder: #8b949e;   /* placeholder text in a form field */
  --on-paper:    #4a5058;   /* caption text ON --paper - fixed, both themes */
  --on-paper-b:  #14181b;   /* the bold in that caption - fixed, both themes */
  --white-plate: #fff;      /* white because what sits ON it needs white:
                               a product shot cut out on white, and a QR
                               code, which needs its quiet zone and its
                               contrast to be scannable at all. Fixed in
                               both themes - this is not a preference. */
  --on-signal:   #fff;      /* text on the red, the light way - both themes */
  --on-signal-2: #0b0c0e;   /* text on the red, the dark way  - both themes */
  --rule-colour: #222a31;   /* so --rule can be rebuilt per theme */
  --grid-line:   rgba(255, 255, 255, .02);   /* the engineering paper behind everything */
  --bar-bg:      rgba(11, 12, 14, .88);      /* the sticky header, over blurred page */
  --hero-glow:   rgba(238, 43, 28, .14);     /* the red bloom top right of a hero */
  --beam-glow:   rgba(238, 43, 28, .40);     /* the halo around the scan line */
  --hatch:       rgba(255, 255, 255, .028);  /* diagonals on a photo not taken yet */

  /* The Part & Qty Check sample file. The app's own match green and
     mismatch red, lifted to carry on near-black the way --signal is. */
  --pqc-ok:      #3f9e46;
  --pqc-no:      #e0574f;

  --rule: 1px solid var(--rule-colour);
  --gutter: clamp(20px, 5vw, 72px);
  --max: 1240px;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--ink);
  color: var(--chalk);
  font: 400 17px/1.65 var(--body);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* Engineering paper, barely there - the dark needs a surface rather than
   a void. Never strong enough to compete with type. */
body::before {
  content: '';
  position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background-image:
    linear-gradient(var(--grid-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
  background-size: 64px 64px;
}

img { max-width: 100%; display: block; }
a { color: inherit; }

.wrap { width: 100%; max-width: var(--max); margin: 0 auto; padding: 0 var(--gutter); position: relative; z-index: 1; }

/* ---------- type ---------- */

h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 900; line-height: .92; letter-spacing: 0;
  margin: 0; color: var(--bright); text-transform: uppercase;
}
h1 { font-size: clamp(52px, 9vw, 132px); }
h2 { font-size: clamp(36px, 5.2vw, 72px); }
h3 { font-size: clamp(22px, 2.2vw, 30px); line-height: 1.06; }
h4 { font-size: 18px; font-weight: 700; letter-spacing: .02em; }

/* The condensed face is drawn tight. Body copy stays in Barlow, which is
   not, or a paragraph turns into a wall. */
p { margin: 0 0 1.1em; font-family: var(--body); text-transform: none; }
p:last-child { margin-bottom: 0; }

.lede { font-size: clamp(18px, 1.6vw, 22px); line-height: 1.55; color: var(--lede-text); max-width: 62ch; }
.muted { color: var(--concrete); }
.small { font-size: 15.5px; line-height: 1.6; }
.small.muted, .muted .small { color: var(--concrete-2); }

/* A part number stencilled on a crate. */
.eyebrow {
  font: 500 12px/1 var(--mono);
  letter-spacing: .18em; text-transform: uppercase; color: var(--signal);
  display: inline-flex; align-items: center; gap: 10px; margin-bottom: 22px;
}
.eyebrow::before { content: ''; width: 26px; height: 2px; background: var(--signal); }
.eyebrow.plain::before { display: none; }
.eyebrow.sto { color: var(--sto); }
.eyebrow.sto::before { background: var(--sto); }

/* ---------- barcode rules ---------- */

.bars {
  height: 26px;
  background-image: repeating-linear-gradient(
    90deg,
    var(--steel-2) 0 2px, transparent 2px 5px,
    var(--steel-2) 5px 6px, transparent 6px 11px,
    var(--steel-2) 11px 14px, transparent 14px 16px,
    var(--steel-2) 16px 17px, transparent 17px 23px
  );
  opacity: .85;
}
.bars.red {
  background-image: repeating-linear-gradient(
    90deg,
    var(--signal) 0 2px, transparent 2px 5px,
    var(--signal) 5px 6px, transparent 6px 11px,
    var(--signal) 11px 14px, transparent 14px 16px,
    var(--signal) 16px 17px, transparent 17px 23px
  );
  height: 14px; opacity: .9;
}

/* ---------- header ---------- */

header.bar {
  position: sticky; top: 0; z-index: 40;
  background: var(--bar-bg);
  backdrop-filter: blur(14px);
  border-bottom: var(--rule);
}
header.bar .wrap { display: flex; align-items: center; gap: 26px; height: 76px; }

.brand { display: flex; align-items: baseline; gap: 10px; text-decoration: none; }
.brand b {
  font: 900 27px/1 var(--display); color: var(--bright);
  text-transform: uppercase; letter-spacing: .01em;
}
.brand b em { font-style: normal; color: var(--signal); }
.brand span {
  font: 400 10.5px/1 var(--mono); color: var(--concrete);
  letter-spacing: .16em; text-transform: uppercase;
}

/* gap 20 rather than 24: the four pixels are what let Clearance into the
   menu without the header going to two lines. See nav.mjs for the
   measurement - 71px needed, 42 from shortening Stocktakes and 36 from
   here. */
nav.main { display: flex; align-items: center; gap: 18px; }
nav.main a {
  font: 500 14px/1 var(--body); text-decoration: none; color: var(--chalk);
  letter-spacing: .01em; padding: 8px 0; border-bottom: 2px solid transparent;
  transition: color .16s, border-color .16s;
}
nav.main a:not(.btn):hover { color: var(--bright); border-bottom-color: var(--signal); }
nav.main a.here { color: var(--bright); border-bottom-color: var(--signal); }

/* ---------- buttons ---------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 13px 22px; border-radius: 2px; text-decoration: none;
  font: 600 14.5px/1 var(--body); letter-spacing: .01em;
  border: 1px solid transparent; cursor: pointer; transition: transform .12s, background .16s, border-color .16s;
}
.btn:hover { transform: translateY(-1px); }
.btn-signal { background: var(--signal); color: var(--on-signal); }
.btn-signal:hover { background: #ff3a2a; }
.btn-ghost { border-color: var(--steel-2); color: var(--chalk); }
.btn-ghost:hover { border-color: var(--signal); color: var(--bright); }
.btn-dark { background: var(--ink); color: var(--bright); }
.btn-lg { padding: 17px 30px; font-size: 16px; }

.menu-btn { display: none; background: none; border: 0; color: var(--bright); font-size: 26px; cursor: pointer; }

/* ---------- hero ---------- */

/* The hero is sized to the screen, not to its contents.

   The six things this company does have to be visible the moment the page
   opens - somebody arriving already knows whether they want a printer, a
   ribbon or a repair, and making them scroll to find out we do it is a
   button we have hidden from the one person guaranteed to press it. So the
   hero claims a viewport and the row sits at the bottom of it.

   100svh, not 100vh: on a phone, vh is measured against the viewport with
   the browser chrome hidden, so a 100vh hero is always taller than what
   you can actually see and the row falls off the bottom - which is the
   exact fault this is fixing. svh is the small viewport, chrome included. */
.hero {
  position: relative; overflow: hidden;
  padding: clamp(56px, 8vw, 104px) 0 clamp(36px, 4vw, 56px);
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
}
.hero > .wrap { width: 100%; }

/* On a short laptop screen the headline is what gives, not the row. */
@media (max-height: 760px) {
  .hero h1 { font-size: clamp(40px, 6vw, 72px); }
  .hero .lede { font-size: 17px; margin-top: 20px; }
}

/* A phone cannot hold six tiles and a headline in one screen, and pretending
   otherwise squashes both. Let it flow. */
@media (max-width: 720px) {
  .hero { min-height: 0; }
}

.hero-legacy { position: relative; padding: clamp(76px, 12vw, 156px) 0 clamp(56px, 8vw, 104px); overflow: hidden; }
.hero::after {
  content: ''; position: absolute; top: -30%; right: -14%; width: 62vw; height: 78vh;
  background: radial-gradient(closest-side, var(--hero-glow), transparent 72%);
  pointer-events: none;
}
.hero h1 { max-width: 16ch; }
.hero h1 em { font-style: normal; color: var(--signal); }
.hero .lede { margin-top: 30px; }
.hero .cta { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 40px; }

/* The beam.

   A horizontal line travelling down the surface and back up, the way a
   beam goes down a rack, the way you sweep a handheld over a shelf, and
   the way a printhead crosses a label as the roll feeds past it. Every
   one of those moves along the page rather than across it, which is why
   this is the axis it should always have been on.

   `alternate` runs the keyframes forward then back, so one declaration
   describes both passes and they can never drift apart. `ease-in-out`
   does the rest - the line slows as it reaches each end, the way an
   oscillating mirror does when it turns around.

   It dims at the turns rather than going out. Held at full brightness it
   becomes wallpaper; switched off it blinks. Dimming reads as the thing
   slowing down, which is what is happening.

   `calc(100% - 2px)` rather than `100%` so the beam finishes flush with
   the bottom edge instead of two pixels past it. Going across, that
   overshoot fell outside the viewport and nobody saw it; travelling down,
   it lands on the seam between two sections. */
.scan { position: relative; }
.scan::before {
  content: ''; position: absolute; left: 0; right: 0; height: 2px; top: 0; z-index: 2;
  background: linear-gradient(90deg, transparent, var(--signal), transparent);
  box-shadow: 0 0 18px 2px var(--beam-glow);
  animation: sweep-y 6.4s ease-in-out infinite alternate;
}

/* The home page beam sweeps the words and stops.

   It used to be on .hero itself, which is min-height:100svh, so the line
   travelled the whole screen - down over the eight tiles and out the
   bottom. Andrei asked for it to turn around at about the end of the text,
   and he is right about why: a red line crossing a grid of links reads as
   something loading rather than something scanning.

   So .hero-say is the zone. It is a full-bleed block - the beam spans the
   section edge to edge as before - and the .wrap inside it holds the text
   to the column. Its height is the height of the words, so the beam turns
   around wherever they happen to end, at any window size, with no number
   here that has to be kept in step with the copy.

   No duration override any more. That existed because the beam had a
   whole screen to cross and 6.4s made it look hurried; over a text block
   it is travelling about the same distance as the one on every other
   page's section head, so it should run at the same speed. */
.hero-say { position: relative; width: 100%; }

@keyframes sweep-y {
  0%   { top: 0;                 opacity: .18; }
  14%  { opacity: 1; }
  86%  { opacity: 1; }
  100% { top: calc(100% - 2px);  opacity: .18; }
}

/* Somebody who has asked their machine to stop moving things has asked
   for this too. */
@media (prefers-reduced-motion: reduce) {
  .scan::before { animation: none; opacity: .45; }
}

/* ---------- the six ----------

   Six doors, in the first screen, in the words a customer would use for
   them. Not "solutions" - Scan, Print, Labels, Rentals, Repairs, and the
   software. Somebody who came here to buy ribbon should be one click from
   ribbon.

   Stocktakes Online carries amber rather than red, because it is the
   product's own colour on the Stocktakes Online site. Sitting in a row of
   red, it reads as the one thing here that is ours rather than Zebra's. */
/* Four across, two down. Six in a row made every tile a narrow column
   with a heading broken over three lines; four gives the words room and
   the second row stops the first from reading as the whole business. */
.quick {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px;
  background: var(--edge); border: var(--rule);
  margin-top: clamp(30px, 4.5vw, 54px);
}
.quick a {
  background: var(--ink); padding: 22px 18px 26px; text-decoration: none;
  display: block; position: relative; transition: background .16s;
}
.quick a:hover { background: var(--ink-2); }
.quick a::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--signal); transform: scaleX(0); transform-origin: left;
  transition: transform .18s ease;
}
.quick a:hover::after { transform: scaleX(1); }
.quick a.sto::after { background: var(--sto); }

.quick .k {
  display: block; font: 500 10.5px/1 var(--mono);
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--signal); margin-bottom: 14px;
}
.quick a.sto .k { color: var(--sto); }
.quick b {
  display: block; font: 900 21px/1.02 var(--display);
  color: var(--bright); text-transform: uppercase;
}
.quick span { display: block; font-size: 13.5px; line-height: 1.5; color: var(--concrete-2); margin-top: 9px; }

@media (max-width: 1100px) { .quick { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px)  { .quick { grid-template-columns: repeat(2, 1fr); } }

/* ---------- the question band ----------
   A whole page is a lot to ask somebody to find. A question they can
   answer about themselves is not, and "still running Windows Mobile?"
   sorts one specific, valuable caller out of everybody else reading the
   page. Two of these, each a sentence and a door, and deliberately not
   tiles - a tile makes it one of eight equal choices, which is the
   opposite of what a question like that is for. */
.ask { border-top: var(--rule); border-bottom: var(--rule); background: var(--ink-2); }
.ask .wrap {
  display: flex; align-items: center; justify-content: space-between;
  gap: clamp(20px, 3vw, 44px); flex-wrap: wrap;
  padding-top: clamp(30px, 3.6vw, 48px); padding-bottom: clamp(30px, 3.6vw, 48px);
}
.ask h2 { font-size: clamp(26px, 3.1vw, 42px); max-width: 18ch; }
.ask h2 em { font-style: normal; color: var(--signal); }
.ask p { color: var(--concrete); max-width: 56ch; margin: 0; font-size: 16px; line-height: 1.6; }
.ask .said { flex: 1 1 380px; }
.ask .go { flex: 0 0 auto; }

@media (max-width: 720px) {
  .ask .wrap { display: block; }
  .ask p { margin: 16px 0 22px; }
}

/* ---------- figures strip ---------- */

.strip { border-top: var(--rule); border-bottom: var(--rule); background: var(--ink-2); }
.strip .wrap { display: grid; grid-template-columns: repeat(4, 1fr); }
/* > div, not div.
   .wrap is itself a div, so ".strip div" matched the wrap as well as the
   four cells inside it - and at 0-1-1 it beat ".wrap" at 0-1-0. The cell
   padding was therefore being applied to the wrap, whose own
   "padding: 0 var(--gutter)" it overrode. Left padding 0 deleted the page
   gutter outright, up to 72px of it, which is why "1991" sat outside the
   column that every other block on the page lines up with.
   The child combinator confines it to the cells. */
.strip .wrap > div { padding: 34px 26px 34px 0; border-right: var(--rule); }
.strip .wrap > div:last-child { border-right: 0; }
.strip b {
  display: block; font-family: var(--display); font-weight: 900;
  font-size: clamp(34px, 3.8vw, 54px); color: var(--bright); line-height: 1;
}
.strip span {
  display: block; font: 400 13px/1.4 var(--mono); color: var(--concrete-2);
  margin-top: 12px; text-transform: uppercase; letter-spacing: .1em;
}

/* ---------- sections ---------- */

section { padding: clamp(64px, 9vw, 124px) 0; position: relative; }
section.tint { background: var(--ink-2); border-top: var(--rule); border-bottom: var(--rule); }
.head { max-width: 70ch; margin-bottom: clamp(40px, 5vw, 66px); }
.head .lede { margin-top: 18px; }

/* ---------- grids and cells ---------- */

.grid { display: grid; gap: 1px; background: var(--edge); border: var(--rule); }
.grid.two { grid-template-columns: repeat(2, 1fr); }
.grid.three { grid-template-columns: repeat(3, 1fr); }
.grid.four { grid-template-columns: repeat(4, 1fr); }

.cell { background: var(--ink); padding: clamp(26px, 3vw, 40px); position: relative; }
.cell .n {
  font: 500 11.5px/1 var(--mono); color: var(--signal);
  letter-spacing: .16em; text-transform: uppercase; display: block; margin-bottom: 18px;
}
.cell h3 { margin-bottom: 14px; }

/* A heading that is a link has to look like one. Base style is
   a { color: inherit }, so without this the MC3400 and MC9400 headings on
   mobile-computers.html read as plain text and nobody clicks them - which
   was the whole problem: 106 old URLs land on that page and it had no way
   through to either product. Same treatment as the upgrade paths. */
.cell h3 a {
  color: inherit; text-decoration: none;
  box-shadow: inset 0 -3px 0 var(--signal);
  transition: color .18s ease;
}
.cell h3 a:hover { color: var(--signal); }

/* A heading that opens a block of running prose inside a section, rather
   than sitting in a tile. Three of these in a column need something to
   separate them or the section reads as one unbroken wall of text - the
   rule does that without the weight of a whole tinted band. */
h3.rule-top {
  margin: clamp(34px, 4vw, 52px) 0 18px;
  padding-top: clamp(24px, 3vw, 34px);
  border-top: var(--rule);
}
h3.rule-top:first-of-type { margin-top: clamp(24px, 3vw, 34px); }
.cell p { font-size: 15.5px; line-height: 1.62; color: var(--concrete); }
.cell p b { color: var(--chalk); font-weight: 600; }
.cell.lit { background: var(--ink-2); }

/* A cell that is a link to somewhere - the whole tile is the target. */
a.cell { text-decoration: none; display: block; transition: background .16s; }
a.cell:hover { background: var(--ink-2); }
a.cell::after {
  content: '→'; position: absolute; right: clamp(26px, 3vw, 40px); bottom: clamp(26px, 3vw, 40px);
  color: var(--signal); font-size: 20px; opacity: 0; transform: translateX(-6px);
  transition: opacity .16s, transform .16s;
}
a.cell:hover::after { opacity: 1; transform: translateX(0); }

/* ---------- numbered steps ---------- */

.steps { display: grid; gap: 1px; background: var(--edge); border: var(--rule); }
.step { background: var(--ink); padding: clamp(24px, 2.6vw, 34px); display: grid; grid-template-columns: 74px 1fr; gap: 20px; align-items: start; }

/* The number lights up as you pass it, and goes out again behind you.
   Lifted from the Stocktakes Online home page, where it is amber because
   amber is that product's colour; here it is red, for the same reason.

   It is doing more than decoration. These lists are read by somebody
   working out which item is theirs, and a number that answers the mouse
   tells them the row is a thing rather than a paragraph that happens to
   start with a digit. */
.step .num { font: 900 40px/1 var(--display); color: var(--steel-2); transition: color .18s ease; }
.step:hover .num { color: var(--signal); }

/* Only the number moves. These rows are not links, and a background that
   lights up would say they were. */
@media (prefers-reduced-motion: reduce) {
  .step .num { transition: none; }
}
.step h3 { margin-bottom: 10px; }
.step p { font-size: 15.5px; color: var(--concrete); margin: 0; }

/* ---------- callout ---------- */

.callout { background: var(--signal); color: var(--on-signal); padding: clamp(44px, 5vw, 72px) 0; }
.callout .wrap { display: flex; align-items: center; justify-content: space-between; gap: 34px; flex-wrap: wrap; }
.callout h2 { color: var(--on-signal); max-width: 20ch; }
.callout p { margin-top: 14px; max-width: 52ch; color: rgba(255, 255, 255, .9); }
.callout .btn-dark:hover { background: #000; color: #fff; }

/* .muted and .small.muted are greys chosen for the page background, and
 * both are more specific than `.callout p` - so on the red they won the
 * cascade and landed at 1.8:1 in the dark theme and 1.6:1 in the light
 * one. Normal text needs 4.5:1. The fine print under the callout on
 * /privacy was the first page to hit it and it was close to invisible.
 * On the red, text is white; the smaller size does the de-emphasis that
 * the grey was there for. */
.callout .muted, .callout .small.muted, .callout .muted .small { color: var(--on-signal); }

/* ---------- notes ---------- */

.note { border-left: 3px solid var(--signal); background: var(--ink-2); padding: 22px 26px; margin: 30px 0; }
.note p { font-size: 15.5px; color: var(--concrete); }
.note p b, .note p strong { color: var(--bright); }

/* ---------- footer ---------- */

footer { border-top: var(--rule); background: var(--ink-2); padding: clamp(48px, 6vw, 80px) 0 40px; }
footer .cols { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: 40px; }
footer h4 { color: var(--bright); margin-bottom: 16px; font-family: var(--display); font-size: 15px; letter-spacing: .1em; }
footer a { color: var(--concrete); text-decoration: none; display: block; padding: 5px 0; font-size: 15px; }
footer a:hover { color: var(--bright); }
footer .legal {
  margin-top: 46px; padding-top: 26px; border-top: var(--rule);
  display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap;
  font: 400 13px/1.5 var(--mono); color: var(--concrete-2);
}

/* =====================================================================
   The light theme.
   ---------------------------------------------------------------------
   Dark is the default and stays the default. No preference stored means
   dark, and a machine set to light mode still gets dark - this does not
   read prefers-color-scheme on purpose. The site was designed dark, and
   the switch is for the two places dark is genuinely worse: a bright
   warehouse office with a window behind the screen, and a page being
   printed or put on a projector.

   Every name below is defined in :root above. Nothing here is a new
   rule - it is the same stylesheet with a second set of values, which
   is the whole point of having spent Phase 1 naming them.

   Four names are deliberately absent, because they do not belong to a
   theme:

     --paper        a printed thing on a bench is pale in any room
     --white-plate  a QR code and a cut-out product shot both need
                    white under them, in any room
     --on-signal    white on the red, in both
     --on-signal-2  near-black on the red, in both
     --on-paper     dark text on that paper, in both

   And two colours change that arguably should not have. Both are the
   same problem: a colour picked to sit on near-black does not have the
   contrast to sit on near-white.

     --signal   #ee2b1c measures 3.61:1 on this page, which fails at the
                12px uppercase the eyebrows are set in. #d01d0e is 4.67
                and still reads as the same red.
     --sto      #ffb800 is 1.49:1 here. Amber on near-white is close to
                unreadable. #8f6100 is 4.66 and still says amber.

   Both of those numbers are measured against #eceef0, the page, and not
   against white. The first version of this palette was checked against
   white, which flatters everything on it by about a quarter of a point,
   and three colours that looked fine were not.

   This is the same argument that produced --concrete-2: small text and
   low contrast are two difficulties, and handing over both at once is
   not a style choice.
   ===================================================================== */

:root[data-theme="light"] {
  --ink:      #eceef0;   /* the page */
  --ink-2:    #ffffff;   /* raised: cards, tinted sections, the menu drawer */
  --steel:    #ffffff;   /* form fields */
  --steel-2:  #c9d0d6;   /* borders, barcode bars, icon strokes */
  --concrete: #5b646d;   /* secondary text            6.0:1 on white */
  --concrete-2: #434b53; /* the same, for under 15px  9.0:1 */
  --chalk:    #23282d;   /* body text */

  --bright:      #0b0c0e;   /* headings, emphasis, active nav */
  --lede-text:   #1b2025;   /* the standfirst, one step below bright */
  --edge:        #dcdfe3;   /* grid gaps and hairline dividers */
  --sunken:      #f2f4f6;   /* inset panels: chips, code blocks, hover rows */
  --sunken-2:    #e8ebee;   /* the deepest inset */
  --placeholder: #6b737b;
  --rule-colour: #d7dbdf;

  --signal:     #d01d0e;
  --signal-dim: rgba(208, 29, 14, .10);
  --sto:        #8f6100;

  --on-signal-2: #fff;   /* on THIS red, the readable one is white. The dark
                            theme keeps near-black: its red is brighter, so
                            the two themes want opposite answers here. */

  /* On a light page these can be the app's colours exactly, out of its
     own colors.xml, because a light page is the background the app has. */
  --pqc-ok:     #2E7D32;
  --pqc-no:     #C62828;

  --grid-line:  rgba(0, 0, 0, .02);    /* .035 read as graph paper - see below */
  --bar-bg:     rgba(236, 238, 240, .94);  /* .90 went pink over the red callout */
  --hero-glow:  rgba(208, 29, 14, .07);
  --beam-glow:  rgba(208, 29, 14, .28);
  --hatch:      rgba(0, 0, 0, .035);

  /* Two of these were dialled back after looking at it rather than at
     the numbers. The engineering paper started at .035, matching the
     dark theme's weight arithmetically, and on near-white it stopped
     being a surface and became graph paper - it competed with the type
     on every page. .02 is barely there, which is the job.

     And the header bar was .90. It blurs whatever is behind it, and
     what is behind it at the bottom of most pages is the red callout,
     so ten per cent of red came through and the bar turned pink. A
     dark bar hides that; a light one does not. .94 keeps the blur and
     loses the tint. */
}

/* The one rule the light theme adds rather than recolours.

   A card on near-black is told apart from the page by being lighter. On
   near-white it cannot be lighter than white, so it is told apart by
   sitting slightly above the page instead. Same job, opposite tool. */
:root[data-theme="light"] .card {
  box-shadow: 0 1px 2px rgba(11, 12, 14, .05), 0 4px 14px rgba(11, 12, 14, .04);
}

/* ---------- the theme switch ----------

   Beside the wordmark on a desktop, because it belongs to the site
   rather than to the menu. On a phone it moves to the right, immediately
   left of the hamburger where a thumb already is, and "Since 1991"
   steps aside to make room - it is the one part of the header that is
   decoration rather than navigation.

   That move needs no reordering and no second element. The auto margin
   simply changes hands: on a desktop the button carries it, so it sits
   tight against the brand and pushes the menu away; on a phone the
   brand carries it and the button falls in beside the hamburger.

   Which icon shows, and what a screen reader is told, are both decided
   by CSS from one attribute on <html>. No JavaScript keeps a label in
   step with a state - the label IS the state. This site's recurring
   bug is a fact written twice. */
.theme-btn {
  position: relative; margin-right: auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0;
  background: none; border: 1px solid transparent; border-radius: 2px;
  color: var(--concrete); cursor: pointer;
  transition: color .16s, border-color .16s;
}
.theme-btn:hover { color: var(--bright); border-color: var(--steel-2); }
.theme-btn:focus-visible { outline: 2px solid var(--signal); outline-offset: 2px; }
.theme-btn svg {
  width: 17px; height: 17px; fill: none; stroke: currentColor;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}

/* Sun in the dark, moon in the light: the icon shows where you are
   going, not where you are. */
.theme-btn .moon { display: none; }
:root[data-theme="light"] .theme-btn .sun  { display: none; }
:root[data-theme="light"] .theme-btn .moon { display: block; }

/* Off-screen but still read aloud. display:none would hide it from a
   screen reader too, which is the point of doing it this way. */
.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
:root[data-theme="light"] .theme-btn .sun-t { display: none; }
.theme-btn .moon-t { display: none; }
:root[data-theme="light"] .theme-btn .moon-t { display: inline; }

/* The tooltip, on a desktop only. A phone has no hover, so it would
   either never appear or appear stuck after a tap. */
.theme-btn::after {
  content: 'Light theme';
  position: absolute; top: calc(100% + 9px); left: 50%; transform: translateX(-50%);
  white-space: nowrap; padding: 7px 9px;
  font: 500 10.5px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--chalk); background: var(--ink-2); border: var(--rule);
  opacity: 0; pointer-events: none; transition: opacity .14s;
}
:root[data-theme="light"] .theme-btn::after { content: 'Dark theme'; }
.theme-btn:hover::after, .theme-btn:focus-visible::after { opacity: 1; }

/* ---------- responsive ---------- */

@media (max-width: 1000px) {
  .grid.four { grid-template-columns: repeat(2, 1fr); }
  .strip .wrap { grid-template-columns: repeat(2, 1fr); }
  .strip .wrap > div:nth-child(2) { border-right: 0; }
  footer .cols { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 720px) {
  .menu-btn { display: block; }
  /* The auto margin changes hands, and the icon lands beside the menu. */
  .brand { margin-right: auto; }
  .brand span { display: none; }
  .theme-btn { margin-right: 0; }
  .theme-btn::after { display: none; }
  nav.main {
    display: none; position: absolute; top: 76px; left: 0; right: 0;
    background: var(--ink-2); border-bottom: var(--rule);
    flex-direction: column; align-items: stretch; gap: 0; padding: 8px 0;
  }
  nav.main.open { display: flex; }
  nav.main a { padding: 14px var(--gutter); border-bottom: 0; }
  .grid.two, .grid.three, .grid.four { grid-template-columns: 1fr; }
  .step { grid-template-columns: 54px 1fr; gap: 14px; }
  .step .num { font-size: 30px; }
  footer .cols { grid-template-columns: 1fr; }
}

/* ---------- a photograph of the real thing ----------
   Edge to edge, no white mount: these are warehouses and workbenches, not
   prints, and a frame would make them look like illustrations of one. The
   caption sits under in body text because it is saying something rather
   than labelling something. */
.photo-wide { margin: clamp(30px, 4vw, 52px) 0 0; }
/* video as well as img: two pages play a manufacturer's own footage from
 * the manufacturer's server, and a bare <video> would sit in the page
 * without the border every other wide figure has. */
.photo-wide img, .photo-wide video { display: block; width: 100%; height: auto; border: var(--rule); }
.photo-wide figcaption {
  margin-top: 14px; font-size: 15px; line-height: 1.6;
  color: var(--concrete); max-width: 74ch;
}
.photo-wide figcaption b { color: var(--chalk); }

/* ---------- enquiry form ----------
   Dark fields need a visible edge or they vanish into the page and read as
   decoration. The focus state is red because that is this site's "look
   here", and because a form somebody is halfway through is exactly when
   they need to know which box they are in. */
.form { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; max-width: 780px; }
.form .full { grid-column: 1 / -1; }

.form label {
  display: block; font: 500 12px/1 var(--mono);
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--concrete-2); margin-bottom: 9px;
}
.form label .req { color: var(--signal); }

.form input, .form select, .form textarea {
  width: 100%; background: var(--steel); color: var(--bright);
  border: 1px solid var(--steel-2); border-radius: 2px;
  padding: 13px 14px; font: 400 16px/1.4 var(--body);
  transition: border-color .16s, background .16s;
}
.form textarea { min-height: 150px; resize: vertical; }
.form input:focus, .form select:focus, .form textarea:focus {
  outline: none; border-color: var(--signal); background: var(--ink);
}
.form input::placeholder, .form textarea::placeholder { color: var(--placeholder); }

/* The honeypot. A real person never sees it and never fills it in; the
   robots that scrape forms fill in everything they find. */
.form .trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form .actions { grid-column: 1 / -1; display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.form .actions p { font-size: 15px; color: var(--concrete-2); margin: 0; }

@media (max-width: 620px) { .form { grid-template-columns: 1fr; } }

/* The answer after pressing Send. Green when it arrived, red when it did
   not - and the failed one keeps the form below it with everything still
   typed in, because losing somebody's message twice is not a mistake worth
   making. */
.sent {
  border-left: 3px solid #21b26b; background: var(--ink-2);
  padding: 26px 30px; max-width: 780px;
}
.sent.bad { border-left-color: var(--signal); margin-bottom: 24px; }
.sent h3 { margin-bottom: 10px; }
.sent p { color: var(--concrete); font-size: 15.5px; }
.sent a { color: var(--bright); }

/* =====================================================================
   Product pages
   ---------------------------------------------------------------------
   One page per product, and the part numbers live on it. The old site
   had eleven separate ZT411 pages competing with each other for the
   same search; this replaces that pattern with a single page that says
   "here are the versions of this thing, here is what each one costs".
   ===================================================================== */

/* ---------- the paper plate ----------

   Zebra ships product shots on white. Dropped straight onto near-black
   they read as bright rectangles with a hard edge, and cutting them out
   is hours of work that has to be redone every time a photo changes.

   So the white is made deliberate instead: the product sits on --paper,
   which has been defined in the palette since the beginning and never
   used. It reads as a spec sheet laid on the bench - which is what it
   is - rather than as a mistake. */
.plate {
  background: var(--paper);
  border: var(--rule);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px;
  padding: clamp(20px, 3vw, 40px);
  min-height: 340px;
}
.plate img { width: 100%; height: auto; max-height: 460px; object-fit: contain; }

/* Dark text, because this one sits on paper rather than on the page. It
   carries the kit contents, so it is doing work rather than labelling. */
.plate figcaption {
  font: 400 14.5px/1.5 var(--body); color: var(--on-paper); text-align: center;
  max-width: 46ch; text-transform: none;
}
.plate figcaption b { color: var(--on-paper-b); font-weight: 600; }

/* A photograph of the thing in use needs no plate - it already has a
   world in it. Edge to edge, same as .photo-wide. */
.in-use { border: var(--rule); }
.in-use img { width: 100%; height: auto; display: block; }

/* Three photographs side by side, for a page that is better off showing
   the parts than describing them. The middle one is portrait and the
   first landscape, so the images are given a fixed aspect and cropped to
   fill rather than left to set their own heights - three figures of
   different depths in a row reads as a mistake.

   The caption sits under the border rather than inside it: these are
   photographs of a shelf, not framed product shots, and a caption over
   the image would cover the part numbers that are the reason for taking
   them. */
.shots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin-top: 8px; }
.shots figure { margin: 0; }
.shots img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border: var(--rule); }
.shots figcaption {
  font: 400 15px/1.6 var(--body); color: var(--concrete-2);
  margin-top: 12px; text-transform: none;
}
.shots figcaption b { color: var(--chalk); font-weight: 600; }

/* Two rather than three, for a pair of photographs that answer a pair of
   questions. Same rules otherwise, so a two-up and a three-up on the same
   site crop and caption identically. */
.shots.two { grid-template-columns: repeat(2, 1fr); }

@media (max-width: 860px) {
  .shots, .shots.two { grid-template-columns: 1fr; gap: 26px; }
  .shots img { aspect-ratio: auto; }
}

.product-top { display: grid; grid-template-columns: 1.05fr 1fr; gap: clamp(26px, 3.4vw, 54px); align-items: start; }
.product-top h1 { font-size: clamp(44px, 6.4vw, 92px); }
.product-top .lede { margin-top: 24px; }
.product-shots { display: grid; gap: 16px; }

@media (max-width: 900px) {
  .product-top { grid-template-columns: 1fr; }
  .product-shots { order: -1; }
}

/* ---------- part numbers ----------

   A table, because it is a table. Zebra part numbers are long, look
   alike, and get transcribed into purchase orders - so they are set in
   the mono face where a 0 and an O are told apart, and they are
   selectable text rather than an image.

   Price is stated ex GST in the column head, once, rather than repeated
   against every row. A number that might be either is worse than no
   number. */
.parts { width: 100%; border-collapse: collapse; margin-top: 8px; }
.parts caption { text-align: left; font-size: 15px; color: var(--concrete); margin-bottom: 18px; max-width: 74ch; }
.parts th {
  text-align: left; font: 500 11.5px/1.3 var(--mono);
  letter-spacing: .14em; text-transform: uppercase; color: var(--concrete);
  padding: 0 18px 14px 0; border-bottom: 1px solid var(--steel-2); vertical-align: bottom;
}
.parts td { padding: 18px 18px 18px 0; border-bottom: var(--rule); vertical-align: top; font-size: 15.5px; color: var(--chalk); }
.parts td.sku { font: 500 14.5px/1.4 var(--mono); color: var(--bright); white-space: nowrap; }
.parts td.desc { color: var(--concrete); }
.parts td.price { font: 600 16px/1.4 var(--body); color: var(--bright); white-space: nowrap; text-align: right; padding-right: 0; }
.parts th.price { text-align: right; padding-right: 0; }
.parts tr:last-child td { border-bottom: 0; }

@media (max-width: 720px) {
  .parts, .parts tbody, .parts tr, .parts td { display: block; width: 100%; }
  .parts thead { display: none; }
  .parts tr { border-bottom: var(--rule); padding: 16px 0; }
  .parts td { border-bottom: 0; padding: 2px 0; }
  .parts td.price { text-align: left; }
}

/* Availability, in a table cell.
 *
 * Named avail-* rather than .in and .ask because both of those are taken -
 * .ask is the question band on the home page, a full-width bordered
 * section, and a <span class="ask"> here would have inherited the lot.
 * That is the same collision .say caused, caught this time before it
 * shipped.
 *
 * Green is deliberately not used. Red is the only colour on this site with
 * a job, and adding a second one to say "yes" would make the page argue
 * with itself. In stock is simply white and definite; everything else is
 * quieter than the part number beside it, because what a customer came for
 * is the number. */
.avail-in   { color: var(--bright); font-weight: 600; }
.avail-low  { color: var(--signal); font-weight: 600; }
.avail-when { color: var(--chalk); }
.avail-ask  { color: var(--concrete); }

/* The spares table carries a part number in almost every cell, so it wants
   to breathe less than the price tables do. */
.spares td { padding-top: 13px; padding-bottom: 13px; }
.spares td:first-child { color: var(--bright); }

/* ---------- quantity breaks ----------

   Kept out of the parts table on purpose. The breaks differ per product -
   2 and 4 on one, 10 and 30 on another - so columns would leave holes,
   and an empty cell in a price table reads as information withheld
   rather than as a break that does not exist.

   The saving is stated in dollars rather than as a percentage, because
   nobody buying five scanners wants to do the multiplication, and the
   whole point of putting prices on the site is that a customer can work
   out what they owe without ringing first. */
.breaks { margin-top: 34px; border: var(--rule); background: var(--ink-2); padding: clamp(22px, 2.6vw, 32px); }
.breaks h4 { color: var(--bright); margin-bottom: 6px; }
.breaks .when { font-size: 15px; color: var(--concrete-2); margin-bottom: 22px; }
.breaks ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; background: var(--edge); border: var(--rule); }
.breaks li {
  background: var(--ink); padding: 16px 20px;
  display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap;
}
.breaks .qty { font: 900 24px/1 var(--display); color: var(--signal); min-width: 62px; }
.breaks .each { font: 600 17px/1 var(--body); color: var(--bright); }
.breaks .save { font: 400 15px/1.4 var(--body); color: var(--concrete-2); margin-left: auto; }

/* ---------- upgrade paths ----------

   Old device on the left, current one on the right, an arrow between. It
   is a table of two columns and it could have been one - but a customer
   arriving here is looking for one row, their own, and scanning a list of
   model numbers for the one that matches is faster when each pair sits on
   its own line with space around it.

   The old model is the larger type, deliberately. They came here knowing
   what they have and not knowing what replaces it. */
.paths { display: grid; gap: 1px; background: var(--edge); border: var(--rule); }
.path {
  background: var(--ink); padding: 24px 26px;
  display: grid; grid-template-columns: 1fr auto 1fr; gap: 24px; align-items: center;
}
.path .from > b, .path .from > .words > b, .path .to > b {
  display: block; font: 900 24px/1.05 var(--display); color: var(--bright); text-transform: uppercase;
}
.path .from > b, .path .from > .words > b { color: var(--concrete); }
.path .to > b { color: var(--bright); }
.path span { display: block; font-size: 14.5px; line-height: 1.55; color: var(--concrete-2); margin-top: 8px; }
.path .arrow { font: 400 26px/1 var(--body); color: var(--signal); }
.path .to .era { color: var(--signal); font: 500 11px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; margin-bottom: 8px; }

@media (max-width: 720px) {
  .path { grid-template-columns: 1fr; gap: 10px; }
  .path .arrow { transform: rotate(90deg); width: 26px; }
}

/* A photograph of the machine somebody actually has, beside its model
   numbers. Recognising your own device by sight is faster than reading
   four part numbers you have never had to say out loud, and these are
   our own - the rental fleet that ran to the end of 2024, still
   switched on. The .from cell becomes a row; the words need their own
   box or they would sit beside each other rather than stacked. */
.path .from.with-shot { display: flex; gap: 16px; align-items: flex-start; }
.path .from .words { min-width: 0; }
.path .shot {
  flex: none; width: 160px; display: block;
  border: var(--rule); background: var(--sunken-2);
}
.path .shot img { display: block; width: 100%; height: auto; }
a.shot { transition: border-color .18s ease; }
a.shot:hover { border-color: var(--signal); }

/* The current model is a destination, so it reads as one. */
.path .to b a { color: inherit; text-decoration: none; box-shadow: inset 0 -3px 0 var(--signal); transition: color .18s ease; }
.path .to b a:hover { color: var(--signal); }

@media (max-width: 720px) {
  .path .shot { width: 116px; }
}

/* ---------- the receipt ----------

   One paragraph on the Windows Mobile page says we kept more than fifty
   rental devices alive to the end of 2024. This puts the shelf they sat
   on next to it. Deliberately small: the photograph is 320 px wide and
   from 2013, so 200 is as large as it can go without going soft, and a
   snapshot that looks like a snapshot is the right register for it.

   Scoped tightly, unlike the .path rules that had to be fixed an hour
   after they went up. */
.then { display: flex; gap: clamp(18px, 2.4vw, 30px); align-items: flex-start; max-width: 70ch; margin-top: 20px; }
.then .shot-then { flex: none; width: 200px; margin: 0; }
.then .shot-then img { display: block; width: 100%; height: auto; border: var(--rule); }
.then .shot-then figcaption { font-size: 13px; line-height: 1.5; color: var(--concrete); margin-top: 10px; }
.then p.lede { margin: 0; }

@media (max-width: 620px) {
  .then { flex-direction: column; gap: 18px; }
  .then .shot-then { width: 100%; max-width: 280px; }
}

/* ---------- downloads ----------

   Lifted from the Stocktakes Online stylesheet rather than reinvented.
   The two sites are siblings and a customer who has seen one should
   recognise the other; a datasheet list is exactly the sort of furniture
   that has no business looking different between them.

   The row slides right on hover instead of lighting up, which reads as
   the file coming towards you. Size is stated because a 6.9 MB reference
   guide on a phone in a warehouse is a decision, not a click. */
.downloads { border-top: var(--rule); }
.dl {
  display: flex; align-items: center; gap: 18px; padding: 20px 4px;
  border-bottom: var(--rule); text-decoration: none;
  transition: padding-left .25s ease, background .25s ease;
}
.dl:hover { padding-left: 14px; background: var(--sunken); }
.dl .kind {
  font: 600 10.5px var(--mono); letter-spacing: .14em; text-transform: uppercase;
  color: var(--on-signal-2); background: var(--signal); padding: 5px 9px; flex: none;
}
.dl .kind.alt { background: var(--steel-2); color: var(--chalk); }
.dl b { font-family: var(--display); font-weight: 700; font-size: 17px; color: var(--bright); display: block; }
.dl span span { font: 400 13px var(--mono); color: var(--concrete-2); }
.dl .go { margin-left: auto; font: 600 13px var(--mono); color: var(--signal); flex: none; }

/* On a phone the chip and the arrow fight the title for the same line. */
@media (max-width: 620px) {
  .dl { flex-wrap: wrap; gap: 10px; }
  .dl .go { margin-left: 0; }
}

/* ---------- unfinished ----------

   A page can be built before every fact about it is known, but it must
   never go out that way. This marker is loud on purpose, and check.mjs
   should refuse to publish any page containing one - the same principle
   as the enquiry form's placeholder action. A quietly blank price is
   how a customer gets quoted nothing at all. */
.todo {
  display: inline-block;
  background: var(--signal-dim); border: 1px dashed var(--signal);
  color: var(--bright); font: 500 12.5px/1.3 var(--mono);
  letter-spacing: .06em; padding: 5px 10px; border-radius: 2px;
}
.todo-block { border-left: 3px solid var(--signal); background: var(--signal-dim); padding: 22px 26px; margin: 26px 0; }
.todo-block h3 { margin-bottom: 12px; }
.todo-block p, .todo-block li { font-size: 15.5px; color: var(--chalk); }
.todo-block ul { margin: 0; padding-left: 20px; }
.todo-block li { margin-bottom: 8px; }

/* ---------- breadcrumb ---------- */

.crumb { font: 500 11.5px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--concrete); margin-bottom: 20px; }
.crumb a { color: var(--concrete); text-decoration: none; }
.crumb a:hover { color: var(--signal); }
.crumb i { font-style: normal; padding: 0 8px; color: var(--steel-2); }

/* ---------------------------------------------------------------------
   The scanner chooser
   ---------------------------------------------------------------------
   Every competitor's scanner page is the same page: a dropdown marked
   "filter by", a wall of identical black scanners, and Zebra's own
   marketing text pasted under each one. A customer who does not already
   know what they want leaves knowing no more than when they arrived.

   So this filters on the four questions that actually decide it, and
   nothing else:

     what the barcode looks like   1D bars or a 2D square
     how it connects               a cable, or a battery and a cradle
     where it lives                a counter, or a warehouse
     how far it has to read from   arm's length, or across an aisle

   Those are the only four that change the answer. Colour, weight, scan
   rate and decode range in millimetres are on the spec sheet, and the
   spec sheet is linked from the product page, where somebody comparing
   two shortlisted scanners will actually want it.

   Each filter carries a drawn glyph as well as its words, because the
   difference between a 1D and a 2D barcode is a picture, not a sentence.
   Somebody who has never heard the words "linear imager" can still point
   at the barcode that looks like theirs.
   --------------------------------------------------------------------- */

.chooser { border: var(--rule); background: var(--ink-2); padding: clamp(22px, 2.6vw, 34px); }

/* The shortcut row. Four filters is not many, but it is still four
   decisions before a single scanner is visible, and a customer who knows
   their trade but not the jargon should not have to make any of them.
   "A shop counter" sets three filters at once and is a sentence anybody
   can answer about themselves. */
.quickpick { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; margin-bottom: 26px; }
.quickpick > span {
  font: 500 11.5px/1 var(--mono); letter-spacing: .16em;
  text-transform: uppercase; color: var(--concrete); margin-right: 4px;
}
.qp {
  background: transparent; border: 1px solid var(--steel-2); color: var(--chalk);
  font: 600 14.5px/1 var(--body); padding: 11px 18px; border-radius: 2px;
  cursor: pointer; transition: border-color .16s, color .16s, background .16s;
}
.qp:hover { border-color: var(--signal); color: var(--bright); }

.filters { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--edge); border: var(--rule); }
.fgroup { background: var(--ink); padding: 20px 22px 22px; }
/* All four headings are short enough to sit on one line, and they are
   written to stay that way. The reserved height is the guard: the moment
   one of them wraps, its group's buttons start lower than the other
   three and the whole row goes crooked - which is a copy change breaking
   a layout in a place nobody would think to look. Reserving the second
   line means a longer label costs nothing. */
.fgroup h4 {
  font: 500 11.5px/1.3 var(--mono); letter-spacing: .16em; text-transform: uppercase;
  color: var(--concrete); margin-bottom: 10px; font-weight: 500;
  min-height: 2.6em;
}

.chip {
  display: flex; align-items: center; gap: 11px; width: 100%;
  background: transparent; border: 1px solid var(--steel-2); color: var(--chalk);
  font: 500 15px/1.2 var(--body); text-align: left;
  padding: 10px 13px; border-radius: 2px; margin-bottom: 8px;
  cursor: pointer; transition: border-color .16s, background .16s, color .16s;
}
.chip:last-child { margin-bottom: 0; }
.chip:hover { border-color: var(--concrete); color: var(--bright); }

/* Pressed, not merely hovered. aria-pressed carries the state for a
   screen reader and for the stylesheet both, so there is one source of
   truth about whether a filter is on. */
.chip[aria-pressed="true"] { border-color: var(--signal); background: var(--signal-dim); color: var(--bright); }

.chip svg { width: 22px; height: 22px; flex: 0 0 22px; stroke: currentColor; fill: none; stroke-width: 1.6; }
.chip[aria-pressed="true"] svg { stroke: var(--signal); }
.chip .cap { flex: 1; }
.chip .n { font: 400 12.5px/1 var(--mono); color: var(--concrete); }
.chip[aria-pressed="true"] .n { color: var(--signal); }

/* The count is the honest part. A filter combination that matches
   nothing must say so out loud rather than silently showing an empty
   page, which reads as a broken site rather than an answer. */
.tally {
  display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap;
  margin: 26px 0 0; padding-top: 22px; border-top: var(--rule);
}
.tally p { font: 500 15px/1.4 var(--body); color: var(--chalk); margin: 0; }
.tally p b { color: var(--bright); }
.tally button {
  background: transparent; border: 0; cursor: pointer; padding: 0;
  font: 500 14px/1 var(--body); color: var(--concrete); text-decoration: underline;
  text-underline-offset: 4px;
}
.tally button:hover { color: var(--signal); }
.tally button[hidden] { display: none; }

/* ---------- the cards ---------- */

.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; background: var(--edge); border: var(--rule); margin-top: 34px; }

.card {
  background: var(--ink); text-decoration: none; color: inherit;
  display: flex; flex-direction: column; position: relative;
  transition: background .16s;
}
.card:hover { background: var(--ink-2); }
.card[hidden] { display: none; }

/* Product photography arrives from Zebra on white and at wildly
   different crops. A fixed square with the image contained inside it is
   the only way nine of them line up as a row rather than a jumble. */
.card .shot {
  aspect-ratio: 4 / 3; background: var(--white-plate); display: flex; align-items: center; justify-content: center;
  padding: 22px; overflow: hidden;
}
.card .shot img { max-height: 100%; width: auto; object-fit: contain; }

/* A card whose photograph has not arrived yet. Deliberately not white and
   deliberately not empty: an empty white square reads as an image that
   failed to load, which makes the site look broken rather than unfinished.
   This says which it is. */
.card .shot.pending {
  background: var(--ink-2);
  background-image: repeating-linear-gradient(
    -45deg, transparent 0 9px, var(--hatch) 9px 18px);
  flex-direction: column; gap: 12px; color: var(--concrete);
}
.card .shot.pending svg { width: 44px; height: 44px; stroke: var(--steel-2); fill: none; stroke-width: 1.4; }
.card .shot.pending span {
  font: 500 10.5px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase;
}

.card .body { padding: 22px 24px 26px; display: flex; flex-direction: column; flex: 1; }
.card h3 { margin-bottom: 8px; }
.card h3 em { font-style: normal; color: var(--signal); }
.card .role { font-size: 15px; line-height: 1.55; color: var(--concrete); margin-bottom: 16px; }

.card .tags { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 18px; }
.tag {
  font: 500 11px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--concrete); border: 1px solid var(--steel-2); padding: 5px 8px; border-radius: 2px;
}

.card .foot { margin-top: auto; display: flex; align-items: baseline; justify-content: space-between; gap: 14px; }
.card .cost { font: 900 30px/1 var(--display); color: var(--bright); }
.card .cost small { font: 500 12px/1 var(--mono); color: var(--concrete); display: block; margin-bottom: 6px; letter-spacing: .1em; text-transform: uppercase; }
.card .go { font: 600 14px/1 var(--body); color: var(--signal); }

/* Eight of the nine product pages are not written yet. A card that
   silently leads to a 404 is worse than a card that says so - and
   check.mjs refuses to publish while any of these links point at a file
   that does not exist, which is the real guard. This badge is for the
   person clicking around locally in the meantime. */
.card .soon {
  position: absolute; top: 0; right: 0;
  font: 500 10.5px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase;
  background: var(--signal); color: var(--on-signal); padding: 6px 9px;
}

.empty { border: var(--rule); background: var(--ink-2); padding: clamp(30px, 4vw, 50px); text-align: center; margin-top: 34px; }
.empty[hidden] { display: none; }
.empty h3 { margin-bottom: 12px; }
.empty p { color: var(--concrete); max-width: 54ch; margin: 0 auto 20px; }

/* The way out. The button sits in the sentence rather than under it,
   because it is the object of the sentence - "let go of across an aisle"
   reads as one thought, and a button on its own line reads as a second
   decision. */
.empty .loosen {
  color: var(--chalk); display: flex; align-items: center; justify-content: center;
  gap: 10px; flex-wrap: wrap; margin-bottom: 26px;
}
.empty .loosen[hidden] { display: none; }
.empty .loosen .qp { font-size: 14px; padding: 9px 15px; }

@media (max-width: 1000px) {
  .filters { grid-template-columns: repeat(2, 1fr); }
  .cards { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 620px) {
  .filters { grid-template-columns: 1fr; }
  .cards { grid-template-columns: 1fr; }
  .quickpick > span { width: 100%; margin-bottom: 4px; }
}

/* ---------------------------------------------------------------------
   The lineage strip - Symbol, then Motorola, then Zebra
   ---------------------------------------------------------------------
   Each logo sits on a light plate rather than being dropped onto the
   page.

   The practical reason: all three arrived as palette PNGs with no alpha
   channel and a solid white background. On near-black they would be
   three glaring white rectangles with a logo somewhere inside. The plate
   turns an accident into a decision.

   The better reason: two of them are pure black artwork, so the obvious
   trick of inverting them to white would work on Motorola and Zebra and
   ruin Symbol - whose wordmark is struck through by a red laser line,
   which inverts to cyan. That red line is the best thing on this page:
   a scan line, drawn decades ago, in almost exactly the red this site
   already uses for its own. Recolouring somebody else's trademark to
   suit our background is the wrong instinct anyway. Each of these gets
   to look like itself.
   --------------------------------------------------------------------- */

.lineage { display: grid; grid-template-columns: 1fr auto 1fr auto 1fr; align-items: start; margin: 8px 0 44px; }

.era .plate {
  background: var(--paper); border: var(--rule);
  display: flex; align-items: center; justify-content: center;
  padding: clamp(20px, 3vw, 34px); min-height: 116px;
}
/* The artwork is 2000px wide and wildly different in proportion between
   the three. Capping the height rather than the width is what makes them
   read as the same weight instead of Motorola towering over Zebra. */
.era .plate img { max-height: 46px; width: auto; max-width: 100%; }

.era .years {
  font: 500 12px/1 var(--mono); letter-spacing: .14em; color: var(--signal);
  display: block; margin: 18px 0 8px;
}
.era h3 { font-size: clamp(19px, 1.7vw, 23px); margin-bottom: 8px; }
.era p { font-size: 15px; line-height: 1.6; color: var(--concrete); margin: 0; }

.lineage .then {
  display: flex; align-items: center; justify-content: center;
  padding: 0 clamp(10px, 1.6vw, 22px); height: 116px;
  color: var(--steel-2); font-size: 26px;
}

@media (max-width: 860px) {
  .lineage { grid-template-columns: 1fr; }
  /* Turned a quarter turn, so the sequence still reads downwards rather
     than as three unrelated boxes stacked on each other. */
  .lineage .then { height: auto; padding: 16px 0; transform: rotate(90deg); }
}

/* ---------- a heading beside its photograph ----------
   A product page was running: text column with a picture column beside
   it, then a full-width band of heading and lede underneath, then the
   three cells. Two of those bands are mostly empty space - the picture
   column runs out before the text does, and the heading band is one
   short paragraph across the full width.

   Pairing the second photograph with the heading closes both gaps at
   once and takes a screenful off the page. It also puts the picture
   next to the argument it is evidence for, which is where a picture
   should have been in the first place. */
.split {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: clamp(28px, 4vw, 60px); align-items: center;
  margin-bottom: clamp(40px, 5vw, 66px);
}
.split .head { margin-bottom: 0; max-width: none; }
.split figure { margin: 0; }

@media (max-width: 900px) {
  /* Heading first on a narrow screen. The photograph is support for the
     argument and reads as decoration when it arrives before it. */
  .split { grid-template-columns: 1fr; }
  .split figure { order: 2; }
}

/* A caption under an in-use photograph. The plate has had one from the
   start; this one arrived the day a photograph needed to explain itself
   rather than just illustrate. */
.in-use figcaption {
  border-top: var(--rule); padding: 14px 18px;
  font-size: 15px; line-height: 1.6; color: var(--concrete-2);
}
.in-use figcaption b { color: var(--chalk); font-weight: 600; }

/* A link inside body copy.

   "Or ring 0414 53 53 95" under a submit button was carrying
   style="color:#fff" in the markup on eight pages - the fastest way to
   make a link look like a link on a dark site, and invisible the day a
   light theme existed. Andrei found it on the ribbons page. An inline
   style cannot follow a theme, which is the whole argument for the
   variables: a colour written into the HTML is a colour nothing can
   reach.

   Every other link on this site is a button, a nav item or a whole card,
   so until product pages began pointing at each other there was nothing
   to style and `a { color: inherit }` was enough. A sentence telling a
   customer to buy the DS3608 instead needs the DS3608 to look clickable,
   or it is just a sentence and they go back to the list to find it. */
.lede a, .cell p a, .note p a, .step p a, .in-use figcaption a, .parts td a,
.form .actions p a {
  color: var(--bright); text-decoration: underline;
  text-decoration-color: var(--signal); text-decoration-thickness: 1.5px;
  text-underline-offset: 3px; transition: text-decoration-color .16s;
}
.lede a:hover, .cell p a:hover, .note p a:hover, .step p a:hover,
.in-use figcaption a:hover, .parts td a:hover,
.form .actions p a:hover { text-decoration-color: var(--bright); }

/* ---------- the aluminium plate button ----------
   Andrei's idea, and the right one: the button for the asset labels page
   is an asset label. Everything else on this site is a red rectangle,
   and a red rectangle is a poor advertisement for a product whose whole
   argument is that it is made of metal.

   Drawn rather than photographed - a few gradients, two mounting holes
   and an etched barcode. That means it stays sharp at any size, weighs
   nothing, and cannot become the wrong photograph of the wrong label. */
.plate-btn {
  --plate-edge: #969ea5;
  position: relative; display: inline-block; text-decoration: none;
  padding: 18px 46px 16px; border-radius: 3px;
  border: 1px solid var(--plate-edge);
  /* Fine vertical striations over a diagonal sheen: brushed, anodised,
     lit from the top left the way a plate on a machine would be. */
  background:
    repeating-linear-gradient(90deg,
      rgba(255, 255, 255, .55) 0 1px, rgba(0, 0, 0, .035) 1px 2px, transparent 2px 4px),
    linear-gradient(158deg, #eceff1 0%, #c6ccd1 26%, #f1f3f5 50%, #b6bdc3 72%, #dfe4e7 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .85),
    inset 0 -1px 0 rgba(0, 0, 0, .18),
    0 2px 0 rgba(0, 0, 0, .35),
    0 10px 24px rgba(0, 0, 0, .45);
  transition: transform .14s ease, box-shadow .14s ease, filter .14s ease;
}
.plate-btn:hover {
  transform: translateY(-2px);
  filter: brightness(1.05);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .9),
    inset 0 -1px 0 rgba(0, 0, 0, .18),
    0 3px 0 rgba(0, 0, 0, .35),
    0 16px 30px rgba(0, 0, 0, .5);
}

/* Two countersunk mounting holes, because a real plate is fixed to
   something. They are what stops it reading as a grey button. */
.plate-btn::before,
.plate-btn::after {
  content: ''; position: absolute; top: 50%; width: 9px; height: 9px;
  margin-top: -4.5px; border-radius: 50%;
  background: radial-gradient(circle at 40% 35%, #6f767c, #2b3034 70%);
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, .6), 0 1px 0 rgba(255, 255, 255, .7);
}
.plate-btn::before { left: 16px; }
.plate-btn::after  { right: 16px; }

/* Etched, not printed: dark on metal, tight and stamped. */
.plate-btn b {
  display: block; text-align: center;
  font: 900 25px/1 var(--display); text-transform: uppercase;
  letter-spacing: .04em; color: #14181b;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .65);
}
.plate-btn .sub {
  display: block; text-align: center; margin-top: 7px;
  font: 500 10px/1 var(--mono); letter-spacing: .18em; text-transform: uppercase;
  color: #4b5359;
}
.plate-btn .code {
  display: block; height: 15px; margin: 0 auto 10px; max-width: 150px;
  background-image: repeating-linear-gradient(90deg,
    #1a1e21 0 2px, transparent 2px 5px,
    #1a1e21 5px 6px, transparent 6px 11px,
    #1a1e21 11px 14px, transparent 14px 16px,
    #1a1e21 16px 17px, transparent 17px 23px);
  opacity: .8;
}

@media (prefers-reduced-motion: reduce) {
  .plate-btn, .plate-btn:hover { transform: none; transition: none; }
}

/* Two bands in a row read as one grey slab with a seam down the middle.
   The second takes the darker ground so they stay two separate things
   being said, rather than one long one. */
.ask + .ask { background: var(--ink); }

/* ---------- scan to call ----------
   A barcode company whose phone number is a barcode. The old site had
   this and it was the best idea on it; the only thing wrong was the
   number inside, which was the landline.

   White plate because a QR needs its quiet zone and its contrast. On the
   red callout that reads as deliberate rather than as a hole. */
.qr {
  display: inline-flex; align-items: center; gap: 16px;
  background: var(--white-plate); border-radius: 3px; padding: 12px 20px 12px 12px;
  text-decoration: none;
  box-shadow: 0 10px 24px rgba(0, 0, 0, .28);
  transition: transform .14s ease, box-shadow .14s ease;
}
.qr:hover { transform: translateY(-2px); box-shadow: 0 16px 30px rgba(0, 0, 0, .34); }
.qr img { width: 92px; height: 92px; display: block; }
.qr .say { display: block; }
.qr .say b {
  display: block; font: 900 21px/1 var(--display);
  text-transform: uppercase; color: #14181b; letter-spacing: .01em;
}
.qr .say span {
  display: block; margin-top: 7px;
  font: 500 12px/1 var(--mono); letter-spacing: .1em; color: #5a626a;
}

/* On a phone there is nothing to scan it with but the phone itself, so
   the tile stops pretending and the tap target beside it does the work. */
@media (max-width: 620px) { .qr { display: none; } }
@media (prefers-reduced-motion: reduce) { .qr, .qr:hover { transform: none; transition: none; } }

/* The same tile with nothing beside it, for a panel that already has its
   own heading and paragraph. */
.qr.bare { padding: 12px; }

/* Scan-to-call and call-us are the same action twice when they sit side
   by side. The tile is a tel: link in its own right, so it covers the
   desktop on its own; the button covers the phone, where a QR code is a
   picture of a thing you are already holding. Never both at once. */
.call-swap { display: flex; align-items: center; }
.call-swap .btn { display: none; }
@media (max-width: 620px) {
  .call-swap .btn { display: inline-flex; }
}

/* ---------------------------------------------------------------------
   The label finder on labels.html.

   144 rows rendered into the page by build-labels.mjs and filtered in
   place, so the whole range is readable with scripting off.

   Drawn size blocks were tried first and were wrong on screen - at true
   scale a 28 mm tag renders as a speck, which reads as a fault rather
   than as a small label. The trade's photographs go in instead, and the
   dimensions lead in the text underneath.
   --------------------------------------------------------------------- */

.find .lbl-ask { display: grid; gap: 26px; margin-bottom: 34px; }
@media (min-width: 900px) { .find .lbl-ask { grid-template-columns: repeat(3, 1fr); gap: 34px; } }

.lbl-step { border: 0; border-top: var(--rule); padding: 18px 0 0; margin: 0; min-width: 0; }
.lbl-step legend {
  font: 500 11.5px/1.3 var(--mono); letter-spacing: .16em; text-transform: uppercase;
  color: var(--concrete); padding: 0 10px 0 0;
}
.lbl-step legend b { color: var(--signal); }

.fchips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.fchip {
  background: transparent; border: 1px solid var(--steel-2); color: var(--chalk);
  font: 500 14px/1.2 var(--body); padding: 8px 13px; border-radius: 2px;
  cursor: pointer; transition: border-color .16s, background .16s, color .16s;
}
.fchip small { display: block; font: 400 11px/1.4 var(--mono); color: var(--concrete); margin-top: 3px; }
.fchip:hover { border-color: var(--concrete); color: var(--bright); }
.fchip[aria-pressed="true"] { border-color: var(--signal); background: var(--signal-dim); color: var(--bright); }
.fchip[aria-pressed="true"] small { color: var(--concrete-2); }

.lbl-size-in { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; margin-top: 12px; }
.lbl-size-in label { font: 500 14px/1.2 var(--body); color: var(--chalk); }
.lbl-size-in input {
  width: 84px; margin: 0 6px; background: var(--ink-2); color: var(--bright);
  border: 1px solid var(--steel-2); border-radius: 2px; padding: 8px 10px;
  font: 500 15px/1.2 var(--mono);
}
.lbl-size-in input:focus { outline: 2px solid var(--signal); outline-offset: 1px; }
.lbl-size-in .muted { flex-basis: 100%; font-size: 13px; }

.lbl-count { font: 500 12.5px/1.3 var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--concrete); margin-bottom: 18px; }
.lbl-none { color: var(--chalk); font-size: 16px; margin-top: 8px; }

.lbl-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
@media (min-width: 700px)  { .lbl-list { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1060px) { .lbl-list { grid-template-columns: repeat(3, 1fr); } }

.lbl {
  display: flex; gap: 16px; align-items: flex-start;
  border: var(--rule); background: var(--ink-2); padding: 14px 16px;
}
.lbl[hidden] { display: none; }

/* The photograph. Square, because the trade's own shots are 400 x 400 and
   scaling a square into a rectangle is how a label ends up looking like a
   different shape from the one you are buying.

   The dimensions still lead in the text. The picture says "this is a roll
   of white labels" - which is honest, because that is what most of them
   are - and the numbers underneath say which one. */
/* The picture is a button. Clicking it shows the label four times bigger,
   which is all the trade's 400 square shots have to give - but four times
   is the difference between a thumbnail and something you can judge. */
.lbl-pic {
  flex: 0 0 92px; width: 92px; height: 92px; padding: 0;
  background: var(--paper); border: 1px solid transparent; border-radius: 2px;
  cursor: zoom-in; transition: border-color .16s;
}
.lbl-pic img { width: 100%; height: 100%; object-fit: contain; display: block; }
.lbl-pic:hover, .lbl-pic:focus-visible { border-color: var(--signal); }

/* RIBBONS ONLY, and the reason is arithmetic rather than taste.
 *
 * A label is a square photograph. A ribbon is a roll standing on its end,
 * and the whole point of the fifteen new photographs is that the roll's
 * HEIGHT is the ribbon's width - 161px of roll at 40mm, 645px at 220mm,
 * all at one scale with the diameter fixed at 179px.
 *
 * Every one sits on an identical 240 x 680 canvas. That is not
 * decoration: object-fit: contain scales an image until the LIMITING side
 * fits, so the moment two canvases differ in shape, contain does the
 * scaling instead of the photograph. The first set arrived 400x400 for
 * everything up to 110mm and 400x696 for the 220mm, which rendered the
 * widest ribbon on the page as the narrowest thing on it - 53px against
 * 92px. One canvas is what makes the comparison true.
 *
 * AND THE KEY IS WIDTH **AND** LENGTH, not width. At one scale the roll
 * diameter is 87px at 70m, 179px at 300m and 213px at 450m, because a 70
 * metre desktop ribbon is a different object - half the diameter, on a
 * 13mm core. Keyed on width alone, every 70m row got an industrial roll
 * and Andrei spotted it inside a minute.
 *
 * So the frame has to be the canvas's shape: 60 x 170 is 0.3529 against
 * the canvas's 0.3529. Get that ratio wrong and white space comes back as
 * a border on two sides. */
#rib-list .lbl-pic { flex: 0 0 60px; width: 60px; height: 170px; }
#rib-list .lbl { align-items: flex-start; }

/* The part number asks for a price. It is the thing a customer would have
   copied out and typed into the form, so it does that itself. */
.lbl-part {
  background: none; border: 0; padding: 0; text-align: left; cursor: pointer;
  font: 400 11.5px/1.3 var(--mono); color: var(--concrete);
  text-decoration: underline; text-decoration-color: var(--steel-2);
  text-underline-offset: 3px; transition: color .16s, text-decoration-color .16s;
}
.lbl-part:hover, .lbl-part:focus-visible { color: var(--signal); text-decoration-color: var(--signal); }

/* ---------- the enlarged label ---------- */
.lbox {
  position: fixed; inset: 0; z-index: 60; display: flex;
  align-items: center; justify-content: center; padding: 24px;
  background: rgba(6, 7, 8, .88);
}
.lbox[hidden] { display: none; }
.lbox-in {
  position: relative; background: var(--ink-2); border: var(--rule);
  padding: 26px; max-width: 460px; width: 100%; text-align: center;
}
.lbox-in img {
  width: 100%; max-width: 340px; height: auto; background: var(--paper);
  border-radius: 2px; margin: 0 auto 18px;
}
/* A LABEL IS WIDE, A RIBBON IS TALL.
 *
 * The rule above caps the picture at 340px WIDE, which is right for a
 * square label photograph and absurd for a ribbon: the 240 x 680 roll
 * shots come out 963px high, so the size, the description and the
 * enquiry button all sit below the fold and the enlarged view looks
 * broken. Cap ribbons by height instead and let the width follow. */
.lbox-tall .lbox-in img {
  width: auto; max-width: 100%; max-height: min(58vh, 520px);
  margin-inline: auto;
}
.lbox-in h3 { font: 600 22px/1.1 var(--body); color: var(--bright); margin-bottom: 8px; }
.lbox-in p { font-size: 14.5px; color: var(--concrete-2); margin-bottom: 6px; }
.lbox-part { font: 400 12px/1.3 var(--mono) !important; color: var(--concrete) !important; margin-bottom: 20px !important; }
.lbox-x {
  position: absolute; top: 6px; right: 10px; background: none; border: 0;
  color: var(--concrete); font-size: 30px; line-height: 1; cursor: pointer; padding: 4px 8px;
}
.lbox-x:hover { color: var(--bright); }

.lbl-body { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.lbl-body b { font: 600 17px/1.2 var(--body); color: var(--bright); }
.lbl-col { font-size: 13.5px; color: var(--concrete-2); }
.lbl-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 2px; }
.lbl-chips i {
  font: 500 11px/1.3 var(--mono); font-style: normal; letter-spacing: .04em;
  color: var(--concrete-2); border: 1px solid var(--steel-2); border-radius: 2px; padding: 3px 6px;
}
.lbl-body .lbl-part { margin-top: 2px; align-self: flex-start; }

/* Offered, not applied. If a size finds nothing but would find something
   the other way round, say so and let the customer decide - quietly
   answering a different question is how "40 across" came back with labels
   100 across and 40 down. */
.lbl-swap { color: var(--chalk); font-size: 16px; margin-top: 10px; }
.lbl-swap[hidden] { display: none; }
.lbl-swap button {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; color: var(--signal); text-decoration: underline;
  text-underline-offset: 3px;
}
.lbl-swap button:hover { color: var(--bright); }

/* A portrait, kept small on purpose. The wide photographs on the about
   page are evidence - a deskful of terminals, a wall of scanners - and
   they earn their width. A studio headshot is not evidence of anything,
   so it sits at the size of a person in a room rather than a hero. */
.photo-portrait { max-width: 232px; margin: 0 0 clamp(30px, 4vw, 46px); }
.photo-portrait img { width: 100%; height: auto; border-radius: 2px; display: block; }
.photo-portrait figcaption {
  font-size: 13.5px; line-height: 1.5; color: var(--concrete); margin-top: 12px;
}
@media (min-width: 900px) {
  .photo-portrait { float: right; margin-left: clamp(28px, 4vw, 54px); }
}

/* The Windows Mobile lineup on the home page.
   Kept narrow so the band stays what it is - a statement, a paragraph and
   one door. The photograph is there to be recognised, not admired: a
   reader who would scroll past the words knows the shape of the thing on
   their own charging cradle. */
.ask-shot { margin: 22px 0 0; max-width: 560px; }
.ask-shot img { width: 100%; height: auto; border-radius: 2px; display: block; }
.ask-shot figcaption {
  font-size: 13.5px; line-height: 1.55; color: var(--concrete); margin-top: 12px;
}
.ask-shot figcaption b { color: var(--chalk); }
@media (max-width: 720px) {
  .ask-shot { margin-bottom: 22px; }
}

/* A label whose photograph has not arrived yet. Says so rather than
   linking a file that is not there - a broken image reads as a broken
   site, and this fixes itself the moment the photograph lands. */
.lbl-nopic {
  display: flex; align-items: center; justify-content: center;
  background: var(--steel); border: 1px dashed var(--steel-2); cursor: default;
}
.lbl-nopic span {
  font: 400 10.5px/1.35 var(--mono); color: var(--concrete);
  text-transform: uppercase; letter-spacing: .08em; text-align: center;
}

/* A manufacturer's product video, where a product page has one. Sized and
   framed exactly like the photograph it replaces, so the page does not
   jump when one becomes the other. */
.in-use video {
  width: 100%; height: auto; display: block; border-radius: 2px;
  background: var(--ink);
}

/* ---------- clearance ----------

   Old stock, new in the box, $99. The page has one job: show what is in
   the carton and how many are left, without dressing it up. Everything
   is prefixed clr- because the last two rules added here reached further
   than intended and had to be scoped an hour later.

   The photograph is the argument on this page - both DS4801s still have
   the factory film on them - so it gets the top of the card and full
   width, not a thumbnail. */
.clr { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; background: var(--edge);
       border: var(--rule); grid-template-columns: repeat(3, 1fr); }
.clr-item { background: var(--ink); display: flex; flex-direction: column; }
.clr-item img { display: block; width: 100%; height: auto; }
.clr-body { padding: 20px 22px 24px; display: flex; flex-direction: column; flex: 1; }
.clr-n { font: 500 11px/1.3 var(--mono); letter-spacing: .1em; color: var(--signal);
         word-break: break-all; }
.clr-item h3 { font: 900 19px/1.15 var(--display); color: var(--bright); text-transform: uppercase;
               margin: 10px 0 0; }
.clr-cond { font-size: 14px; line-height: 1.5; color: var(--concrete); margin: 10px 0 0; }
.clr-box { list-style: none; margin: 14px 0 0; padding: 0; }
.clr-box li { font-size: 13.5px; line-height: 1.5; color: var(--concrete-2); padding-left: 16px;
              position: relative; }
.clr-box li::before { content: '+'; position: absolute; left: 0; color: var(--signal); }
.clr-foot { margin-top: auto; padding-top: 20px; display: flex; align-items: baseline;
            justify-content: space-between; gap: 12px; }
.clr-price { font: 900 30px/1 var(--display); color: var(--bright); }
.clr-qty { font: 500 11.5px/1.3 var(--mono); letter-spacing: .1em; text-transform: uppercase;
           color: var(--concrete); text-align: right; }
.clr-gone { color: var(--signal); }

@media (max-width: 1000px) { .clr { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px)  { .clr { grid-template-columns: 1fr; } }

/* ---------- the staging checklist ----------

   Everybody in this trade says "we can stage them" and nobody says what
   that means. The whole value of the list is its dullness: install
   batteries, attach wrist straps, label the power supplies. Set as a
   plain list so it reads as a job sheet rather than a feature grid,
   because a job sheet is what it is.

   stg- prefixed, no bare element selectors. */
.stg-list { list-style: none; margin: 16px 0 0; padding: 0; }
.stg-list li { font-size: 14.5px; line-height: 1.65; color: var(--concrete-2);
               padding-left: 20px; position: relative; }
.stg-list li::before { content: '+'; position: absolute; left: 0; color: var(--signal); }
.stg-list li b { color: var(--chalk); font-weight: 600; }
.stg-when { font: 500 11px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase;
            color: var(--signal); display: block; margin-bottom: 6px; }

/* ---------- the Android part number cards ----------
   Written by build-mobile-computers.mjs. mc- prefixed. */
.mc-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 0; }
.mc-chips i {
  font: 500 11px/1 var(--mono); letter-spacing: .06em; font-style: normal;
  color: var(--concrete); background: var(--sunken); border: var(--rule);
  padding: 6px 9px;
}
/* p.mc-avail, not .mc-avail - and that one character is the whole point.
   Found 12-09-2026 while adding the price beside it.

   `.cell p` on line 494 sets 15.5px and --concrete. It scores 0,1,1.
   A bare `.mc-avail` scores 0,1,0, so it LOST every time regardless of
   coming twelve hundred lines later, and the availability line has been
   rendering grey at 15.5px on both product pages since they were built -
   never the 11.5px signal red it asks for here.

   Nothing looked broken, which is why it survived: a grey uppercase
   line in a slightly wrong size still reads as a label. The only way it
   surfaced was measuring the computed style in a browser rather than
   reading the stylesheet and assuming.

   Tying it to the element rather than to .cell keeps it working if the
   card markup ever changes shape. */
p.mc-avail { font: 500 11.5px/1.4 var(--mono); letter-spacing: .1em; text-transform: uppercase;
             color: var(--signal); margin: 14px 0 0; }

/* The price on a mobile computer card, added 12-09-2026 when these pages
   stopped being quote-only.

   It sits above the availability line and below the chips, because those
   are the two facts somebody is hunting for and they belong together.
   Bigger than the body but not a hero number - the argument on these
   pages is the configuration, and a price set in 40px would tell the
   reader the opposite.

   "inc GST" is not small print. Half the competitors reviewed quote ex
   GST, so the three characters after the number are the difference
   between comparing like with like and not. */
p.mc-price { margin: 16px 0 0; font: 700 22px/1 var(--display); color: var(--bright); }
p.mc-price span { font: 500 11.5px/1 var(--mono); letter-spacing: .08em;
                 color: var(--concrete); margin-left: 8px; }
p.mc-price-ask { font: 600 14px/1.45 var(--body); color: var(--concrete); }
p.mc-price-ask b { color: var(--bright); }

/* ---------- reading a part number ----------

   The decoder on the MC3400 page. Zebra publish no key to this that we
   could find; it was worked out from 56 part numbers and the page says
   so. Monospace because the alignment IS the explanation, and it scrolls
   inside itself rather than making the page scroll sideways on a phone.

   pn- prefixed. */
.pn-break {
  font: 500 13px/1.7 var(--mono); color: var(--chalk);
  background: var(--sunken); border: var(--rule); padding: 20px 22px;
  overflow-x: auto; white-space: pre; margin: 0 0 24px;
}
.pn-break b { color: var(--signal); font-weight: 500; }
.pn-break i { color: var(--concrete); font-style: normal; }
.pn-legend { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px;
             background: var(--edge); border: var(--rule); }
.pn-legend > div { background: var(--ink); padding: 18px 20px; }
.pn-legend h4 { font: 500 10.5px/1 var(--mono); letter-spacing: .16em; text-transform: uppercase;
                color: var(--signal); margin: 0 0 12px; }
.pn-legend dl { margin: 0; font-size: 14px; line-height: 1.6; }
.pn-legend dt { font: 500 13px/1.6 var(--mono); color: var(--bright); float: left; width: 2.2em; }
.pn-legend dd { margin: 0 0 6px 2.4em; color: var(--concrete-2); }

@media (max-width: 760px) { .pn-legend { grid-template-columns: 1fr; } }

/* ---------- Part & Qty Check ----------

   The first custom app that has a page of its own. Written by another
   session against the Stocktakes Online variable names and handed over
   as a fragment with its own <style> block; most of that block was
   layout this site already had - .head, .grid, .steps, .step, .bars,
   .eyebrow.plain, .small - so what is left here is only the three
   things that are genuinely new.

   pqc- prefixed, the same as clr- and pn-, because the last component
   added without a prefix reached further than it was meant to.
   --------------------------------------------------------------------- */

/* A pulled quote. Not .note - that is a bordered box for an aside. This
   is one sentence at heading weight with a rule beside it, used twice on
   the page and both times for the sentence the section exists to make. */
.pqc-quote {
  border-left: 3px solid var(--signal);
  padding: 6px 0 6px 22px; margin: 34px 0; max-width: 64ch;
}
.pqc-quote p {
  font: 700 clamp(19px, 1.9vw, 25px)/1.42 var(--display);
  letter-spacing: -.01em; color: var(--bright); margin: 0;
}

/* ---------- the two device screens ----------

   The one place on this site with literal colours that are not identity
   and do not follow the theme, and it is deliberate.

   They are the app's own colours, read out of its colors.xml rather than
   chosen to suit the page. And the screens stay LIGHT in both themes,
   because the app's theme is Light on purpose: that was fixed after a
   scanner arrived with dark mode switched on and the login screen
   rendered white text on white fields. A mock that flipped with the site
   toggle would be showing a product that does not exist.

   480x800 is the real panel on these scanners, so the mock keeps that
   proportion rather than a convenient one. */
.pqc-screens {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(22px, 3vw, 44px); align-items: start;
}
.pqc-device { max-width: 340px; margin: 0 auto; width: 100%; }
.pqc-device .pqc-bezel {
  background: #0a0d0f; border: 1px solid var(--steel-2); border-radius: 6px;
  padding: 14px 12px 26px; box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
}
.pqc-device .pqc-glass {
  aspect-ratio: 480 / 800; background: #F4F7FB; color: #1A2733;
  display: flex; flex-direction: column; overflow: hidden;
}
.pqc-device .pqc-top {
  background: #546E7A; color: #fff; padding: 10px 12px;
  display: flex; align-items: center; justify-content: space-between;
}
.pqc-device .pqc-top b { font: 700 13px var(--body); }
.pqc-device .pqc-top span { font: 500 10.5px var(--mono); letter-spacing: .1em; opacity: .85; }
.pqc-device .pqc-screen { padding: 12px; display: flex; flex-direction: column; gap: 10px; flex: 1; }
.pqc-device .pqc-field { border: 1px solid #cdd6de; background: #fff; padding: 9px 10px; }
.pqc-device .pqc-field .pqc-lbl {
  font: 500 9.5px var(--mono); letter-spacing: .14em; text-transform: uppercase;
  color: #5C6B7A; display: block; margin-bottom: 5px;
}
.pqc-device .pqc-field .pqc-val { font: 600 15px var(--mono); color: #1A2733; word-break: break-all; }
.pqc-device .pqc-field.pqc-done { border-color: #4A7C82; border-left-width: 4px; }
.pqc-device .pqc-result { margin-top: auto; padding: 14px 12px; color: #fff; text-align: center; }
.pqc-device .pqc-result.ok { background: #2E7D32; }
.pqc-device .pqc-result.no { background: #C62828; }
.pqc-device .pqc-result b {
  display: block; font: 900 24px var(--display); letter-spacing: -.01em; line-height: 1.05;
}
.pqc-device .pqc-result span {
  display: block; font: 400 12px/1.45 var(--mono); margin-top: 8px;
  opacity: .92; word-break: break-all;
}
.pqc-device figcaption { margin-top: 18px; font-size: 15px; color: var(--concrete); max-width: 38ch; }
.pqc-device figcaption b { color: var(--bright); font-weight: 600; }

/* ---------- the sample file ----------

   A CSV shown as what it is. The two result colours DO follow the theme,
   unlike the screens above, because here they are text on the page
   rather than a photograph of a device.

   In light they are the app's own green and red, #2E7D32 and #C62828,
   straight out of colors.xml - a light page is the same background the
   app has, so the app's colours are simply correct. In dark they are the
   same two hues lifted to carry on near-black, which is what --signal
   already does relative to the printed brand red.

   Both pairs are in lib/contrast.mjs. The values handed over measured
   5.44 and 4.96 in dark and 3.39 and 3.72 in light, because the light
   theme did not exist when they were chosen. */
.pqc-file { border: var(--rule); background: var(--ink-2); }
.pqc-file .pqc-name {
  font: 500 12.5px var(--mono); letter-spacing: .08em; color: var(--signal);
  padding: 12px 16px; border-bottom: var(--rule);
}
.pqc-file .pqc-sheet { overflow-x: auto; }
.pqc-file table {
  border-collapse: collapse; width: 100%; min-width: 660px;
  font: 400 13.5px var(--mono); font-variant-numeric: tabular-nums;
}
.pqc-file th, .pqc-file td {
  text-align: left; padding: 9px 16px; border-bottom: 1px solid var(--edge); white-space: nowrap;
}
.pqc-file th {
  color: var(--concrete); font-weight: 500; font-size: 11.5px;
  letter-spacing: .12em; text-transform: uppercase;
}
.pqc-file td { color: var(--chalk); }
.pqc-file td.ok { color: var(--pqc-ok); }
.pqc-file td.no { color: var(--pqc-no); }
.pqc-file .pqc-foot {
  padding: 14px 16px; border-top: var(--rule); color: var(--concrete); font-size: 14px;
}

@media (max-width: 900px) {
  .pqc-screens { grid-template-columns: 1fr; }
}

/* ---------- a figure you can open at full size ----------

   Not the labels lightbox. That one carries a title, a part number and
   an "ask for a price" button, because a label thumbnail is a product;
   this is just a picture that is bigger than the space it is in.

   It exists because of one image. The MC9400 keypad chart was a crop off
   a screen render of Zebra's accessory guide, 542px wide and shown at
   542px - already 1:1, so there was nothing to enlarge and a lightbox
   would only have blurred it. Re-rendering page 11 of the guide at 400
   dpi gave a genuinely sharp source, and the small version got smaller
   AND sharper on the way past. Now there is something worth opening.

   The big file is referenced only by the button, so a reader who never
   clicks never downloads it.
   --------------------------------------------------------------------- */
.zoomable {
  display: block; width: 100%; padding: 0; border: 0; background: none;
  cursor: zoom-in; line-height: 0;
}
.zoomable img { display: block; width: 100%; height: auto; }
.zoomable:focus-visible { outline: 2px solid var(--signal); outline-offset: 3px; }

.zbox {
  position: fixed; inset: 0; z-index: 70;
  display: flex; align-items: center; justify-content: center;
  padding: clamp(12px, 3vw, 44px);
  background: rgba(6, 7, 8, .94); cursor: zoom-out;
}
.zbox[hidden] { display: none; }
/* White behind it in both themes: the chart is drawn on white and a dark
   surround would leave it floating on a grey rectangle. */
.zbox img {
  max-width: 100%; max-height: 100%; width: auto; height: auto;
  background: var(--white-plate); border-radius: 2px;
}
/* Literal white, not --bright: this sits on the dark scrim in both
   themes, so it must not follow the palette. */
.zbox-x {
  position: absolute; top: 10px; right: 16px;
  background: none; border: 0; color: #fff; font-size: 34px; line-height: 1;
  cursor: pointer; padding: 6px 10px;
}
.zbox-x:focus-visible { outline: 2px solid var(--signal); outline-offset: 2px; }

/* ---------- MC9300 spare parts ----------

   A grid of parts for a device you already own. It reuses .grid and .cell
   so the tiles behave like every other tile on the site; these two rules
   are the only things that are new.

   The photograph sits on white because that is how the trade supplies
   them - cut out, on white - and because a cradle in a dark box reads as
   a hole in the page. Same reasoning and the same variable as the
   product shots on the section pages.

   mc93- prefixed, like clr- and pn- and pqc-, because the last component
   added here without a prefix reached further than it was meant to.
   --------------------------------------------------------------------- */
.mc93-shot {
  background: var(--white-plate); border: var(--rule);
  aspect-ratio: 1 / 1; display: flex; align-items: center; justify-content: center;
  padding: 14px; overflow: hidden; margin-bottom: 18px;
}
.mc93-shot img { max-width: 100%; max-height: 100%; width: auto; height: auto; }

/* Price, availability and whether it also fits the MC9400, on one line.
   The last of those is the whole compatibility argument stated as data
   rather than as a paragraph, so it earns its place on every card it is
   true of. */
.mc93-line { font-size: 15px; line-height: 1.6; color: var(--concrete); margin-top: 12px; }
.mc93-line b { color: var(--bright); font-weight: 600; }
.mc93-line i { font-style: normal; color: var(--signal); }



/* ---------- the FAQ on /rentals ----------
   Stacked rather than the two-column .grid used elsewhere on that page.
   A question and its answer are one thought, and putting six of them in
   columns makes a reader scan for the one that matches instead of
   reading the one that matters - which here is the first, about what
   happens when a scanner dies mid-count. */
.faq { margin-top: 26px; border-top: var(--rule); }
.qa { padding: 26px 0; border-bottom: var(--rule); max-width: 74ch; }
.qa h3 { font: 800 20px/1.2 var(--display); color: var(--bright);
         text-transform: none; margin: 0 0 12px; }
.qa p { font-size: 15.5px; line-height: 1.62; color: var(--concrete); margin: 0; }
.qa p b { color: var(--chalk); font-weight: 600; }
@media (max-width: 620px) { .qa h3 { font-size: 18px; } }
/* ---------- the rest of the catalogue ----------
   Written by build-mobile-computers.mjs into MC3400-ALL / MC9400-ALL.
   Eighty-six configurations nobody stocks, grouped by keypad.

   Deliberately quieter than the priced cards above it. Those are the
   offer; this is a reference, and if it competed for attention it would
   bury the ten machines that actually have a price and a delivery date.
   Mono for the part number because that is the string somebody copies
   into an email. */
.cfg-all { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1px;
           background: var(--edge); border: var(--rule); margin-top: 26px; }
.cfg-group { background: var(--ink); padding: 20px 20px 22px; }
.cfg-group h4 { font: 800 15px/1.1 var(--display); color: var(--bright);
                text-transform: uppercase; margin: 0 0 12px; }
.cfg-group h4 i { font: 500 11px/1 var(--mono); font-style: normal;
                  letter-spacing: .08em; color: var(--concrete); text-transform: none; }
.cfg-list { list-style: none; margin: 0; padding: 0; }
.cfg-list li { padding: 9px 0; border-top: var(--rule); }
.cfg-list li:first-child { border-top: 0; }
.cfg-list b { display: block; font: 500 12.5px/1.3 var(--mono);
              letter-spacing: .02em; color: var(--chalk); }
.cfg-list span { display: block; font-size: 12.5px; line-height: 1.45;
                 color: var(--concrete); margin-top: 3px; }
@media (max-width: 760px) { .cfg-all { grid-template-columns: 1fr; } }
/* ---------- Code blocks ----------------------------------------------
   First used on /zpl, where the point of the page is a block of ZPL a
   reader copies into Notepad. --sunken has said "code blocks" in its
   own comment since the site was built; this is the rule that finally
   uses it.

   It scrolls sideways rather than wrapping. A wrapped ^FO line is a
   changed line, and somebody copying a broken one gets a broken label.
--------------------------------------------------------------------- */
pre.code {
  margin: 22px 0 0;
  padding: 18px 20px;
  background: var(--sunken);
  border: 1px solid var(--edge);
  border-left: 3px solid var(--signal);
  overflow-x: auto;
  font: 400 13.5px/1.8 var(--mono);
  color: var(--chalk);
  white-space: pre;
}
pre.code b { color: var(--bright); font-weight: 600; }
pre.code i { color: var(--concrete); font-style: normal; }
@media (max-width: 620px) { pre.code { font-size: 12px; padding: 14px 15px; } }


/* ---------- search ----------

   The site had no search until 07-09-2026. Fifty-one pages, eight nav
   headings, and a customer who arrives knowing a string rather than a
   category - a part number off a battery, a label size off a carton -
   had to guess which heading held it.

   WHY AN ICON AND NOT A BOX IN THE HEADER. The header is full. Brand,
   theme button, eight nav items and the Contact button come to roughly
   1098px of content; the wrap is 1240px less two 72px gutters, so 1096px
   is all there is. A text field would push Contact onto a second line at
   the desktop width most customers use. So the header carries a
   magnifier and the box lives on the page it opens, full size and
   already focused. On a phone the nav is a dropped column and the word
   appears beside the icon, because there the room exists.

   The anchor deliberately carries no class of its own. check.mjs decides
   which menu item a page should light by matching <a class="here"> - so
   an anchor that already had a class could never be lit, and /search
   would be the one page in the menu that cannot show you are on it.
   Everything is styled from the span inside instead. */

.nav-search { display: inline-flex; align-items: center; gap: 13px; }
.nav-search svg {
  display: block; width: 18px; height: 18px;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.nav-search i { display: none; font-style: normal; }

/* The search page itself. */
/* The headline is scaled down from the site h1 of clamp(52px, 9vw, 132px).
   Every other page wants you to read the headline; this one wants you to
   reach the box. At the full size the box sat below the fold on a 900px
   window, so landing on /search showed a heading and no way to search.
   Product pages already scale h1 down for their own reason - this is the
   same move for a better one. */
.search-hero h1 { font-size: clamp(34px, 4.6vw, 62px); max-width: 18ch; }

.searchbox {
  display: flex; align-items: center; gap: 15px;
  max-width: 720px; margin-top: 34px; padding: 0 20px;
  background: var(--ink-2);
  border: 1px solid var(--steel-2); border-radius: 2px;
}
.searchbox:focus-within { border-color: var(--signal); }
.searchbox svg {
  flex: none; width: 21px; height: 21px;
  fill: none; stroke: var(--concrete); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.searchbox input {
  flex: 1; min-width: 0;
  background: none; border: 0; outline: none; color: var(--bright);
  font: 400 clamp(17px, 1.7vw, 21px)/1.2 var(--body);
  padding: 21px 0;
}
.searchbox input::placeholder { color: var(--concrete); }

.scount {
  margin-top: 22px; min-height: 14px;
  font: 400 12.5px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--concrete);
}

.sresults { max-width: 820px; margin-top: 8px; }

.sresult { display: block; padding: 22px 0; border-bottom: var(--rule); text-decoration: none; }
/* text-transform is reset because h2 site-wide is the uppercase display
   face. A result is a page title quoted back, and ZEBRA ZT411 - INDUSTRIAL
   PRINTER AT FOUR INCHES shouted in caps is harder to scan than the title
   the page actually carries. Quote it as written. */
.sresult h2 {
  margin: 0; font: 600 20px/1.35 var(--body); color: var(--bright);
  text-transform: none; letter-spacing: 0;
}
.sresult:hover h2 { color: var(--signal); }

/* Why the page came back, in the page's own words - the part number or
   the heading that matched. A list of ten blue links makes a customer
   open all ten; naming the match means they open one. */
.sresult .why { margin: 7px 0 0; font-size: 15px; line-height: 1.55; color: var(--chalk); }
.sresult .surl {
  display: block; margin-top: 10px;
  font: 400 12px/1 var(--mono); letter-spacing: .04em; color: var(--concrete);
}

.shint { max-width: 660px; margin-top: 32px; color: var(--concrete); line-height: 1.75; }
.shint b { color: var(--chalk); font-weight: 600; }
.shint a { color: var(--chalk); }
.shint a:hover { color: var(--signal); }
.noresult { max-width: 660px; margin-top: 26px; color: var(--chalk); line-height: 1.75; }
.noresult a { color: var(--signal); }

/* The nav is a dropped column on a phone, so the icon becomes a row like
   every other item and says what it is. */
@media (max-width: 720px) {
  .nav-search i { display: inline; }
}
