/* =============================================================
   LabelPal — brand tokens + primitives
   Created 2026-08-15. The foundation layer for the new website
   (spec 16.6, "One Shop, Four Voices").

   HOW THIS IS MEANT TO BE USED
   A section sets one attribute on <html> or on a wrapper:

       <body class="lp-page" data-voice="teacher">

   ...and every component downstream reads --v-ink / --v-primary /
   --v-deep / --v-accent / --v-tint. Nothing below hardcodes a voice
   colour, so adding a fifth voice later is a block of CSS in ONE
   place and no component changes at all.

   The four palettes were approved by Ray 2026-08-14 and are anchored
   to the LabelPal brand blue #83B9E3, sampled from the logo. Teacher's
   set is already in production — it is what prints on the teacher
   sticker-sheet header — so treat these hexes as fixed brand assets,
   not as suggestions.

   LAUNCH NOTE: /teachers/, /parents/ and /business/ ship first.
   The event voice is defined here and simply not linked yet
   (signed off 2026-08-15).

   FILE ORDER — if this ever needs splitting, cut at the marked
   boundaries: (1) tokens, (2) voices, (3) theme, (4) base + layout,
   (5) components, (6) product motion, (7) motion/access fallbacks.
   ============================================================= */

:root {
  color-scheme: light dark;   /* also what makes light-dark() below resolve */

  /* --- The anchor, shared by all four voices ------------------ */
  --lp-brand: #83B9E3;          /* LabelPal blue, from the logo */
  --lp-brand-deep: #3D6E9B;     /* darkened for text/links on white */

  /* --- Neutrals ----------------------------------------------
     Biased very slightly toward the brand blue rather than a
     pure grey, so the chrome reads as this company. A neutral
     should be chosen, not inherited. */
  --lp-ground:     #F6F8FB;
  --lp-surface:    #FFFFFF;
  --lp-surface-2:  #EFF4F9;
  --lp-ink:        #141C25;
  --lp-ink-2:      #3C4A59;
  --lp-ink-3:      #6C7C8C;
  --lp-rule:       #D8E2EC;
  --lp-rule-soft:  #E7EEF5;

  /* --- Semantic, deliberately separate from any voice accent --
     A voice colour must never be asked to also mean "error", or a
     teacher page starts reading its own brand red as a warning. */
  --lp-ok:    #2E7D5B;
  --lp-warn:  #9A6B12;
  --lp-stop:  #A63A2C;

  /* --- Type --------------------------------------------------- */
  --lp-display: Georgia, "Iowan Old Style", "Times New Roman", serif;
  --lp-sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --lp-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* Type scale — stay on it. */
  --lp-t-xs: 12px;  --lp-t-sm: 14px;  --lp-t-base: 16px;
  --lp-t-lg: 18px;  --lp-t-xl: 22px;  --lp-t-2xl: 28px;
  --lp-t-3xl: 36px; --lp-t-4xl: clamp(34px, 5vw, 54px);

  /* --- Space + shape ------------------------------------------ */
  --lp-s-1: 4px;  --lp-s-2: 8px;  --lp-s-3: 12px; --lp-s-4: 16px;
  --lp-s-5: 24px; --lp-s-6: 32px; --lp-s-7: 48px; --lp-s-8: 64px;
  --lp-radius: 12px;
  --lp-radius-sm: 8px;
  --lp-radius-pill: 999px;
  --lp-shadow: 0 1px 2px rgba(20,28,37,.05), 0 8px 24px -12px rgba(20,28,37,.18);
  --lp-shadow-lift: 0 2px 6px rgba(20,28,37,.08), 0 18px 40px -18px rgba(20,28,37,.28);

  /* Page measure. One number, so every page is the same width and
     nobody has to remember 1140. */
  --lp-measure: 1140px;
  --lp-gutter: var(--lp-s-5);

  /* --- Motion -------------------------------------------------
     One place for timing, so every product interaction below feels
     like it came from the same hand. Durations are short on purpose:
     these are hover garnishes on a shop, not an animation showreel.
     --lp-ease-out is the workhorse; --lp-ease-spring gives the one
     bit of physical overshoot the stamp and badge need to feel
     like objects rather than divs. */
  --lp-dur-fast: 140ms;
  --lp-dur:      260ms;
  --lp-dur-slow: 520ms;
  --lp-ease-out:    cubic-bezier(.22, .61, .36, 1);
  --lp-ease-spring: cubic-bezier(.34, 1.56, .64, 1);
}

