/* =====================================================================
   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;

  --rule: 1px solid #222a31;
  --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(rgba(255, 255, 255, .02) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .02) 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: #fff; 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: #e4e9ee; 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: rgba(11, 12, 14, .88);
  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; margin-right: auto; }
.brand b {
  font: 900 27px/1 var(--display); color: #fff;
  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;
}

nav.main { display: flex; align-items: center; gap: 24px; }
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: #fff; border-bottom-color: var(--signal); }
nav.main a.here { color: #fff; 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: #fff; }
.btn-signal:hover { background: #ff3a2a; }
.btn-ghost { border-color: var(--steel-2); color: var(--chalk); }
.btn-ghost:hover { border-color: var(--signal); color: #fff; }
.btn-dark { background: var(--ink); color: #fff; }
.btn-lg { padding: 17px 30px; font-size: 16px; }

.menu-btn { display: none; background: none; border: 0; color: #fff; 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, rgba(238, 43, 28, .14), 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 rgba(238, 43, 28, .40);
  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: #1e252b; 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: #fff; 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: #fff; 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: #1e252b; 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 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: #1e252b; 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: #fff; 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: #fff; max-width: 20ch; }
.callout p { margin-top: 14px; max-width: 52ch; color: rgba(255, 255, 255, .9); }
.callout .btn-dark:hover { background: #000; }

/* ---------- 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: #fff; }

/* ---------- 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: #fff; 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: #fff; }
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);
}

/* ---------- 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; }
  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; }
.photo-wide img { 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: #fff;
  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: #8b949e; }

/* 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: #fff; }

/* =====================================================================
   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: #4a5058; text-align: center;
  max-width: 46ch; text-transform: none;
}
.plate figcaption b { color: var(--ink); 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: #fff; white-space: nowrap; }
.parts td.desc { color: var(--concrete); }
.parts td.price { font: 600 16px/1.4 var(--body); color: #fff; 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: #fff; 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: #fff; }

/* ---------- 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: #fff; 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: #1e252b; 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: #fff; }
.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: #1e252b; 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 .to b {
  display: block; font: 900 24px/1.05 var(--display); color: #fff; text-transform: uppercase;
}
.path .from b { color: var(--concrete); }
.path .to b { color: #fff; }
.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; }
}

/* ---------- 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: #10161a; }
.dl .kind {
  font: 600 10.5px var(--mono); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink); 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: #fff; 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: #fff; 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: #fff; }

.filters { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: #1e252b; 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: #fff; }

/* 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: #fff; }

.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: #fff; }
.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: #1e252b; 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: #fff; 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, rgba(255, 255, 255, .028) 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: #fff; }
.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: #fff; 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.
   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 {
  color: #fff; 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 { text-decoration-color: #fff; }

/* ---------- 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: #fff; 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: #fff; }
.fchip[aria-pressed="true"] { border-color: var(--signal); background: var(--signal-dim); color: #fff; }
.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: #fff;
  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); }

/* 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;
}
.lbox-in h3 { font: 600 22px/1.1 var(--body); color: #fff; 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: #fff; }

.lbl-body { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.lbl-body b { font: 600 17px/1.2 var(--body); color: #fff; }
.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: #fff; }

/* 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);
}
