/* public.css — the design system for every public marketing page.

   WHY THIS FILE EXISTS
   Before it, the public templates carried 2,588 lines of CSS across 25 separate
   inline <style> blocks and not one of them extended a base. The duplication was
   not approximate: authenticate.html, redirect.html and display.html held byte
   -for-byte IDENTICAL 135-line blocks, and landing.html and security_labels.html
   shared 76 identical lines. Every brand change had to be made by hand in two
   dozen places, which is the drift shape this codebase has already been bitten
   by twice (a navbar copy-pasted per page that kept advertising a renamed
   section; a public nav offering a page that 404s).

   WHAT BELONGS HERE
   Anything two public pages could share: the shell, type scale, sections, bands,
   cards, buttons, the pricing ladder, the order form. Page-specific one-offs stay
   in that page's own {% block page_css %}.

   WHAT DOES NOT
   Colour and typeface literals. Every value here resolves to a token from
   brand-tokens.css, which is Optrical's own tokens.css copied verbatim. A brand
   change is a swap of that file, not an edit of this one.

   NOT YET LOADED BY public/landing.html. The live homepage still carries its own
   inline styles, deliberately: it is the one page being left exactly as it
   renders today. It joins this file when the homepage is swapped, and
   macros/public_nav.html can be deleted at the same time.
*/

*{box-sizing:border-box;margin:0;padding:0}
:root{
    /* ── The Optrical logo, and nothing else ────────────────────────
       The wordmark is --c-navy #023151; the spiral mark beside it is
       --c-orange #F26429. That is the whole logo -- two colours on
       white -- so those two carry the page and every other value here
       is Optrical's own supporting token rather than a colour invented
       to sit next to them.

       THE ORANGE COMES IN TWO STRENGTHS, and the difference is not
       decorative. Measured:
         #F26429 on white ....... 3.17:1  fails AA for text
         #F26429 on navy ........ 4.25:1  AA at 24px+ only
         #FFFFFF on #F26429 ..... 3.17:1  fails -- so no white-on-orange
         #CF4A13 on white ....... 4.52:1  AA
         #FFFFFF on #CF4A13 ..... 4.52:1  AA
       So --accent is the brand orange, used for LARGE display type on
       navy, for borders and for rules; --accent-text is Optrical's own
       --c-orange-deep and is the only one allowed to carry or be small
       text. Optrical's tokens already drew this line; this just names
       which side each use sits on. */
    --ink:var(--c-navy);              /* #023151 dark bands, headings  */
    --ink-deep:var(--c-navy-deep);    /* #00243D                       */
    --ink-black:var(--c-navy-black);  /* #001A2C footer                */
    --ink2:var(--c-text);             /* #4F5256 body, 7.85:1 on white */
    --steel:var(--c-steel);           /* #8FA3B0 secondary ON navy     */
    --footer-text:var(--c-footer-text);
    --border:var(--c-border);         /* #E5E5E5                       */
    --surface:var(--c-panel);         /* #F4F6F8                       */
    --white:var(--c-surface);
    /* ORANGE NEVER TOUCHES A NAVY GROUND. Optrical's own sections.css
       settles this: every navy band it draws (.banner, .hero-simple,
       .hero) sets color:#fff and dims secondary copy with opacity, and
       there is no orange anywhere on them. The orange lives on white --
       buttons, notices, the nav underline. On navy it is only 4.25:1
       anyway, and next to a large white headline it reads as muddy
       rather than as an accent. So the two grounds get two treatments:
       on navy, hierarchy comes from white and --on-navy-2; on white, it
       comes from the orange. */
    --accent:var(--c-orange);         /* #F26429 LIGHT GROUNDS ONLY    */
    /* Optrical's own secondary-on-navy: white, dimmed. Composites to
       about #CBD4DA over --c-navy, ~9:1, and stays in the white family
       instead of introducing a fourth hue. */
    --on-navy-2:rgba(255,255,255,.82);
    --on-navy-rule:rgba(255,255,255,.22);
    --accent-text:var(--c-orange-deep);/* #CF4A13 the only text orange */
    --accent-hover:var(--c-blue-hover);/* #0868AA Optrical's own       */
    /* #797C80 is 4.19:1 on white and fails AA for body sizes, so the
       brand's --c-text-muted is deliberately NOT mapped to a --muted
       token here. On white, secondary text uses --ink2; on navy it uses
       --steel (5.15:1). Small grey text was one of the density
       complaints -- making it darker is part of the fix, not a
       compromise forced by the palette. */
    --muted:var(--c-text);
    /* 4px, from Optrical's --radius-btn. The 10px pill this page had is
       a generic-SaaS tell; the brand's buttons are nearly square. */
    --radius:4px;
    --radius-card:6px;
    /* TWO section rhythms, not one. The live page gives all twelve
       bands the same 72px, which is why they read as a single
       undifferentiated column. --gap-major separates the six sections;
       --gap-minor is for space INSIDE one. A reader can only tell a
       new topic has started if the gaps are unequal. */
    --gap-major:104px;
    --gap-minor:40px;
}
html{scroll-behavior:smooth}
/* 17px, up from the browser default the live page inherits. Its copy is
   set at 13-15px in the nav, chips and currency buttons -- small type at
   high word count is most of why the page FEELS dense before a word of
   it is read. Bigger type also caps how much can be written: the column
   runs out of room, which is the constraint the old page lacked. */
body{font-family:var(--f-body);color:var(--ink);background:var(--white);
     font-size:17px;line-height:1.65;-webkit-font-smoothing:antialiased}
a{color:var(--accent-text)}
em{font-style:normal;color:var(--accent)}
.wrap{max-width:1020px;margin:0 auto;padding:0 28px}