/* =============================================================
   (2) THE FOUR VOICES
   Five roles every time:
     ink      TEXT and UI made of the voice — links, buttons,
              headings. Always AA against the page, in both themes.
     primary  the voice as a FILL — blocks, graphics, rules, art.
              This is the approved brand hex, untouched.
     deep     high-contrast pairing, text on tint
     accent   highlights, badges, chart keys
     tint     section grounds, cards, empty states

   Why ink and primary are separate, and it is not pedantry:
   Parent Coral #EE7360 is 2.9:1 on white. As a heading colour it
   is unreadable and fails AA even at display size. Darkening it far
   enough to pass turns it into the Teacher's Apple Red — the two
   voices collapse into each other. So Parent WRITES in its Plum and
   FILLS in its Coral, and stays unmistakably itself. Teacher's red
   needed only a nudge (4.4:1 -> 5.6:1) and keeps its identity.
   Business and Event pass as drawn and are unchanged.

   Every --v-ink below is stated as light-dark(light, dark): brand
   hexes never shift with the OS setting, but TEXT made of them has
   to, or half the palette disappears on a dark ground.

   ONE BROWSER CAVEAT, measured in Chrome 148 on 2026-08-15: when the
   colour scheme flips WHILE the page is open, a light-dark() value
   inherited from an ancestor is not always re-resolved — it repaints
   correctly on the next load. Verified as a repaint quirk, not a
   cascade error (fresh elements resolve correctly at the same
   moment). It costs nothing today because the theme is read from the
   OS. If a manual light/dark TOGGLE is ever added to the site, test
   that first: it may need the voice values re-stated explicitly in
   the [data-theme] blocks rather than relying on light-dark().
   ============================================================= */