/* ── Nav ──────────────────────────────────────────────────────────────
   Four links and two buttons. The live nav offers up to FIFTEEN targets
   (a five-item Platform menu, a five-item Company menu, Pricing,
   Security Labels, DPP, Login, CTA) and six of them are #anchors into
   the page you are already on -- so the nav mostly scrolls you rather
   than navigating, and a deep link to #analytics drops you mid-band
   under a sticky bar with no landmark.

   No dropdowns at all here. Besides the count, the live ones are
   hover-driven <button>s with aria-haspopup and no aria-expanded, which
   makes them unreachable by keyboard; not rebuilding them is cheaper
   than fixing them. Company moves to the footer, where a visitor looks
   for corporate provenance anyway. */
/* SOLID white, not the translucent blur this had. Over the navy hero
   the 92%-opaque bar let the headline show through it -- an orange word
   ghosting up through the navbar, which read as a rendering fault. It
   is also what optrical.com does: .header-main is a flat --c-surface
   with a --c-rule underneath. */
.nav{position:sticky;top:0;z-index:50;background:var(--white);
     border-bottom:1px solid var(--c-rule)}
.nav-in{max-width:1020px;margin:0 auto;padding:0 28px;height:64px;
        display:flex;align-items:center;gap:28px}
.brand{font-family:var(--f-head);font-weight:700;font-size:21px;
       color:var(--ink);text-decoration:none;letter-spacing:-.02em;line-height:1.1}
/* The wordmark is navy and the mark beside it is orange -- so "O4" takes
   --ink and "QR" takes the spiral's colour. On white at 21px/800 the
   brand orange is large text (3.17:1 clears the 3:1 large threshold),
   which is the one place --accent is allowed on a white ground. */
.brand span{color:var(--accent)}
.brand small{display:block;font-family:var(--f-body);font-size:10.5px;
             font-weight:500;color:var(--ink2);letter-spacing:.02em}
.nav-links{margin-left:auto;display:flex;align-items:center;gap:24px}
.nav-links a{font-size:15px;color:var(--ink2);text-decoration:none;font-weight:500}
.nav-links a:hover{color:var(--accent-text)}
/* Navy, not orange. Optrical's own token file annotates --c-navy as
   "headings, nav links, primary button" and --c-orange as
   "accent / button hover" -- the brand's primary button has always been
   the navy one. It is also the only one that works: white on #F26429 is
   3.17:1 and fails. */
.nav-cta{padding:9px 18px;background:var(--ink);color:#fff!important;
         border-radius:var(--radius);font-weight:700;font-size:15px}