[data-voice="teacher"] {
  --v-ink:     light-dark(#B93B2B, #F2836F);  /* Apple Red, print hex nudged for AA */
  --v-primary: #D64533;   /* Apple Red */
  --v-deep:    #2E5E4E;   /* Chalkboard Green */
  --v-accent:  #F4B942;   /* Gold Star */
  --v-tint:    light-dark(#FBF3E4, #201A14);  /* Warm Cream */
}
[data-voice="parent"] {
  --v-ink:     light-dark(#5B3A5E, #C79BCB);  /* Plum — see note above */
  --v-primary: #EE7360;   /* Coral */
  --v-deep:    #5B3A5E;   /* Plum */
  --v-accent:  #6FBFB4;   /* Soft Teal */
  --v-tint:    light-dark(#FBEDE8, #241A20);  /* Blush */
}
[data-voice="business"] {
  --v-ink:     light-dark(#27476E, #9FCBEC);  /* Navy */
  --v-primary: #27476E;   /* Navy */
  --v-deep:    #2B3138;   /* Charcoal */
  --v-accent:  #83B9E3;   /* Brand Blue */
  --v-tint:    light-dark(#EEF3F7, #14202B);  /* Cool Grey */
}
[data-voice="event"] {
  --v-ink:     light-dark(#7A4FA8, #C79BE8);  /* Violet */
  --v-primary: #7A4FA8;   /* Violet */
  --v-deep:    #322B4D;   /* Midnight */
  --v-accent:  #E85D9E;   /* Celebration Pink */
  --v-tint:    light-dark(#F4EEF9, #1E1930);  /* Lilac */
}

/* No data-voice at all (homepage, cart, checkout, account) falls back
   to the house blue, so an unthemed page is never unstyled. */
:root {
  --v-ink:     light-dark(#3D6E9B, #A8CFEE);
  /* Literal, NOT var(--lp-brand-deep): that neutral flips light in dark
     mode, and a --v-primary that flips breaks this file's own rule that
     fills never shift with the OS setting. It also broke in practice —
     white label text on the product mock-ups measured 1.6:1 once the
     fill went pale. Fills stay put; only ink and tint follow the theme. */
  --v-primary: #3D6E9B;
  --v-deep:    var(--lp-ink);
  --v-accent:  var(--lp-brand);
  --v-tint:    var(--lp-surface-2);
}

/* Text that sits ON --v-ink (button labels). White in light mode;
   in dark mode the ink IS the light colour, so the label inverts. */
:root { --v-on-ink: light-dark(#FFFFFF, #0B1219); }

/* PAPER. Fixed in both themes, for the same reason --v-primary is:
   this file's rule is "fills stay put; only ink and tint follow the
   theme", and a sheet of paper that turns dark is no longer a sheet
   of paper. A photograph of a notepad would not invert either.

   ADDED 2026-08-15 after measuring the product artwork in dark mode.
   The paper pieces had been drawn with --lp-surface, which inverts to
   #151E27 — against a --v-tint stage of #1B2732 that is 1.02:1, an
   invisible product. Same class of bug as the leather tag at 1.77:1,
   found the same way: by measuring the fill against the ground it
   actually sits on rather than against white. */
:root {
  --lp-paper:      #FDFDFB;   /* the sheet itself */
  --lp-paper-ink:  #2B3138;   /* anything printed on it */
  --lp-paper-rule: #D5DBE1;   /* ruled lines and the sheet's edge */
}

/* =============================================================
   (3) DARK THEME
   Neutrals invert; the four brand palettes do NOT — a brand colour
   that shifts with the OS setting is no longer a brand colour. What
   changes is how they are USED: --v-ink swaps to its light twin so
   voice text stays readable, and --v-tint swaps to a dark ground of
   the same TEMPERATURE (teacher's stays warm, business's stays cool)
   rather than to a pale cream, which on a dark page is a lamp. Both
   swaps ride on the light-dark() pairs above, so there is nothing
   per-voice to maintain down here.
   ============================================================= */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --lp-ground:    #0E141B;
    --lp-surface:   #151E27;
    --lp-surface-2: #1B2732;
    --lp-ink:       #E8EFF6;
    --lp-ink-2:     #B3C3D2;
    --lp-ink-3:     #7E90A1;
    --lp-rule:      #2A3945;
    --lp-rule-soft: #22303B;
    --lp-brand-deep:#A8CFEE;
    --lp-ok:   #6FCFA1;
    --lp-warn: #E0B457;
    --lp-stop: #EE8A7A;
    --lp-shadow: 0 1px 2px rgba(0,0,0,.4), 0 10px 30px -14px rgba(0,0,0,.7);
    --lp-shadow-lift: 0 2px 8px rgba(0,0,0,.5), 0 22px 48px -20px rgba(0,0,0,.8);
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --lp-ground:#0E141B; --lp-surface:#151E27; --lp-surface-2:#1B2732;
  --lp-ink:#E8EFF6; --lp-ink-2:#B3C3D2; --lp-ink-3:#7E90A1;
  --lp-rule:#2A3945; --lp-rule-soft:#22303B; --lp-brand-deep:#A8CFEE;
  --lp-ok:#6FCFA1; --lp-warn:#E0B457; --lp-stop:#EE8A7A;
  --lp-shadow: 0 1px 2px rgba(0,0,0,.4), 0 10px 30px -14px rgba(0,0,0,.7);
  --lp-shadow-lift: 0 2px 8px rgba(0,0,0,.5), 0 22px 48px -20px rgba(0,0,0,.8);
}
:root[data-theme="light"] { color-scheme: light; }

/* =============================================================
   (4) BASE + LAYOUT
   Scoped to .lp-page on <body>, NOT to bare elements. This CSS will
   eventually load inside WordPress alongside the theme and
   WooCommerce stylesheets; a global `h2 { }` in here would reach
   into the cart, the account pages and every plugin's markup. One
   class on <body> keeps the blast radius to pages we built.
   ============================================================= */

.lp-page {
  margin: 0;
  background: var(--lp-ground);
  color: var(--lp-ink);
  font-family: var(--lp-sans);
  font-size: var(--lp-t-base);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}
.lp-page *, .lp-page *::before, .lp-page *::after { box-sizing: border-box; }

/* Every base rule is wrapped in :where() so it contributes ZERO
   specificity beyond .lp-page. Without it `.lp-page a {color:inherit}`
   (0,1,1) quietly outranks `.lp-btn {color:…}` (0,1,0) and every
   button label inherits body ink — a pale label on a pale button,
   1.4:1, invisible. Caught on the first real page. Base styles set
   defaults; they must never win an argument with a component. */
.lp-page :where(h1, h2, h3) {
  font-family: var(--lp-display);
  font-weight: 400;
  text-wrap: balance;
  margin: 0;
  color: var(--lp-ink);
}
.lp-page :where(h1) { font-size: clamp(36px, 5.4vw, 60px); line-height: 1.04; letter-spacing: -.02em; }
.lp-page :where(h2) { font-size: clamp(24px, 3vw, 34px); line-height: 1.15; margin-bottom: var(--lp-s-2); }
.lp-page :where(h3) { font-size: var(--lp-t-lg); line-height: 1.3; }
.lp-page :where(p)  { color: var(--lp-ink-2); margin: 0 0 var(--lp-s-3); }
.lp-page :where(a)  { color: inherit; }
.lp-page :where(img, svg) { max-width: 100%; }

/* Layout ----------------------------------------------------- */
.lp-wrap {
  max-width: var(--lp-measure);
  margin-inline: auto;
  padding-inline: var(--lp-gutter);
}
.lp-section { padding-block: var(--lp-s-8); border-top: 1px solid var(--lp-rule-soft); }
.lp-section--flush { border-top: 0; }

/* Grids. auto-fit rather than fixed columns: one rule covers the
   3-up product grid, the 3-up door row and the 2-up on a phone,
   and nothing needs a breakpoint written for it. */
.lp-grid {
  display: grid;
  gap: var(--lp-s-4);
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
}
.lp-grid--wide { grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }

/* Small text roles ------------------------------------------- */
.lp-eyebrow {
  font-family: var(--lp-mono);
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--lp-ink-3);
  margin-bottom: var(--lp-s-2);
}
.lp-lede { font-size: var(--lp-t-lg); max-width: 46ch; }
.lp-measure-text { max-width: 62ch; }   /* a paragraph should not run the full 1140 */
.lp-meta { font-size: var(--lp-t-sm); color: var(--lp-ink-3); }

.lp-sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}
.lp-skip {
  position: absolute; left: var(--lp-s-4); top: -60px;
  background: var(--v-ink); color: var(--v-on-ink);
  padding: var(--lp-s-2) var(--lp-s-4);
  border-radius: var(--lp-radius-sm);
  z-index: 100;
  transition: top var(--lp-dur-fast) var(--lp-ease-out);
}
.lp-skip:focus { top: var(--lp-s-4); }

/* =============================================================
   (5) COMPONENTS
   ============================================================= */

/* --- Buttons -------------------------------------------------
   Three weights and no more. Solid = the one thing this screen
   wants you to do; ghost = the reasonable alternative; quiet = a
   link that happens to need a hit area. A page with two solid
   buttons has not decided what it is for. */
.lp-btn {
  --_bg: var(--v-ink);
  --_fg: var(--v-on-ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--lp-s-2);
  font: inherit;
  font-size: 15px;
  line-height: 1.2;
  text-decoration: none;
  padding: 11px var(--lp-s-5);
  border: 1px solid transparent;
  border-radius: var(--lp-radius-sm);
  background: var(--_bg);
  color: var(--_fg);
  cursor: pointer;
  transition: transform var(--lp-dur-fast) var(--lp-ease-out),
              filter    var(--lp-dur-fast) var(--lp-ease-out),
              background var(--lp-dur-fast) var(--lp-ease-out);
}
.lp-btn:hover  { filter: brightness(1.08); transform: translateY(-1px); }
.lp-btn:active { transform: translateY(0); filter: brightness(.96); }
.lp-btn--ghost {
  --_bg: transparent;
  --_fg: var(--v-ink);
  border-color: currentColor;
}
.lp-btn--ghost:hover { background: var(--v-tint); filter: none; }
.lp-btn--quiet {
  --_bg: transparent;
  --_fg: var(--lp-ink-2);
  padding-inline: var(--lp-s-3);
}
.lp-btn--quiet:hover { background: var(--lp-surface-2); filter: none; }
.lp-btn--sm { font-size: var(--lp-t-sm); padding: 8px var(--lp-s-4); }
.lp-btn--lg { font-size: var(--lp-t-lg); padding: 14px var(--lp-s-6); }
.lp-btn[disabled], .lp-btn[aria-disabled="true"] {
  opacity: .5; cursor: not-allowed; transform: none; filter: none;
}

/* --- Links ---------------------------------------------------
   Underlined, always. An underline is the only link affordance
   that survives colour-blindness, a dark theme and a printout. */
.lp-link {
  color: var(--v-ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.lp-link:hover { text-decoration-thickness: 2px; }

/* --- Fields -------------------------------------------------- */
.lp-field { display: block; margin-bottom: var(--lp-s-4); }
.lp-label {
  display: block;
  font-size: var(--lp-t-sm);
  color: var(--lp-ink-2);
  margin-bottom: var(--lp-s-2);
}
.lp-input, .lp-select, .lp-textarea {
  width: 100%;
  font: inherit;
  font-size: 15px;
  padding: 10px var(--lp-s-3);
  border: 1px solid var(--lp-rule);
  border-radius: var(--lp-radius-sm);
  background: var(--lp-ground);
  color: var(--lp-ink);
  transition: border-color var(--lp-dur-fast) var(--lp-ease-out);
}
.lp-input:hover, .lp-select:hover, .lp-textarea:hover { border-color: var(--lp-ink-3); }
.lp-hint  { font-size: var(--lp-t-xs); color: var(--lp-ink-3); margin: var(--lp-s-2) 0 0; }
.lp-error { font-size: var(--lp-t-sm); color: var(--lp-stop); margin: var(--lp-s-2) 0 0; }
.lp-row { display: flex; gap: var(--lp-s-2); align-items: stretch; }
.lp-row > .lp-input { flex: 1; min-width: 0; }

/* --- Surfaces ------------------------------------------------ */
.lp-card {
  background: var(--lp-surface);
  border: 1px solid var(--lp-rule);
  border-radius: var(--lp-radius);
  box-shadow: var(--lp-shadow);
  padding: var(--lp-s-5);
}
.lp-panel {
  background: var(--v-tint);
  border-radius: var(--lp-radius);
  padding: var(--lp-s-5);
}
.lp-pill {
  display: inline-block;
  font-family: var(--lp-mono);
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 3px var(--lp-s-2);
  border-radius: var(--lp-radius-pill);
  background: var(--v-tint);
  color: var(--v-deep);
}

/* --- Door card (the four/three sections) ---------------------
   Put data-voice ON the card and it colours itself. That is the
   payoff of keeping voices in variables: the homepage shows three
   voices at once without a single per-voice rule here. */
.lp-door {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--lp-s-2);
  text-decoration: none;
  padding: var(--lp-s-6) var(--lp-s-5) var(--lp-s-5);
  border: 1px solid var(--lp-rule);
  border-radius: var(--lp-radius);
  background: var(--lp-surface);
  box-shadow: var(--lp-shadow);
  overflow: hidden;
  transition: transform var(--lp-dur) var(--lp-ease-out),
              box-shadow var(--lp-dur) var(--lp-ease-out);
}
.lp-door::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 4px;
  background: var(--v-primary);
}
.lp-door:hover { transform: translateY(-3px); box-shadow: var(--lp-shadow-lift); }
.lp-door h3 { color: var(--v-ink); }
.lp-door p  { font-size: var(--lp-t-sm); margin: 0; }
.lp-door__go {
  margin-top: auto;
  padding-top: var(--lp-s-3);
  font-family: var(--lp-mono);
  font-size: var(--lp-t-xs);
  color: var(--lp-ink-3);
}

/* --- Breadcrumbs ---------------------------------------------
   Visible, and mirrored in BreadcrumbList JSON-LD. Both halves
   matter: the markup tells a person where they are, the schema
   tells Google and the AI crawlers how the section hierarchy
   nests, which is what a pillar/cluster structure needs in order
   to read AS a structure rather than as loose pages. */
.lp-crumbs {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--lp-s-2);
  list-style: none; margin: 0 0 var(--lp-s-4); padding: 0;
  font-size: var(--lp-t-sm);
  color: var(--lp-ink-3);
}
.lp-crumbs li { display: flex; align-items: center; gap: var(--lp-s-2); }
.lp-crumbs li + li::before { content: "/"; color: var(--lp-rule); }
.lp-crumbs a { color: var(--lp-ink-2); text-decoration: none; }
.lp-crumbs a:hover { text-decoration: underline; text-underline-offset: 3px; }
.lp-crumbs [aria-current="page"] { color: var(--lp-ink-3); }

/* --- Lead answer (AEO) ---------------------------------------
   The 40–60 word self-contained answer that opens a page, before
   any build-up. Written for a person who wants the fact fast; it
   also happens to be the block an AI assistant can lift cleanly
   and attribute. Set slightly larger than body text so it reads
   as the summary it is, and kept to one paragraph — the moment it
   becomes three, there is no clean answer in it to quote. */
.lp-answer {
  font-size: var(--lp-t-lg);
  line-height: 1.55;
  color: var(--lp-ink);
  max-width: 62ch;
  border-left: 3px solid var(--v-primary);
  padding-left: var(--lp-s-4);
  margin-bottom: var(--lp-s-5);
}

/* --- FAQ -----------------------------------------------------
   Always visible, never an accordion, and never marked up unless
   the questions are real ones customers ask. FAQPage schema is
   the most-abused type there is; the rule here is that the JSON-LD
   is generated FROM this visible text and must match it word for
   word. */
.lp-faq { display: grid; gap: var(--lp-s-5); max-width: 72ch; }
.lp-faq__q { font-family: var(--lp-display); font-size: var(--lp-t-lg); color: var(--v-ink); margin-bottom: var(--lp-s-1); }
.lp-faq__a { margin: 0; }

/* --- Unconfirmed figure marker -------------------------------
   Wraps any number that has NOT been verified against the pricing
   rules or the business. Deliberately ugly: these pages are written
   to be quoted verbatim by AI assistants, so an unverified number
   must be impossible to miss and impossible to ship by accident.
   Greppable too — search the folder for "lp-tbc" and the count must
   be zero before launch. */
.lp-tbc {
  background: var(--lp-warn);
  color: #fff;
  padding: 0 6px;
  border-radius: 4px;
  font-family: var(--lp-mono);
  font-size: .92em;
  white-space: nowrap;
}

/* --- "Shopping as" indicator ---------------------------------
   For deep arrivals from search that never passed through a
   section (spec 16.6). It is a FALLBACK, not a second mechanism:
   it states what it guessed, and the correction is one click. It
   must never look like a filter the customer has to operate. */
.lp-voicebar {
  display: flex;
  align-items: center;
  gap: var(--lp-s-3);
  flex-wrap: wrap;
  padding: var(--lp-s-2) var(--lp-gutter);
  background: var(--v-tint);
  border-bottom: 1px solid var(--lp-rule-soft);
  font-size: var(--lp-t-sm);
  color: var(--v-deep);
}
/* WITHOUT THIS THE BAR SHOWS TO EVERYONE. `hidden` is nothing more
   than `display: none` in the UA stylesheet, and the `display: flex`
   above outranks it — so every page set .hidden = true, every script
   test passed, and the bar rendered anyway. Found 2026-08-15 by
   measuring the element instead of trusting the property: hidden was
   true and getBoundingClientRect().height was still 39px.

   This is the same failure as the blank production file whose 45
   assertions passed: the check and the pixels were never connected.
   Any future `display:` on a class that also gets `hidden` needs the
   same guard. */
.lp-voicebar[hidden] { display: none; }

.lp-voicebar__dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--v-primary);
  flex: none;
}
.lp-voicebar__name { font-weight: 600; }

/* --- Site chrome --------------------------------------------- */
.lp-nav {
  position: sticky; top: 0; z-index: 20;
  background: var(--lp-surface);
  border-bottom: 1px solid var(--lp-rule);
}
.lp-nav__row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--lp-s-5);
  padding-block: var(--lp-s-3);
}
.lp-brandmark {
  display: flex; align-items: center; gap: var(--lp-s-2);
  font-family: var(--lp-display);
  font-size: 21px;
  letter-spacing: -.01em;
  text-decoration: none;
}
.lp-brandmark i { width: 15px; height: 15px; border-radius: 4px; background: var(--lp-brand); }
.lp-doorbar { display: flex; gap: var(--lp-s-1); list-style: none; margin: 0; padding: 0; }
.lp-doorbar a {
  display: flex; align-items: center; gap: var(--lp-s-2);
  font-size: var(--lp-t-sm);
  text-decoration: none;
  color: var(--lp-ink-2);
  padding: 7px var(--lp-s-3);
  border-radius: var(--lp-radius-pill);
  transition: background var(--lp-dur-fast) var(--lp-ease-out);
}
.lp-doorbar a:hover { background: var(--lp-surface-2); }
.lp-doorbar a[aria-current="page"] { background: var(--v-tint); color: var(--v-deep); }
.lp-doorbar em { width: 8px; height: 8px; border-radius: 50%; background: var(--v-primary); }
@media (max-width: 780px) { .lp-doorbar { display: none; } }

.lp-footer {
  padding: var(--lp-s-7) 0 var(--lp-s-8);
  border-top: 1px solid var(--lp-rule-soft);
  color: var(--lp-ink-3);
  font-size: 13.5px;
}

/* =============================================================
   (6) PRODUCT HOVER BEHAVIOUR (per Ray, 2026-08-15)
   "a Self inking stamp makes an impression when a mouse hover, a
    sticker peeling slightly when hover, button badge spinning
    around when hover"

   The rule these all follow: THE MOVEMENT IS WHAT THE OBJECT
   ACTUALLY DOES. A sticker peels because stickers peel. A badge
   spins because it is a disc with a pin. A tag swings because it
   hangs off a hole. This is why it reads as charm rather than as
   decoration — nothing here is a generic "card lift".

   Restraint, deliberately:
     - one gesture per product, on hover/focus only
     - transform + opacity only, so it stays on the compositor
     - nothing loops; every gesture settles and stops
     - all of it collapses to a plain fade under reduced-motion

   Markup contract:
     <a class="lp-item lp-item--sticker">
       <span class="lp-item__stage"><span class="lp-item__art">…</span></span>
       <span class="lp-item__name">Vinyl Stickers</span>
       <span class="lp-item__price">from R185</span>
     </a>
   ============================================================= */

.lp-item {
  display: block;
  position: relative;
  padding: var(--lp-s-4);
  text-decoration: none;
  color: inherit;
  border-radius: var(--lp-radius);
  background: var(--lp-surface);
  border: 1px solid var(--lp-rule);
  box-shadow: var(--lp-shadow);
  transition: box-shadow var(--lp-dur) var(--lp-ease-out),
              transform var(--lp-dur) var(--lp-ease-out);
}
.lp-item:hover,
.lp-item:focus-visible { box-shadow: var(--lp-shadow-lift); }

.lp-item__stage {
  display: grid;
  place-items: center;
  position: relative;
  height: 140px;
  margin-bottom: var(--lp-s-3);
}
.lp-item__name  { display: block; font-family: var(--lp-display); font-size: 17px; }
.lp-item__price { display: block; font-size: 13px; color: var(--lp-ink-3); margin-top: 2px; }

.lp-item__art {
  display: block;
  position: relative;
  transform-origin: center;
  will-change: transform;
}

/* --- Sticker: a corner peels up ------------------------------
   The art tilts on a corner-anchored 3D rotation and a soft
   triangular shadow grows underneath it, which is what sells the
   lift — a rotation alone reads as a wobble, not a peel. */
.lp-item--sticker { perspective: 700px; }
.lp-item--sticker .lp-item__art {
  transform-origin: 88% 88%;
  transition: transform var(--lp-dur) var(--lp-ease-out);
}
.lp-item--sticker .lp-item__art::after {
  content: "";
  position: absolute;
  right: 6%; bottom: 6%;
  width: 34%; height: 34%;
  border-radius: 0 0 var(--lp-radius-sm) 0;
  background: radial-gradient(circle at 100% 100%, rgba(20,28,37,.30), rgba(20,28,37,0) 68%);
  opacity: 0;
  transition: opacity var(--lp-dur) var(--lp-ease-out);
  pointer-events: none;
}
.lp-item--sticker:hover .lp-item__art,
.lp-item--sticker:focus-visible .lp-item__art {
  transform: rotate3d(-1, 1, 0, 15deg) translateZ(6px);
}
.lp-item--sticker:hover .lp-item__art::after,
.lp-item--sticker:focus-visible .lp-item__art::after { opacity: 1; }

/* --- Self-inking stamp: presses, and leaves the impression ----
   Two beats, and the order matters: the stamp drops FAST (a stamp
   is sprung — it snaps), then rebounds slowly, and the impression
   it left stays behind after the stamp has lifted. That lag is the
   whole gag; without it the ink looks painted on rather than
   pressed. */
.lp-item--stamp .lp-item__art {
  transition: transform var(--lp-dur-fast) var(--lp-ease-out);
}
.lp-item--stamp .lp-item__impression {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  opacity: 0;
  transform: scale(1.06);
  transition: opacity var(--lp-dur) var(--lp-ease-out) 90ms,
              transform var(--lp-dur) var(--lp-ease-out) 90ms;
  pointer-events: none;
}
.lp-item--stamp:hover .lp-item__art,
.lp-item--stamp:focus-visible .lp-item__art {
  transform: translateY(7px) scale(.972);
  transition-duration: 90ms;   /* the strike is quicker than the rebound */
}
.lp-item--stamp:hover .lp-item__impression,
.lp-item--stamp:focus-visible .lp-item__impression {
  opacity: 1;
  transform: scale(1);
}

/* --- Button badge: spins on its pin ---------------------------
   A full turn about Y with a little spring at the end, plus a
   shine that sweeps across as it comes round — a bare rotation on
   a flat disc is nearly invisible, and the highlight is what tells
   the eye it is a domed metal object. */
.lp-item--badge { perspective: 900px; }
.lp-item--badge .lp-item__art {
  transform-style: preserve-3d;
  transition: transform var(--lp-dur-slow) var(--lp-ease-spring);
}
.lp-item--badge .lp-item__art::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: linear-gradient(105deg, rgba(255,255,255,0) 38%, rgba(255,255,255,.55) 50%, rgba(255,255,255,0) 62%);
  opacity: 0;
  transform: translateX(-60%);
  transition: opacity var(--lp-dur) var(--lp-ease-out),
              transform var(--lp-dur-slow) var(--lp-ease-out);
  pointer-events: none;
}
.lp-item--badge:hover .lp-item__art,
.lp-item--badge:focus-visible .lp-item__art { transform: rotateY(360deg); }
.lp-item--badge:hover .lp-item__art::before,
.lp-item--badge:focus-visible .lp-item__art::before {
  opacity: 1;
  transform: translateX(60%);
}

/* --- Leather / satin tag: swings from its hole ----------------
   Pivots at the punch hole, not the centre, and settles in three
   decreasing swings. Anything more becomes a metronome. */
.lp-item--tag .lp-item__art {
  transform-origin: 50% 6%;   /* the hole */
}
.lp-item--tag:hover .lp-item__art,
.lp-item--tag:focus-visible .lp-item__art {
  animation: lp-swing 1.15s var(--lp-ease-out);
}
@keyframes lp-swing {
  0%   { transform: rotate(0deg); }
  22%  { transform: rotate(6.5deg); }
  48%  { transform: rotate(-4deg); }
  72%  { transform: rotate(2deg); }
  88%  { transform: rotate(-.8deg); }
  100% { transform: rotate(0deg); }
}

/* --- Notepad: the top sheet lifts and falls back --------------
   Hinged at the top edge, the way a bound pad opens. */
.lp-item--notepad { perspective: 900px; }
.lp-item--notepad .lp-item__sheet {
  position: absolute;
  inset: 0;
  transform-origin: 50% 0;
  transition: transform var(--lp-dur-slow) var(--lp-ease-out);
  backface-visibility: hidden;
}
.lp-item--notepad:hover .lp-item__sheet,
.lp-item--notepad:focus-visible .lp-item__sheet { transform: rotateX(38deg); }

/* --- Poster: the corner curls --------------------------------- */
.lp-item--poster .lp-item__art {
  transform-origin: 100% 0;
  transition: transform var(--lp-dur) var(--lp-ease-out);
}
.lp-item--poster:hover .lp-item__art,
.lp-item--poster:focus-visible .lp-item__art {
  transform: rotate3d(1, 1, 0, 9deg);
}

/* =============================================================
   (7) REDUCED MOTION
   Not "animations off" — the hover still has to ANSWER, or the
   page feels broken to anyone who set the preference. Every
   gesture collapses to the same quiet lift.
   ============================================================= */
@media (prefers-reduced-motion: reduce) {
  .lp-item,
  .lp-item__art,
  .lp-item__sheet,
  .lp-item__impression,
  .lp-item__art::before,
  .lp-item__art::after,
  .lp-door,
  .lp-btn {
    transition-duration: 1ms !important;
    animation: none !important;
  }
  .lp-item:hover .lp-item__art,
  .lp-item:focus-visible .lp-item__art { transform: none !important; }
  .lp-item:hover, .lp-item:focus-visible { box-shadow: var(--lp-shadow-lift); }
  .lp-item--stamp:hover .lp-item__impression,
  .lp-item--stamp:focus-visible .lp-item__impression { opacity: 1; transform: none; }
  .lp-door:hover, .lp-btn:hover { transform: none; }
}

/* Touch devices have no hover; :focus-visible above keeps every one
   of these reachable from the keyboard, which is the accessible
   equivalent and costs nothing. */
@media (hover: none) {
  .lp-item__art { transform: none !important; animation: none !important; }
}

.lp-page :focus-visible {
  outline: 2px solid var(--v-ink);
  outline-offset: 3px;
  border-radius: var(--lp-radius-sm);
}