.nav-cta:hover{background:var(--accent-hover)}
.nav-login{color:var(--ink2)}
.burger{display:none;margin-left:auto;background:none;border:0;padding:8px;cursor:pointer}
.burger span{display:block;width:22px;height:2px;background:var(--ink);margin:4px 0;border-radius:2px}
.nav-mob{display:none;flex-direction:column;padding:8px 28px 18px;
         border-top:1px solid var(--border);background:#fff}
.nav-mob a{padding:12px 0;font-size:17px;color:var(--ink2);text-decoration:none;
           border-bottom:1px solid var(--border)}
.nav-mob a:last-child{border-bottom:0}

/* ── Hero ──────────────────────────────────────────────────────────────
   ONE headline, ONE sentence, ONE primary button.

   The live hero carries all four of the company's propositions before
   the first button: a two-clause headline (genuine AND compliant), then
   ~55 words covering micro-optic manufacturing AND per-unit identity AND
   passports, then a second paragraph about counterfeits, diversion and
   the ESPR deadline. Roughly ninety words. A visitor 400px down still
   cannot say what the product is.

   Compliance is not dropped -- it is a different buyer with a different
   question, and it gets the DPP page and one line further down. A hero
   that argues two cases wins neither. */
.hero{background:var(--ink);color:#fff;padding:96px 0 88px}
.hero-in{max-width:1020px;margin:0 auto;padding:0 28px;
         display:grid;grid-template-columns:1.05fr .95fr;gap:56px;align-items:center}
.hero h1{font-family:var(--f-head);font-size:60px;line-height:1.04;
         font-weight:300;letter-spacing:-.035em;margin-bottom:22px}
/* White, not orange. This was the loudest orange-on-navy on the page
   and the one that prompted the note. The emphasis survives without a
   hue change: "genuine" is already the last word of a three-word
   headline at 60px. */
.hero h1 em{color:#fff}
.hero p{font-size:20px;line-height:1.5;color:var(--on-navy-2);max-width:30em;margin-bottom:34px}
.hero-btns{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
/* .btn-primary only ever sits on a navy band. White fill, navy label:
   13.46:1, and it is the loudest thing available on that ground. An
   orange fill was the obvious first choice and is wrong twice over --
   white on #F26429 is 3.17:1, and even the deep orange only reaches
   2.97:1 against the navy behind it, so the button would not clear the
   3:1 that non-text UI needs to be seen as a shape at all. */
.btn-primary{padding:15px 30px;background:#fff;color:var(--ink);border-radius:var(--radius);
             font-weight:700;text-decoration:none;font-size:17px;display:inline-block;
             transition:background .15s,transform .15s}
.btn-primary:hover{background:var(--surface);color:var(--ink);transform:translateY(-1px)}
/* White outline, not orange. Same rule as the headline -- and the pair
   now reads as one family, a solid white button beside an outlined one,
   rather than as two unrelated buttons. */
.btn-ghost{padding:15px 28px;border:1px solid var(--on-navy-rule);color:#fff;
           border-radius:var(--radius);font-weight:500;text-decoration:none;font-size:17px}
.btn-ghost:hover{border-color:#fff;background:rgba(255,255,255,.08);color:#fff}
/* The parent-brand band was 87 words, four capability chips and two of
   its own buttons, sitting between the hero and the first explanation of
   the product. Optrical is a CREDIBILITY fact, not a section: it belongs
   where a reader checks credibility, which is next to the claim and in
   the footer. One line. */
.hero-trust{margin-top:30px;padding-top:22px;border-top:1px solid var(--on-navy-rule);
            font-size:15px;color:var(--on-navy-2);max-width:34em}
.hero-trust a{color:#fff;text-decoration:underline;text-underline-offset:3px}
.hero-visual img{width:100%;display:block}

/* ── Section shell ───────────────────────────────────────────────────── */
.sec{padding:var(--gap-major) 0}
/* TWO ADJACENT LIGHT SECTIONS SHARE ONE GAP, not two. Both carry --gap-major,
   so a .sec following a .sec put 208px of empty white between the last card of
   one and the eyebrow of the next -- five times the 40px rhythm used INSIDE a
   section, with no change of ground to justify it. Where a navy band sits
   between two sections the colour change carries the transition and the full
   padding is right; between two white ones it reads as a missing block.

   This also replaces the inline style="padding-top:0" that had been hand-added
   to eleven sections across nine templates, which is the same fix written out
   by hand every time it was noticed. */
.sec + .sec{padding-top:0}
/* A section's last element must not push its own bottom margin into the join.
   .sec-lead carries margin-bottom:var(--gap-minor), so a section ENDING on one
   produced a 144px gap where a section ending on a card grid produced 104 -- the
   spacing between sections depended on what happened to be last inside them. */
.sec > .wrap > *:last-child{margin-bottom:0}
/* The alternating white/grey band is GONE. It was part of what made the
   live page read as one undifferentiated column -- twelve stripes of
   equal weight -- and it also broke the eyebrow: --accent-text is
   4.52:1 on white but only 4.18:1 on the #F4F6F8 panel, so the label
   would have failed AA on every other section. Rhythm now comes from
   the two navy bands and the gap scale, which is the argument this
   redesign is making anyway. The panel tint is spent on cards instead. */
.sec.alt{background:var(--white)}
.sec-label{font-size:12.5px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;
           color:var(--accent-text);margin-bottom:12px}
/* 24em, not 16. At 16em a 38px heading broke at about 28 characters, so
   "Micro-optic security labels with a unique QR code, made in-house." wrapped
   three times while two fifths of the column sat empty beside it -- which reads
   as text that failed to fill rather than as a measure that was chosen.

   Headings are not running text and do not need the 60-to-75 character limit
   that body copy does: they are scanned in one or two fixations, not read line
   by line. text-wrap:balance then evens the lines out instead of leaving a
   single orphaned word on the last one. */
/* 20em and text-wrap:pretty.

   24em let a heading run 912px while the paragraph under it stopped at 646px --
   a 266px overhang that read as two unrelated column widths stacked on each
   other. 20em brings it to 760px, still far wider than the 16em that was
   breaking headings at 28 characters, and close enough to the body measure that
   the block has one right edge.

   pretty, not balance: balance equalises the two lines of a wrapped heading, so
   "Micro-optic security labels with a unique QR code, made in-house." rendered
   as two ~500px lines inside a 912px box and looked NARROWER than the one-line
   headings in neighbouring sections, even though the element was the same
   width. pretty fills the measure and only guards the last-line orphan. */
.sec-title{font-family:var(--f-head);font-size:38px;font-weight:300;
           letter-spacing:-.025em;line-height:1.18;margin-bottom:14px;
           max-width:20em;text-wrap:pretty}
/* ONE sub-paragraph is allowed per section, and it is capped at a single
   sentence. The live page has eleven .sec-sub paragraphs, several of
   them two or three sentences, and they are the bulk of the reading
   load -- each one restating the heading it sits under. */
/* DELIBERATELY NARROWER THAN THE COLUMN, and it stays that way. 34em puts this
   at about 59 characters a line. Running text set to a full 964px column runs
   past 90 characters, where the eye loses its place on the return sweep and
   re-reads the line it just finished. The empty space to the right is not waste;
   it is what makes the paragraph readable. Headings above are a different case
   and were widened. */
.sec-lead{font-size:19px;color:var(--ink2);max-width:34em;margin-bottom:var(--gap-minor)}

/* ── Statement band ────────────────────────────────────────────────────
   The sharpest 60 words on the live site, given the room to land instead
   of being one band among twelve. No cards, no grid, nothing to compare
   it against -- a claim this central should be read, not scanned. */
.statement{background:var(--ink);color:#fff;padding:64px 0}
.statement .wrap{text-align:center}
.statement h2{font-family:var(--f-head);font-size:36px;font-weight:300;
              letter-spacing:-.03em;line-height:1.2;margin:0 auto 14px;max-width:18em}
.statement h2 em{color:#fff}
.statement p{font-size:19px;color:var(--on-navy-2);max-width:38em;margin:0 auto}
/* The one eyebrow that is NOT orange, because it is the one on navy. */
.statement .kicker{font-size:12.5px;font-weight:700;letter-spacing:.12em;
                   text-transform:uppercase;color:var(--on-navy-2);margin-bottom:16px}

/* ── Cards ─────────────────────────────────────────────────────────────
   One line of description each. The live equivalents carry a description
   plus a chip row of three or four keywords plus a link -- the chips are
   the section's word count and add no fact the sentence does not. */
/* 200px, not 260: the row holds FOUR cards when the Redirect and Display
   actions are both switched on, and 260 made the fourth wrap to a line
   of its own -- a single orphaned card under three, which reads as a
   layout fault rather than a category. auto-fit still collapses the
   empty tracks when the gated cards are off, so a two-card row fills
   the width instead of hugging the left edge. */
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:20px}
.card{background:var(--surface);border:1px solid var(--border);
      border-radius:var(--radius-card);padding:28px 26px}
/* ── Icons ─────────────────────────────────────────────────────────────
   Moved to icons.css. It had to leave this file: the auth and status pages
   (register, verify_email, reset_password, the dpp_* stakeholder pages,
   complete_profile, maintenance) are STANDALONE templates that never extend
   public/_base.html and never load this stylesheet -- seven of them load
   style-unified.css and four load no stylesheet at all. They carry the same
   icons, so the icon rules cannot live in the public marketing sheet.
   icons.css depends on brand-tokens.css and nothing else. */

.card h3{font-family:var(--f-head);font-size:20px;font-weight:400;margin-bottom:8px}
.card p{font-size:16px;color:var(--ink2);margin-bottom:14px}
.card a{font-size:15px;font-weight:700;text-decoration:none}
.card a:hover{text-decoration:underline}
/* Optrical already has a "coming soon" notice style -- --c-notice-soon
   is the brand orange over a --c-notice-soon-bg warm wash. Borrowed
   rather than invented, with the label in navy so an 11px pill is not
   relying on orange to be legible. */
.soon{display:inline-block;margin-left:8px;font-size:11px;font-weight:700;
      letter-spacing:.06em;text-transform:uppercase;color:var(--ink);
      background:#FFF9F5;border:1px solid var(--accent);
      border-radius:3px;padding:2px 8px;vertical-align:middle}

/* ── Pricing ───────────────────────────────────────────────────────────
   The tier LADDER only. The live pricing section is the densest thing on
   the site (328 words) and most of it is not prices: three meter columns
   (codes / records / top-ups), a starter-basket row, a regional-pricing
   paragraph and a printed-label footnote. A visitor cannot answer "what
   will this cost me" without first learning the billing ontology.

   That material is not wrong, it is just not a homepage's job -- it is
   the content of a /pricing page, linked below the ladder. The one fact
   from it that changes how the TABLE is read (volume is bought
   separately, so you are not pushed up a tier to print more) stays, as
   one sentence. */
/* 200px for the same reason the card row uses it: four public plans at
   230 need 974px of track in a 964px column, which orphaned Enterprise
   onto a second row -- and an orphaned tier reads as "there is more
   above this", which is the opposite of what the top tier should say.
   The plan count comes from the database, so this has to survive a
   fifth plan being published as well; auto-fit wraps to 2x2 or 3x2
   rather than leaving a single card stranded. */
.tiers{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:18px}
.tier{background:var(--surface);border:1px solid var(--border);
      border-radius:var(--radius-card);padding:26px 22px;
      display:flex;flex-direction:column}
/* The one card the eye should land on gets the spiral colour as a
   2px edge -- non-text UI at 4.25:1 against the page, no text riding
   on it, so full-strength orange is exactly right here. */
.tier.featured{background:#fff;border:2px solid var(--accent)}
.tier-badge{align-self:flex-start;font-size:11px;font-weight:700;letter-spacing:.06em;
            text-transform:uppercase;color:#fff;background:var(--accent-text);
            border-radius:3px;padding:3px 10px;margin-bottom:12px}
.tier-name{font-family:var(--f-head);font-size:19px;font-weight:400;margin-bottom:6px}
.tier-price{font-family:var(--f-head);font-size:30px;font-weight:700;
            color:var(--ink);line-height:1.15;margin:4px 0 2px}
.tier-price small{font-size:14px;font-weight:500;color:var(--ink2)}
.tier-meta{font-size:15px;color:var(--ink2);margin-bottom:16px}
.tier-feats{list-style:none;margin:0 0 22px;font-size:15px;color:var(--ink2)}
.tier-feats li{padding:5px 0 5px 20px;position:relative}
.tier-feats li::before{content:"✓";position:absolute;left:0;color:var(--accent-text);font-weight:700}
.tier-cta{margin-top:auto;text-align:center;padding:11px 16px;border-radius:var(--radius);
          border:1px solid var(--c-field-border);color:var(--ink);text-decoration:none;
          font-weight:700;font-size:15px;background:#fff}
.tier-cta:hover{border-color:var(--accent);color:var(--accent-text)}
.tier-cta.primary{background:var(--ink);border-color:var(--ink);color:#fff}
.tier-cta.primary:hover{background:var(--accent-hover);border-color:var(--accent-hover);color:#fff}
.ccy-row{display:flex;gap:8px;align-items:center;margin-bottom:20px}
.ccy-row span.lab{font-size:14px;color:var(--ink2);font-weight:500}
.ccy-btn{font:inherit;font-size:14px;font-weight:500;padding:5px 12px;border-radius:var(--radius);
         border:1px solid var(--c-field-border);background:#fff;color:var(--ink2);cursor:pointer}
.ccy-btn.active{border-color:var(--accent);color:var(--accent-text);font-weight:700}
/* 40em ~ 640px at 16px, the same physical measure .sec-lead uses at 19px. It
   had no max-width at all, so it was the only running text on the site set to
   the full 964px column -- past 100 characters a line, where the eye loses the
   return sweep. It also made these notes read as far longer than they are: two
   sentences looked like a paragraph. */
.price-note{margin-top:24px;font-size:16px;color:var(--ink2);max-width:40em}

/* ── Constructions ─────────────────────────────────────────────────────
   The catalogue of what the factory actually makes. Given a plain
   bordered list rather than the card treatment the jobs get: these are
   specifications a buyer scans down looking for their format, not six
   propositions to choose between, and stamping the same radius and fill
   on both would flatten the difference. */
/* The rule lives on each CELL, not on the grid.

   It was a 1px grid gap over a --border-coloured background, which draws
   the dividers for free -- but only while the cells fill every track.
   There are six constructions in a four-column row, so the last two
   tracks of row two had nothing in them and the backing colour showed
   through as a grey rectangle sitting in the middle of the section.
   With the border on the cell an empty track is simply empty.

   280px also makes it three across rather than four, which divides six
   exactly today; the cell border is what keeps it correct when the
   factory adds a seventh. */
.cons{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));
      gap:14px}
.con{background:var(--white);border:1px solid var(--border);
     border-radius:var(--radius-card);padding:22px 20px}
.con h3{font-family:var(--f-head);font-size:17px;font-weight:400;margin-bottom:7px}
.con p{font-size:15px;color:var(--ink2);margin-bottom:12px;line-height:1.5}
.con-for{font-size:13px;color:var(--accent-text);font-weight:500;
         letter-spacing:.02em}
.cons-note{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));
           gap:22px;margin-top:26px;font-size:16px;color:var(--ink2)}
.cons-note strong{color:var(--ink);display:block;margin-bottom:3px}

/* ── Jobs ──────────────────────────────────────────────────────────────
   Outcomes, for the visitor who is still working out what they need. */
.jobs{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:20px}
.job{background:var(--surface);border:1px solid var(--border);
     border-radius:var(--radius-card);padding:22px 20px}
.job h3{font-family:var(--f-head);font-size:17px;font-weight:400;margin-bottom:6px}
.job p{font-size:15px;color:var(--ink2);line-height:1.5}

/* The other half of the same answer: the visitor who already knows the
   name of the thing they want, and only needs to see it listed. Set as
   a plain run of names rather than 17 more cards -- it is an index, and
   an index that looks like a menu of decisions is worse at both jobs. */
.cat{margin-top:30px;padding-top:24px;border-top:1px solid var(--border)}
.cat-h{font-size:15px;color:var(--ink2);margin-bottom:12px}
.cat-list{display:flex;flex-wrap:wrap;gap:8px}
.cat-list span{font-size:14px;color:var(--ink);background:var(--surface);
               border:1px solid var(--border);border-radius:3px;padding:5px 11px}

/* ── Order form ────────────────────────────────────────────────────────
   On navy, because this is the page's primary conversion surface and the
   navy bands are what the eye stops at. Same rule as everywhere else
   holds: no orange on this ground -- the submit button is white. */
/* Its own vertical rhythm, tighter than --gap-major. This band is dominated by
   a form, and a form already carries generous internal spacing -- stacking the
   page's full section padding on top of it left a tall panel of empty navy above
   and below the copy column beside it. */
.order-sec{background:var(--ink);color:#fff;padding:72px 0}
/* align-items:center, not start. The form is the taller column by a long
   way -- it has eight fields and a captcha against four lines of copy --
   so top-aligning left the copy hanging at the top of a tall empty navy
   panel. Centring splits that leftover height above and below the copy
   instead of dumping all of it underneath, which is what makes the two
   columns read as a pair rather than as one column and one orphan.
   Cheaper and more robust than padding the left column out with text
   nobody needs. */
.order-grid{display:grid;grid-template-columns:.85fr 1.15fr;gap:52px;align-items:center}
.order-copy h2{font-family:var(--f-head);font-size:32px;font-weight:400;
               letter-spacing:-.025em;line-height:1.2;margin:0 0 14px;color:#fff}
.order-copy p{font-size:17px;color:var(--on-navy-2);margin-bottom:20px}
.order-pts{list-style:none;margin:0;padding:0;font-size:15.5px;color:var(--on-navy-2)}
.order-pts li{padding:7px 0 7px 22px;position:relative}
.order-pts li::before{content:"✓";position:absolute;left:0;color:#fff;font-weight:700}

.order-form{background:#fff;border-radius:var(--radius-card);padding:22px 22px;
            color:var(--ink)}
/* The kind switch is the form's first and most consequential control, so
   it is a pair of large targets rather than a dropdown or a checkbox
   buried further down. */
.kind{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:20px}
.kind-opt{display:flex;align-items:flex-start;gap:9px;cursor:pointer;
          border:1px solid var(--c-field-border);border-radius:var(--radius);
          padding:12px 13px;transition:border-color .15s,background .15s}
.kind-opt:hover{border-color:var(--accent)}
.kind-opt input{margin-top:3px;flex-shrink:0;accent-color:var(--accent-text)}
.kind-opt span{font-size:14px;color:var(--ink2);line-height:1.35}
.kind-opt strong{display:block;font-size:15px;color:var(--ink);font-weight:700}
.kind-opt:has(input:checked){border-color:var(--accent);background:#FFF9F5}
.kind-opt:focus-within{outline:2px solid var(--accent-text);outline-offset:2px}

.of-row{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.of-f{margin-bottom:10px;display:flex;flex-direction:column}
.of-f label{font-size:13.5px;font-weight:500;color:var(--ink2);margin-bottom:5px}
.of-f .opt{color:var(--muted);font-weight:400}
.of-f input,.of-f select,.of-f textarea{
    font:inherit;font-size:15px;padding:10px 12px;border-radius:var(--radius);
    border:1px solid var(--c-field-border);background:#fff;color:var(--ink);width:100%}
.of-f input:focus,.of-f select:focus,.of-f textarea:focus{
    outline:2px solid var(--accent-text);outline-offset:1px;border-color:var(--accent)}
.of-f textarea{resize:vertical}
.of-cap{align-items:end}
.cap-wrap{display:flex;align-items:center;gap:8px}
.cap-img{height:44px;border:1px solid var(--c-field-border);border-radius:var(--radius);
         background:#fff}
.cap-reload{font:inherit;font-size:17px;line-height:1;cursor:pointer;padding:9px 11px;
            border:1px solid var(--c-field-border);border-radius:var(--radius);
            background:#fff;color:var(--ink2)}
.cap-reload:hover{border-color:var(--accent);color:var(--accent-text)}
.of-consent{display:flex;align-items:flex-start;gap:9px;font-size:14px;
            color:var(--ink2);margin:4px 0 18px;line-height:1.45}
.of-consent input{margin-top:3px;flex-shrink:0;width:auto;accent-color:var(--accent-text)}
.of-submit{font:inherit;font-size:16px;font-weight:700;width:100%;padding:14px;
           border:0;border-radius:var(--radius);background:var(--ink);color:#fff;
           cursor:pointer}
.of-submit:hover{background:var(--accent-hover)}
.of-submit:disabled{opacity:.6;cursor:default}
/* No reserved line before there is anything to say. It held 1.2em of empty
   space under the button on every page load, for a message that only exists
   after a submission. */
.of-msg{margin-top:0;font-size:14.5px}
.of-msg:not(:empty){margin-top:12px}
.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);
         white-space:nowrap}

/* ── Closing CTA ───────────────────────────────────────────────────────
   The live page's only conversion surface is a five-field contact form
   with an image captcha and a consent checkbox, at the bottom of 1,649
   words -- and when registration is switched off, EVERY primary button
   on the page funnels into it. Here the close is two links and the form
   lives on its own page, reachable in one click from anywhere. */
.close{background:var(--ink);color:#fff;padding:88px 0;text-align:center}
.close h2{font-family:var(--f-head);font-size:38px;font-weight:300;
          letter-spacing:-.03em;margin-bottom:14px}
.close p{font-size:19px;color:var(--on-navy-2);max-width:34em;margin:0 auto 30px}
.close .hero-btns{justify-content:center}

/* ── Deferred sections ─────────────────────────────────────────────────
   PREVIEW SCAFFOLD, not part of the proposal.

   Five bands come off this page in the redesign (Analytics, Lifecycle,
   Industries, Production, About) and the proposal is that each becomes
   its own page. Building five pages before the structure is agreed would
   be the wrong order, so for now these link back to the LIVE homepage's
   anchors -- which is why the row is labelled as such rather than
   dressed up as finished navigation. On adoption this row is deleted and
   the links move into the nav as real destinations. */
.deferred{border-top:1px dashed #cbd5e1;background:#fffbeb;padding:26px 0}
.deferred .wrap{display:flex;flex-wrap:wrap;gap:10px 18px;align-items:baseline}
.deferred b{font-size:13px;letter-spacing:.05em;text-transform:uppercase;color:#92400e}
.deferred a{font-size:15px;color:#92400e}
.deferred small{width:100%;font-size:13.5px;color:#a16207}

/* ── Footer ────────────────────────────────────────────────────────── */
/* --c-navy-deep, which is what Optrical uses for its own footer body.
   It reads as one step down from the --c-navy bands above it, so the
   page darkens as it ends rather than repeating the same navy a fourth
   time. (Optrical splits a --c-navy-black bar below this one; there is
   nothing here to put in it.) */
.foot{background:var(--ink-deep);color:var(--footer-text);padding:44px 0 30px;font-size:15px}
.foot .wrap{display:grid;grid-template-columns:1fr auto;gap:28px;align-items:start}
.foot .fbrand{font-family:var(--f-head);font-weight:700;font-size:21px;color:#fff}
/* The wordmark keeps its two colours everywhere: "O4" takes the ground's
   text colour, "QR" is always the spiral orange. It is the brand mark,
   not body copy, and a mark that changes colour per section stops being
   recognisable as one.

   This is the one sanctioned exception to "no orange on navy", and it
   clears the bar on the measurement rather than on the exception:
   #F26429 on --c-navy-deep is 5.02:1, comfortably AA, because the footer
   navy is darker than the band navy. The rule was always about large
   display text and hairlines looking muddy on #023151; a 21px/700
   wordmark on a darker ground is neither. */
.foot .fbrand span{color:var(--accent)}
.foot p{margin-top:10px;max-width:32em;line-height:1.6}
.foot a{color:var(--c-footer-icon);text-decoration:none}
.foot a:hover{color:#fff;text-decoration:underline}
/* Two columns, left-aligned. Seven links stacked in a single right-aligned
   column made the footer 435px tall -- taller than several of the sections above
   it -- and a right-aligned list has a ragged left edge, so the eye has no
   consistent start position to scan down. Two columns halve the height and give
   each column a straight edge to read from. */
/* COLUMN-major over three rows, so the two columns are two GROUPS rather than
   an alphabet split across a row-major grid: this site's pages on the left,
   utility and legal on the right. Row-major filled them Industries / About,
   Optrical / Label manufacturing, which paired items that have nothing to do
   with each other. */
.flinks{display:grid;grid-template-rows:repeat(3,auto);grid-auto-flow:column;
        gap:10px 40px;text-align:left;align-content:start}
/* The outbound Optrical links, under the sentence that introduces Optrical. */
.fout{display:flex;flex-wrap:wrap;gap:6px 18px;margin-top:12px}
.fout a{white-space:nowrap}
.fcopy{margin-top:34px;padding-top:20px;border-top:1px solid var(--c-footer-rule);
       font-size:14px;color:var(--footer-text)}

.banner{background:#fef3c7;border-bottom:1px solid #fcd34d;color:#78350f;
        font-size:14px;padding:9px 28px;text-align:center}
.banner a{color:#78350f;font-weight:700}

@media(max-width:900px){
    :root{--gap-major:72px;--gap-minor:30px}
    .hero-in{grid-template-columns:1fr;gap:36px}
    .hero{padding:64px 0}
    .hero h1{font-size:42px}
    .hero p{font-size:18px}
    /* Text FIRST on a phone. The visual is decorative -- it names no
       product and makes no claim -- and at 375px it filled the entire
       first screen, so the headline and the primary button both opened
       below the fold. Ordering it above the copy is a desktop habit
       that costs the whole above-the-fold pitch on the device most
       scanners actually arrive on. */
    .hero-visual{margin-top:8px;opacity:.9}
    .sec-title,.statement h2,.close h2{font-size:30px}
    .nav-links{display:none}
    .burger{display:block}
    .order-grid{grid-template-columns:1fr;gap:30px}
    .of-row{grid-template-columns:1fr;gap:0}
    .kind{grid-template-columns:1fr}
    .foot .wrap{grid-template-columns:1fr}
    .flinks{grid-template-rows:none;grid-template-columns:repeat(2,1fr);
            grid-auto-flow:row;gap:12px 20px}
}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}
    .btn-primary:hover{transform:none}}

/* ── Nav: current-page marker ───────────────────────────────────────────────
   The base template sets nav_active per page. Without this the nav on a
   standalone page looked identical to the nav on every other page, so a
   visitor could not tell where they were. */
.nav-links a.current{color:var(--accent-text);font-weight:700}
.nav-mob a.current{color:var(--accent-text);font-weight:700}

/* ── Cookie banner ──────────────────────────────────────────────────────────
   Strictly-necessary cookies only, so this is a notice rather than a consent
   gate -- there is nothing to opt out of, and a page that blocks on a choice it
   is not going to honour is worse than one that just says what it does.
   Recoloured from the old #38bdf8 link to the brand: the ground is navy, so the
   link is white rather than orange. */
.cookie-banner{position:fixed;bottom:0;left:0;right:0;z-index:999;
               background:var(--ink-deep);color:var(--on-navy-2);
               padding:13px 22px;display:flex;gap:16px;align-items:center;
               flex-wrap:wrap;font-size:14px;line-height:1.5}
.cookie-banner p{margin:0;flex:1;min-width:200px}
.cookie-banner a{color:#fff;text-decoration:underline;text-underline-offset:2px}
.cookie-accept{font:inherit;padding:8px 18px;background:#fff;color:var(--ink);
               border:0;border-radius:var(--radius);font-weight:700;
               font-size:14px;cursor:pointer;flex-shrink:0}
.cookie-accept:hover{background:var(--surface)}

/* ── Prose pages (privacy, terms) ───────────────────────────────────────────
   One narrow measure and a real type scale, so a legal page is readable rather
   than merely present. */
.prose-page{max-width:none;padding:var(--gap-major) 0}
.prose-page .wrap{max-width:760px}
.prose-page h1{font-family:var(--f-head);font-size:38px;font-weight:300;
               letter-spacing:-.025em;margin-bottom:10px}
.prose-page .updated{font-size:15px;color:var(--muted);margin-bottom:36px}
.prose-page h2{font-family:var(--f-head);font-size:22px;font-weight:400;
               margin:34px 0 10px}
.prose-page h3{font-family:var(--f-head);font-size:18px;font-weight:400;margin:24px 0 8px}
.prose-page p,.prose-page li{font-size:16.5px;color:var(--ink2);line-height:1.7}
.prose-page p{margin-bottom:14px}
.prose-page ul,.prose-page ol{margin:0 0 16px 22px}
.prose-page li{margin-bottom:7px}

/* ── Heading weight ─────────────────────────────────────────────────────────
   Titles are set in Lato LIGHT (300), which is why that face is self-hosted --
   Optrical's own import ships only 400 and 700.

   Light is applied BY SIZE, not everywhere. Above about 28px it reads as
   deliberate and gives the page its air. At 17 to 22px it renders lighter than
   the Roboto 400 body copy sitting underneath it, which inverts the hierarchy:
   a card heading that is thinner than its own paragraph stops looking like a
   heading at all. Those use 400 -- still far lighter than the 700 they were.

   Two exceptions keep 700. The O4QR wordmark is a logotype rather than a title,
   and a light logo reads as a rendering fault. A tier price is a figure, and
   figures need weight to be read as data. */

/* ── Standalone page header ─────────────────────────────────────────────────
   The promoted pages open with a compact dark header rather than the homepage's
   full hero: they are destinations somebody chose, so they need to confirm
   where you are, not sell you on arriving. */
.page-head{background:var(--ink);color:#fff;padding:64px 0 60px}
.page-head .kicker{font-size:12.5px;font-weight:700;letter-spacing:.12em;
                   text-transform:uppercase;color:var(--on-navy-2);margin-bottom:14px}
.page-head h1{font-family:var(--f-head);font-size:clamp(30px,4.4vw,44px);font-weight:300;
              letter-spacing:-.03em;line-height:1.1;margin-bottom:16px;
              max-width:22em;text-wrap:pretty}
.page-head p{font-size:19px;color:var(--on-navy-2);max-width:44em;line-height:1.55}

/* Bullets inside a card. The homepage's analytics band carried these as loose
   text; as a list they can be skimmed, which is the only way six cards of
   capability get read at all. */
.feat-list{list-style:none;margin:12px 0 0;padding:0}
.feat-list li{font-size:15px;color:var(--ink2);line-height:1.45;
              padding:5px 0 5px 18px;position:relative}
.feat-list li::before{content:"";position:absolute;left:0;top:12px;width:6px;height:6px;
                      border-radius:50%;background:var(--accent)}

/* ── Timeline ───────────────────────────────────────────────────────────────
   The lifecycle argument is chronological -- launch, month 6, year 1, year 2 --
   so the stage marker carries real information and earns its place. A numbered
   or dated rail on content that is NOT a sequence would be decoration. */
.tl{display:grid;gap:0;border-left:2px solid var(--border);margin-left:6px}
.tl-item{padding:0 0 30px 26px;position:relative}
.tl-item:last-child{padding-bottom:0}
.tl-item::before{content:"";position:absolute;left:-7px;top:6px;width:12px;height:12px;
                 border-radius:50%;background:var(--accent);border:2px solid var(--white)}
.tl-when{font-size:12.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
         color:var(--accent-text);margin-bottom:4px}
.tl-item h3{font-family:var(--f-head);font-size:19px;font-weight:400;margin-bottom:5px}
.tl-item p{font-size:16px;color:var(--ink2);max-width:52em}

/* ── Catalogue rows ─────────────────────────────────────────────────────────
   Every QR action by its own name. A row per action rather than a card per
   action: this is an index somebody scans for one word, and 17 cards would read
   as 17 decisions to make. */
.uc-row{display:grid;grid-template-columns:minmax(160px,220px) 1fr;gap:20px;
        padding:16px 0;border-bottom:1px solid var(--border);align-items:baseline}
.uc-row:last-child{border-bottom:0}
.uc-row dt{font-family:var(--f-head);font-size:17px;font-weight:400;color:var(--ink)}
.uc-row dd{margin:0;font-size:16px;color:var(--ink2);line-height:1.5}
@media(max-width:640px){.uc-row{grid-template-columns:1fr;gap:4px}}

/* ── Pricing model ──────────────────────────────────────────────────────────
   The three meters a buyer is actually billed on. Off the homepage because a
   first-time visitor does not need the billing ontology to decide whether to
   keep reading; here because somebody comparing plans genuinely does. */
.meters{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:20px}
.meter{background:var(--surface);border:1px solid var(--border);
       border-radius:var(--radius-card);padding:24px 22px}
.meter h3{font-family:var(--f-head);font-size:19px;font-weight:400;margin-bottom:6px}
.meter > p{font-size:15.5px;color:var(--ink2);margin-bottom:12px}
.meter-rate{display:flex;flex-wrap:wrap;gap:7px;margin-top:14px}
.meter-rate span{font-size:13px;background:var(--white);border:1px solid var(--border);
                 border-radius:3px;padding:4px 9px;color:var(--ink)}
.meter-rate span.free{border-color:var(--accent);color:var(--accent-text);font-weight:600}

/* ── Credentials ────────────────────────────────────────────────────────────
   Proof, for a page with no salesperson behind it. Deliberately NOT the card
   treatment: these are facts about the company, and boxing each one makes them
   look like features. */
/* 220px, so the four credentials sit 4-across rather than 3 + 1. At 240 they
   fitted three columns in a 964px content column and stranded the fourth
   ("Rotherham, South Yorkshire" -- the invitation to come and see the line,
   which is the most useful one on the page for a buyer with no salesperson). */
.creds{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:28px}
.cred h3{font-family:var(--f-head);font-size:18px;font-weight:400;margin-bottom:6px;
         color:var(--accent-text)}
.cred p{font-size:16px;color:var(--ink2);line-height:1.55}

/* ── Figures ────────────────────────────────────────────────────────────────
   A diagram is not a card: no fill, no shadow, just a hairline and room to
   breathe, so it reads as an illustration of the argument rather than as
   another item in a grid.

   overflow-x on the container, not the page. Several of these diagrams have a
   min-width wider than a phone (scan-path is three rows of a journey and does
   not usefully reflow), so the FIGURE scrolls sideways while the page body
   never does. The caption stays put outside the scroll area. */
.figure{margin:var(--gap-minor) 0 0}
.figure-inner{overflow-x:auto;border:1px solid var(--border);
              border-radius:var(--radius-card);background:var(--surface);
              padding:18px}
.figure img{display:block;width:100%;height:auto}
.figure img.wide{min-width:620px}
.figure figcaption{margin-top:10px;font-size:14.5px;color:var(--muted);max-width:60em}

/* ── Steps ──────────────────────────────────────────────────────────────────
   A numbered sequence, used for the scan flow on /use-cases. The marker earns
   its place here because the content genuinely is ordered: a code cannot be
   logged before it is resolved, or answered before it is logged. Numbering
   anything that is not a sequence would be decoration.

   160px, not 180: five steps at 180 need 972px of track inside a 964px column,
   so step five dropped onto a row of its own and the sequence stopped reading
   as one flow. */
.steps{counter-reset:s;list-style:none;display:grid;margin:0;padding:0;
       grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:18px}
.steps li{counter-increment:s;background:var(--surface);border:1px solid var(--border);
          border-radius:var(--radius-card);padding:22px 20px}
.steps li::before{content:counter(s);display:flex;align-items:center;justify-content:center;
                  width:28px;height:28px;border-radius:50%;background:var(--accent-text);
                  color:#fff;font-weight:700;font-size:14px;margin-bottom:14px}
.steps strong{display:block;font-size:17px;margin-bottom:4px}
.steps span{font-size:15px;color:var(--ink2);line-height:1.45}

/* ── "soon" marker in the nav ───────────────────────────────────────────────
   Subtle on purpose: no box, no fill, just the accent at a smaller size riding
   above the baseline. A badge with a border would compete with the CTA button
   two items along, and a nav is not where you sell the roadmap -- it only has
   to stop somebody clicking through expecting a finished product. */
.nav-soon{font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:lowercase;
          color:var(--accent-text);margin-left:4px;vertical-align:3px}
.nav-mob .nav-soon{vertical-align:2px}

/* A figure that was never a diagram.

   authenticate-clone and dpp-record were lifted out of the homepage, where they
   were inline illustrations inside a card about 300px wide. Dropped into a
   .figure they inherited width:100% and rendered at 926px -- over three times
   their drawn size, which does not blur a vector but does make every stroke and
   label absurd next to 17px body copy. The four real diagrams are 860 to 980
   units wide and land at roughly 1:1, so they are unaffected by this.

   Centred rather than left-aligned: at 400px in a 964px column, flush left
   reads as a layout that failed to fill, whereas centred reads as a plate. */
.figure.compact .figure-inner{display:flex;justify-content:center;padding:26px 18px}
.figure.compact img{max-width:400px}
.figure.compact figcaption{text-align:center}

/* A card grid pinned to three across.

   .cards auto-fits four columns in a 964px content column, which is right for a
   four or six card row but leaves nine cards as 4 + 4 + 1, with the last one
   stranded. 280px min gives three columns exactly, so nine reads as 3 x 3 -- and
   the cards stop being narrow enough to wrap every description four times. */
.cards.three{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
