/* File: /assets/css/qr-tool.css
   Module: QR Code Generator (tool shell, controls, preview, content, gate)
   Scope: loaded only on the generator page by inc/qr-tool.php.
   Light theme first, dark overrides at the bottom (html[data-theme="dark"]),
   matching the convention in portal.css. Colours come from the global tokens
   in style.css, never hardcoded outside the local token block below. */

.ch-page-qr {
    /* Surfaces, light to dark: the page well, the panels, the tiles inside
       the panels. A tile must never equal its panel or the controls vanish. */
    --ch-qr-surface: #FFFFFF;
    --ch-qr-well: #F1F5F9;
    --ch-qr-tile: #F8FAFC;
    --ch-qr-tile-hover: #FFFFFF;

    /* The "paper" a frame thumbnail paints inside its body. It has to flip
       with the theme: the body is drawn in currentColor, so a hardcoded white
       panel disappears the moment the icon colour goes light on slate. */
    --ch-qr-void: #FFFFFF;

    /* Hairlines: resting comes from --ch-border, these two are the hover and
       pressed steps up the Slate ladder. */
    --ch-qr-line-soft: #CBD5E1;
    --ch-qr-line-hard: #94A3B8;

    /* Emerald at the darkness each theme needs to hold contrast as TEXT.
       Emerald 500 fails on white, Emerald 600 fails on slate, so it flips. */
    --ch-qr-accent: #059669;
    --ch-qr-accent-bg: rgba(16, 185, 129, 0.09);
    --ch-qr-accent-bg-hi: rgba(16, 185, 129, 0.16);
    --ch-qr-accent-line: rgba(16, 185, 129, 0.45);

    /* Notices. Advisory is amber, blocker is red, both in the house alert
       grammar used by .ch-portal-alert: 8-10% wash, 35% hairline, solid text. */
    --ch-qr-warn-bg: rgba(245, 158, 11, 0.10);
    --ch-qr-warn-line: rgba(245, 158, 11, 0.38);
    --ch-qr-warn-text: #92400E;
    --ch-qr-warn-ico: #B45309;
    --ch-qr-stop-bg: rgba(239, 68, 68, 0.07);
    --ch-qr-stop-line: rgba(239, 68, 68, 0.34);
    --ch-qr-stop-text: #B91C1C;
    --ch-qr-stop-ico: #DC2626;

    --ch-qr-radius: 14px;
    --ch-qr-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 24px -16px rgba(15, 23, 42, 0.18);
    --ch-qr-shadow-lift: 0 14px 26px -14px rgba(15, 23, 42, 0.24);
    --ch-qr-ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* One shared focus grammar for the whole tool: every interactive element that
   does not paint its own ring gets the emerald outline. */
.ch-page-qr :is(a, button, summary):focus-visible {
    outline: 2px solid var(--ch-cta);
    outline-offset: 2px;
    border-radius: 6px;
}

/* HIDDEN GUARD - the second cascade trap in this file, after the GP one below.
   The user agent hides [hidden] with a bare element rule, so ANY class that
   sets display (flex, grid, inline-flex) silently outguns it and the element
   can never hide. This tool toggles a lot of flex controls with the hidden
   attribute, and each one that lacked a guard was permanently on screen: the
   scanability notice with no message in it, the logo Remove button with no
   logo loaded, the eyedropper in browsers with no EyeDropper API. One rule at
   0,2,0 covers every control here and every control added later. The gate is
   printed into the footer, outside .ch-page-qr, so it needs its own branch. */
.ch-page-qr [hidden],
.ch-qr-gate [hidden] {
    display: none;
}

/* ==========================================================================
   HARD OVERRIDE - READ BEFORE ADDING ANY BUTTON TO THIS TOOL
   GeneratePress paints every bare <button> with its own background AND white
   text on :hover, :focus and :active, at specificity 0,1,1. A component rule
   written as ".ch-thing" (0,1,0) therefore loses its paint the instant the
   control is hovered or clicked, and a hover rule that declares a background
   but no colour only wins half the fight - which is how a light panel ended
   up with white-on-white titles and how the frame thumbnails turned into
   white blobs (their bodies are currentColor, and GP forced that to #FFF).

   The fix has two halves, and BOTH are required for every new control:
   1. list ":focus" and ":active" alongside the base selector so the resting
      paint is pinned at 0,2,0, which outguns GP;
   2. declare background-color AND color together in the :hover rule.
   Focus rings stay on :focus-visible via the grammar above, so pinning
   :focus to the resting paint costs nothing in accessibility terms.
   ========================================================================== */

/* Entrance: the tool follows the hero, editorial stays put. One-shot keyframes
   with no observer to fail - below-the-fold content never blinks. The hero
   itself animates through the shared .ch-hero-inner cascade in style.css. */
@media (prefers-reduced-motion: no-preference) {
    .ch-qr-app {
        animation: ch-qr-rise 0.6s var(--ch-qr-ease) 200ms both;
    }
}

@keyframes ch-qr-rise {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
}

/* ==========================================================================
   1. HERO - "ENCODER CONSOLE"
   A contained panel, not the shared full bleed .ch-hero. Three reasons the
   tool page needs its own composition:

   1. Vertical budget. The front page hero is allowed to own a viewport. Here
      the generator has to be reachable, so this hero spends horizontal space
      (a two column split plus a full width spec strip) instead of vertical
      space, and lands at roughly the same desktop height as the centred hero
      it replaces while carrying considerably more.
   2. Reading ergonomics. A 60 word SEO paragraph centred at 660px is a wall.
      Left aligned at 64ch with a ragged right edge is a paragraph.
   3. Signal. A tool page that opens with a live encoder plate tells the
      reader what the page does before they read a word, and it demonstrates
      three product features - rounded modules, emerald finder styling, a
      centred brand logo - as ornament rather than as a claim.

   The parts that carry the brand are preserved verbatim from the dossier
   panel (style.css section 15.4): the emerald top rail, the blueprint grid,
   the corner spotlight, the JetBrains Mono kicker with a ping dot. Anyone who
   arrives from the front page reads this as the same site.

   Every token below is themed at the foot of this file. Nothing in here
   hardcodes a Slate value except the emerald graphics, which are identical in
   both themes by design.
   ========================================================================== */
.ch-qrhero {
    /* Surfaces, page level down to the module stage */
    --ch-qh-panel: #FFFFFF;
    --ch-qh-panel-2: #F1F5F9;
    --ch-qh-plate: #FFFFFF;
    --ch-qh-stage: #F8FAFC;

    --ch-qh-line: #E2E8F0;

    /* Module ink. Flips to Slate 200 on dark or the matrix disappears. */
    --ch-qh-mod: #0F172A;

    --ch-qh-grid: rgba(15, 23, 42, 0.05);
    --ch-qh-glow: rgba(16, 185, 129, 0.17);

    --ch-qh-shadow: 0 30px 60px -44px rgba(15, 23, 42, 0.62);
    --ch-qh-plate-shadow: 0 18px 38px -26px rgba(15, 23, 42, 0.42);

    position: relative;
    isolation: isolate;
    overflow: hidden;
    max-width: 1200px;
    margin: 0 auto 1.75rem;
    /* The whole point of this composition is that it costs no more height than
       the centred hero it replaced (~500px at desktop) while carrying a live
       plate and a spec strip. Every vertical value below is on that budget:
       raise this padding and the generator drops below the fold. */
    padding: clamp(1.4rem, 3vw, 2.25rem);
    border: 1px solid var(--ch-qh-line);
    border-radius: 22px;
    background-color: var(--ch-qh-panel);
    background-image: linear-gradient(168deg, var(--ch-qh-panel) 0%, var(--ch-qh-panel) 52%, var(--ch-qh-panel-2) 100%);
    box-shadow: var(--ch-qh-shadow);
}

/* Top rail: the "system online" hairline the dossier and the Learning Center
   deck both run. It is the single strongest cue that this is the same site. */
.ch-qrhero-rail {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    z-index: 2;
    pointer-events: none;
    background-image: linear-gradient(90deg, transparent 0%, var(--ch-cta) 20%, rgba(16, 185, 129, 0.3) 58%, transparent 100%);
    background-size: 220% 100%;
    background-position: 120% 0;
}

/* Blueprint substrate. Near invisible, and it is the only thing stopping a
   large light panel from reading as an empty box. */
.ch-qrhero-grid {
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image:
        linear-gradient(to right, var(--ch-qh-grid) 1px, transparent 1px),
        linear-gradient(to bottom, var(--ch-qh-grid) 1px, transparent 1px);
    background-size: 46px 46px;
    -webkit-mask-image: radial-gradient(ellipse 82% 70% at 22% 0%, #000 0%, transparent 78%);
    mask-image: radial-gradient(ellipse 82% 70% at 22% 0%, #000 0%, transparent 78%);
}

/* Emerald spotlight, anchored behind the headline rather than centred. The
   asymmetry is the point: it is what stops this reading as the home hero. */
.ch-qrhero-aurora {
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background:
        radial-gradient(ellipse 46% 58% at 12% 8%, var(--ch-qh-glow), transparent 68%),
        radial-gradient(ellipse 38% 48% at 88% 96%, var(--ch-qh-glow), transparent 70%);
}

.ch-qrhero-inner {
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-columns: minmax(0, 1fr) clamp(232px, 27%, 306px);
    align-items: center;
    gap: clamp(1.5rem, 3.5vw, 3rem);
}

/* --- Copy column ---------------------------------------------------------- */
.ch-qrhero-kicker {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    margin-bottom: 1.15rem;
    padding: 0.36rem 0.85rem 0.36rem 0.7rem;
    border: 1px solid var(--ch-qr-accent-line);
    border-radius: 999px;
    background-color: var(--ch-qr-accent-bg);
    color: var(--ch-qr-accent);
    font-family: 'JetBrains Mono', 'Roboto Mono', monospace;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 1.8px;
    text-transform: uppercase;
}

.ch-qrhero-dot {
    position: relative;
    flex: none;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background-color: var(--ch-cta);
}

.ch-qrhero-dot::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background-color: var(--ch-cta);
}

/* The H1 runs as two stacked lines: the exact-match keyword phrase in Slate,
   the intent phrase in emerald. Same string as before, no comma added, no
   word dropped - only the line break is designed.

   2.6rem is not a taste call. At that size both phrases clear a ~720px column
   on one line each, so the headline is exactly two lines. 2.85rem wraps the
   second phrase and costs a third line, which is ~45px straight off the
   budget in the note on .ch-qrhero above. */
.ch-qrhero-title {
    margin: 0 0 0.85rem;
    color: var(--ch-text-main);
    font-size: clamp(1.8rem, 3.2vw, 2.6rem);
    font-weight: 800;
    letter-spacing: -0.032em;
    line-height: 1.1;
    text-wrap: balance;
}

.ch-qrhero-title span {
    display: block;
    background-image: linear-gradient(100deg, #059669 0%, #10B981 30%, #34D399 50%, #10B981 70%, #059669 100%);
    background-size: 220% auto;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: var(--ch-qr-accent);
}

/* 64ch, not the full column. Measure caps at a readable line; the column is
   ~720px, which would run this paragraph at ~79 characters. */
.ch-qrhero-sub {
    max-width: 64ch;
    margin: 0 0 1.35rem;
    color: var(--ch-text-muted);
    font-size: 1rem;
    line-height: 1.6;
}

/* Spec strip: four claims on one full width row under the split, divided off
   with the panel hairline. Pills say "badges" and a 2x2 list says "bullets";
   a strip across 1100px reads as a specification, which is the register this
   page wants - and it costs ~60px instead of ~140px. */
.ch-qrhero-trust {
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0.65rem 1.75rem;
    margin: clamp(1.25rem, 2.6vw, 1.9rem) 0 0;
    padding: clamp(1.1rem, 2.4vw, 1.6rem) 0 0;
    border-top: 1px solid var(--ch-qh-line);
    list-style: none;
}

.ch-qrhero-trust li {
    display: flex;
    align-items: flex-start;
    gap: 0.55rem;
    color: var(--ch-text-main);
    font-size: 0.85rem;
    font-weight: 600;
    line-height: 1.4;
}

.ch-qrhero-tick {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 21px;
    height: 21px;
    margin-top: -1px;
    border-radius: 7px;
    background-color: var(--ch-qr-accent-bg);
    color: var(--ch-qr-accent);
}

.ch-qrhero-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.9rem 1.15rem;
}

/* :focus and :active pinned to the resting paint - see the GeneratePress
   override note at the top of this file. */
.ch-qrhero-cta,
.ch-qrhero-cta:focus,
.ch-qrhero-cta:active {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.72rem 1.35rem;
    border: 0;
    border-radius: 10px;
    background-color: var(--ch-cta);
    color: #FFFFFF;
    font-size: 0.92rem;
    font-weight: 700;
    text-decoration: none;
    box-shadow: 0 10px 20px -12px rgba(16, 185, 129, 0.9);
    transition: background-color 0.2s ease, transform 0.2s var(--ch-qr-ease), box-shadow 0.2s ease;
}

.ch-qrhero-cta:hover {
    background-color: #059669;
    color: #FFFFFF;
    transform: translateY(-2px);
    box-shadow: 0 16px 26px -14px rgba(16, 185, 129, 0.95);
}

.ch-qrhero-cta svg {
    transition: transform 0.2s var(--ch-qr-ease);
}

.ch-qrhero-cta:hover svg {
    transform: translateX(3px);
}

.ch-qrhero-formats {
    font-family: 'JetBrains Mono', 'Roboto Mono', monospace;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.6px;
    color: var(--ch-text-muted);
}

/* --- Encoder plate -------------------------------------------------------- */
.ch-qrhero-plate {
    padding: 0.85rem;
    border: 1px solid var(--ch-qh-line);
    border-radius: 18px;
    background-color: var(--ch-qh-plate);
    box-shadow: var(--ch-qh-plate-shadow);
}

.ch-qrhero-plate-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0 0.2rem 0.7rem;
}

.ch-qrhero-plate-name,
.ch-qrhero-badge {
    font-family: 'JetBrains Mono', 'Roboto Mono', monospace;
    font-size: 0.64rem;
    font-weight: 700;
    letter-spacing: 1.4px;
    text-transform: uppercase;
}

.ch-qrhero-plate-name {
    color: var(--ch-text-muted);
}

.ch-qrhero-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.18rem 0.5rem;
    border: 1px solid var(--ch-qr-accent-line);
    border-radius: 999px;
    background-color: var(--ch-qr-accent-bg);
    color: var(--ch-qr-accent);
}

.ch-qrhero-badge-dot {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background-color: var(--ch-cta);
}

.ch-qrhero-stage {
    position: relative;
    aspect-ratio: 1;
    padding: 9%;
    border: 1px solid var(--ch-qh-line);
    border-radius: 13px;
    background-color: var(--ch-qh-stage);
}

.ch-qrhero-matrix {
    display: block;
    width: 100%;
    height: 100%;
    fill: var(--ch-qh-mod);
}

/* Finder patterns and the alignment block go emerald: it is the fastest way
   to say "these are your brand colours" without a caption. */
.ch-qrhero-eyes {
    color: var(--ch-cta);
}

/* Scanner viewfinder. Eight gradients rather than four elements, so the whole
   reticle is one span with nothing to lay out. */
.ch-qrhero-reticle {
    position: absolute;
    inset: 5.5%;
    pointer-events: none;
    --ch-qh-ret: var(--ch-qr-accent-line);
    background-image:
        linear-gradient(var(--ch-qh-ret), var(--ch-qh-ret)), linear-gradient(var(--ch-qh-ret), var(--ch-qh-ret)),
        linear-gradient(var(--ch-qh-ret), var(--ch-qh-ret)), linear-gradient(var(--ch-qh-ret), var(--ch-qh-ret)),
        linear-gradient(var(--ch-qh-ret), var(--ch-qh-ret)), linear-gradient(var(--ch-qh-ret), var(--ch-qh-ret)),
        linear-gradient(var(--ch-qh-ret), var(--ch-qh-ret)), linear-gradient(var(--ch-qh-ret), var(--ch-qh-ret));
    background-repeat: no-repeat;
    background-size: 13px 2px, 2px 13px, 13px 2px, 2px 13px, 13px 2px, 2px 13px, 13px 2px, 2px 13px;
    background-position:
        0 0, 0 0,
        100% 0, 100% 0,
        0 100%, 0 100%,
        100% 100%, 100% 100%;
}

/* The scan beam is a 2px band inside a full size background, so the sweep is
   a background-position animation on one element - no wrapper, no clipping,
   and no dependence on the stage's pixel height. */
.ch-qrhero-beam {
    position: absolute;
    inset: 9%;
    pointer-events: none;
    opacity: 0;
    background-image: linear-gradient(90deg, transparent 0%, var(--ch-cta) 22%, #6EE7B7 50%, var(--ch-cta) 78%, transparent 100%);
    background-repeat: no-repeat;
    background-size: 100% 2px;
    background-position: 0 0;
    filter: drop-shadow(0 0 7px rgba(16, 185, 129, 0.8));
}

/* Centre brand plate: the "logo in the middle" feature, shown rather than
   claimed. It sits on the 7x7 module reserve the matrix generator leaves. */
.ch-qrhero-brand {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 23%;
    aspect-ratio: 1;
    border-radius: 26%;
    background-color: var(--ch-qh-plate);
    color: var(--ch-cta);
    box-shadow: 0 6px 16px -8px rgba(15, 23, 42, 0.45);
}

.ch-qrhero-brand svg {
    width: 55%;
    height: 55%;
}

.ch-qrhero-readout {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.5rem;
    margin: 0.85rem 0 0;
}

.ch-qrhero-readout > div {
    min-width: 0;
    padding: 0.5rem 0.55rem;
    border-radius: 9px;
    background-color: var(--ch-qh-stage);
    text-align: center;
}

.ch-qrhero-readout dt {
    margin: 0 0 0.15rem;
    font-family: 'JetBrains Mono', 'Roboto Mono', monospace;
    font-size: 0.56rem;
    font-weight: 700;
    letter-spacing: 1.1px;
    text-transform: uppercase;
    color: var(--ch-text-muted);
}

.ch-qrhero-readout dd {
    margin: 0;
    font-size: 0.76rem;
    font-weight: 700;
    color: var(--ch-text-main);
    white-space: nowrap;
}

/* --- Motion --------------------------------------------------------------
   All of it lives inside prefers-reduced-motion: no-preference. Nothing here
   starts at opacity 0 outside that query, so a reader who opts out of motion
   gets the finished panel rather than an empty one - the failure mode the
   shared .ch-hero cascade has to unwind with !important. */
@media (prefers-reduced-motion: no-preference) {
    .ch-qrhero-rail {
        animation: ch-qh-rail 9s ease-in-out 0.6s infinite;
    }

    .ch-qrhero-dot::after {
        animation: ch-qh-ping 2.4s cubic-bezier(0, 0, 0.2, 1) infinite;
    }

    .ch-qrhero-title span {
        animation: ch-qh-shimmer 6s linear infinite;
    }

    .ch-qrhero-copy > *,
    .ch-qrhero-plate,
    .ch-qrhero-trust {
        animation: ch-qh-rise 0.65s cubic-bezier(0.22, 1, 0.36, 1) both;
    }

    .ch-qrhero-kicker  { animation-delay: 0.04s; }
    .ch-qrhero-title   { animation-delay: 0.10s; }
    .ch-qrhero-sub     { animation-delay: 0.18s; }
    .ch-qrhero-actions { animation-delay: 0.26s; }
    .ch-qrhero-plate   { animation-delay: 0.14s; }
    .ch-qrhero-trust   { animation-delay: 0.32s; }

    /* Modules materialise on a diagonal mask sweep. One animated property on
       one element beats staggering ~170 <use> nodes, and it finishes fully
       unmasked, so nothing is left clipped once the sweep is done. */
    .ch-qrhero-matrix {
        -webkit-mask-image: linear-gradient(135deg, #000 0 34%, transparent 56%);
        mask-image: linear-gradient(135deg, #000 0 34%, transparent 56%);
        -webkit-mask-size: 300% 300%;
        mask-size: 300% 300%;
        animation: ch-qh-materialize 1.1s cubic-bezier(0.22, 1, 0.36, 1) 0.3s both;
    }

    .ch-qrhero-mods .is-live {
        fill: var(--ch-cta);
        animation: ch-qh-flicker 4.2s ease-in-out infinite;
        animation-delay: calc(var(--i, 0) * 210ms);
    }

    .ch-qrhero-beam {
        animation: ch-qh-beam 4.8s cubic-bezier(0.6, 0, 0.35, 1) 1.2s infinite;
    }
}

@keyframes ch-qh-rise {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
}

@keyframes ch-qh-rail {
    0%, 100% { background-position: 130% 0; }
    50%      { background-position: -30% 0; }
}

@keyframes ch-qh-ping {
    0%        { transform: scale(1);   opacity: 0.55; }
    70%, 100% { transform: scale(3.2); opacity: 0; }
}

@keyframes ch-qh-shimmer {
    from { background-position: 220% center; }
    to   { background-position: -20% center; }
}

@keyframes ch-qh-materialize {
    from { -webkit-mask-position: 100% 100%; mask-position: 100% 100%; }
    to   { -webkit-mask-position: 0% 0%;     mask-position: 0% 0%; }
}

@keyframes ch-qh-flicker {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.25; }
}

@keyframes ch-qh-beam {
    0%        { background-position: 0 0%;   opacity: 0; }
    12%       { opacity: 1; }
    78%       { opacity: 1; }
    92%, 100% { background-position: 0 100%; opacity: 0; }
}

/* ==========================================================================
   2. TOOL SHELL AND TYPE TABS
   ========================================================================== */
.ch-qr-app {
    max-width: 1200px;
    margin: 0 auto 4rem;
    /* The hero CTA jumps to #chQrTool and .site-header is position: sticky, so
       without this the type tabs land underneath it. 110px is the offset the
       sticky sidebar in style.css already assumes for the same header. */
    scroll-margin-top: 110px;
}

.ch-qr-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    padding: 0.35rem;
    margin-bottom: 1rem;
    background-color: var(--ch-qr-well);
    border: 1px solid var(--ch-border);
    border-radius: 12px;
}

/* :focus and :active are pinned to the resting paint - see the hard override
   note at the top of this file. Every button block below does the same. */
.ch-qr-tab,
.ch-qr-tab:focus,
.ch-qr-tab:active {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    flex: 1 1 auto;
    justify-content: center;
    padding: 0.6rem 0.9rem;
    border: 1px solid transparent;
    border-radius: 9px;
    background-color: transparent;
    color: var(--ch-text-muted);
    font-family: inherit;
    font-size: 0.86rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    cursor: pointer;
    transition: background-color 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}

.ch-qr-tab svg {
    transition: color 0.2s ease, transform 0.3s var(--ch-qr-ease);
}

.ch-qr-tab:hover {
    color: var(--ch-text-main);
    background-color: var(--ch-qr-tile-hover);
}

.ch-qr-tab:hover svg {
    transform: translateY(-1px);
    color: var(--ch-qr-accent);
}

.ch-qr-tab[aria-selected="true"],
.ch-qr-tab[aria-selected="true"]:focus,
.ch-qr-tab[aria-selected="true"]:active,
.ch-qr-tab[aria-selected="true"]:hover {
    background-color: var(--ch-qr-surface);
    border-color: var(--ch-border);
    color: var(--ch-text-main);
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
}

.ch-qr-tab[aria-selected="true"] svg {
    color: var(--ch-cta);
}

.ch-qr-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 380px;
    gap: 1.25rem;
    align-items: start;
}

/* ==========================================================================
   3. SETTINGS CARDS
   ========================================================================== */
.ch-qr-card {
    background-color: var(--ch-qr-surface);
    border: 1px solid var(--ch-border);
    border-radius: var(--ch-qr-radius);
    box-shadow: var(--ch-qr-shadow);
    overflow: hidden;
    transition: border-color 0.25s ease, box-shadow 0.25s ease;
}

.ch-qr-card:hover {
    border-color: var(--ch-qr-line-soft);
}

.ch-qr-card + .ch-qr-card {
    margin-top: 1.25rem;
}

.ch-qr-card-head,
.ch-qr-card-head:focus,
.ch-qr-card-head:active {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 1.1rem 1.35rem;
    border: 0;
    background-color: transparent;
    color: var(--ch-text-main);
    font-family: inherit;
    cursor: pointer;
    transition: background-color 0.2s ease, color 0.2s ease;
}

/* Both properties, always. Declaring the background alone left GP's white
   text in place and the CONTENT / DESIGN titles read as blank on light. */
.ch-qr-card-head:hover {
    background-color: var(--ch-qr-tile);
    color: var(--ch-text-main);
}

.ch-qr-card-head:hover .ch-qr-card-title svg {
    color: var(--ch-qr-accent);
}

.ch-qr-card-head:hover .ch-qr-chev {
    border-color: var(--ch-text-main);
}

.ch-qr-card-title {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.76rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.ch-qr-card-title svg {
    color: var(--ch-text-muted);
    transition: color 0.2s ease;
}

.ch-qr-chev {
    width: 9px;
    height: 9px;
    border-right: 2px solid var(--ch-text-muted);
    border-bottom: 2px solid var(--ch-text-muted);
    transform: rotate(-135deg);
    transition: transform 0.24s ease;
}

.ch-qr-card:not(.is-open) .ch-qr-chev {
    transform: rotate(45deg);
}

.ch-qr-card-body {
    padding: 0 1.35rem 1.35rem;
}

.ch-qr-card:not(.is-open) .ch-qr-card-body {
    display: none;
}

/* ==========================================================================
   4. FORM CONTROLS
   ========================================================================== */
.ch-qr-field {
    margin-bottom: 1rem;
}

.ch-qr-field:last-child {
    margin-bottom: 0;
}

.ch-qr-field label,
.ch-qr-color label {
    display: block;
    margin-bottom: 0.4rem;
    color: var(--ch-text-muted);
    font-size: 0.73rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.ch-qr-field input[type="text"],
.ch-qr-field input[type="url"],
.ch-qr-field input[type="email"],
.ch-qr-field input[type="tel"],
.ch-qr-field select,
.ch-qr-field textarea,
.ch-qr-hex {
    width: 100%;
    padding: 0.7rem 0.85rem;
    border: 1px solid var(--ch-border);
    border-radius: 9px;
    background-color: var(--ch-qr-surface);
    color: var(--ch-text-main);
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.9rem;
    line-height: 1.5;
    transition: border-color 0.18s ease, box-shadow 0.18s ease;
}

.ch-qr-field select {
    font-family: inherit;
}

.ch-qr-field textarea {
    resize: vertical;
    min-height: 110px;
}

.ch-qr-field input:hover,
.ch-qr-field select:hover,
.ch-qr-field textarea:hover,
.ch-qr-hex:hover {
    border-color: #CBD5E1;
}

.ch-qr-field input:focus,
.ch-qr-field select:focus,
.ch-qr-field textarea:focus,
.ch-qr-hex:focus {
    outline: none;
    border-color: var(--ch-cta);
    box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.18);
}

.ch-qr-field input::placeholder,
.ch-qr-field textarea::placeholder {
    color: #94A3B8;
}

.ch-qr-hint {
    margin: 0.5rem 0 0;
    color: var(--ch-text-muted);
    font-size: 0.82rem;
    line-height: 1.55;
}

.ch-qr-field-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 1rem;
}

.ch-qr-field-grid .col-2 {
    grid-column: 1 / -1;
}

/* --- Option row -----------------------------------------------------------
   A native checkbox with an accent-color is a browser widget, not a control
   that belongs next to the rest of this panel: it renders differently on every
   OS, it offers nowhere to explain what ticking it does, and its 16px target
   is below the touch minimum. So the input is taken out of the flow (kept for
   the accessibility tree and keyboard focus) and the visible control is a full
   width tile that states the consequence underneath the label. */
.ch-qr-check {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 0.7rem;
    align-items: start;
    margin-top: 0.85rem;
    padding: 0.8rem 0.9rem;
    border: 1px solid var(--ch-border);
    border-radius: 11px;
    background-color: var(--ch-qr-tile);
    cursor: pointer;
    transition: border-color 0.18s, background-color 0.18s;
}

.ch-qr-check:hover {
    border-color: var(--ch-qr-line-soft);
    background-color: var(--ch-qr-tile-hover);
}

/* Visually hidden, never display:none - that would drop it out of the tab
   order and off the accessibility tree along with it. */
.ch-qr-check input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.ch-qr-check-box {
    position: relative;
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    margin-top: 0.08rem;
    border: 1.5px solid var(--ch-qr-line-hard);
    border-radius: 6px;
    background-color: var(--ch-qr-surface);
    transition: background-color 0.18s var(--ch-qr-ease),
                border-color 0.18s var(--ch-qr-ease),
                box-shadow 0.18s var(--ch-qr-ease);
}

/* Tick drawn from two borders rather than an SVG: it scales with the box, it
   inherits the transition, and it costs no markup in a control that repeats. */
.ch-qr-check-box::after {
    content: '';
    position: absolute;
    left: 6px;
    top: 2px;
    width: 5px;
    height: 10px;
    border: solid #FFFFFF;
    border-width: 0 2.5px 2.5px 0;
    border-radius: 1px;
    transform: rotate(45deg) scale(0.5);
    opacity: 0;
    transition: opacity 0.16s var(--ch-qr-ease), transform 0.16s var(--ch-qr-ease);
}

.ch-qr-check:hover .ch-qr-check-box {
    border-color: var(--ch-qr-accent);
}

.ch-qr-check input:checked ~ .ch-qr-check-box {
    background-color: var(--ch-cta);
    border-color: var(--ch-cta);
}

.ch-qr-check input:checked ~ .ch-qr-check-box::after {
    opacity: 1;
    transform: rotate(45deg) scale(1);
}

.ch-qr-check input:focus-visible ~ .ch-qr-check-box {
    box-shadow: 0 0 0 3px var(--ch-qr-accent-bg-hi);
    border-color: var(--ch-cta);
}

.ch-qr-check-text {
    display: grid;
    gap: 0.2rem;
    color: var(--ch-text-main);
    font-size: 0.89rem;
    font-weight: 600;
    line-height: 1.45;
}

.ch-qr-check-text small {
    color: var(--ch-text-muted);
    font-size: 0.79rem;
    font-weight: 400;
    line-height: 1.55;
}

/* Checked state tints the whole tile, so the active option is readable at a
   glance without hunting for a 20px box. */
.ch-qr-check:has(input:checked) {
    border-color: var(--ch-qr-accent-line);
    background-color: var(--ch-qr-accent-bg);
}

@media (prefers-reduced-motion: reduce) {
    .ch-qr-check,
    .ch-qr-check-box,
    .ch-qr-check-box::after {
        transition: none;
    }
}

/* --- Range readout --------------------------------------------------------
   Paired with .ch-qr-field-head so the label and the live value sit on one
   line above the track. */
.ch-qr-field-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
}

.ch-qr-field-head label {
    margin-bottom: 0;
}

.ch-qr-range-value {
    flex-shrink: 0;
    padding: 0.15rem 0.5rem;
    border-radius: 6px;
    background-color: var(--ch-qr-well);
    color: var(--ch-text-main);
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.76rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    transition: background-color 0.18s, color 0.18s;
}

/* Past 22% of the code width the recovery budget starts to matter, which is
   exactly what the hint underneath says. The number says it too. */
.ch-qr-range-value.is-risky {
    background-color: var(--ch-qr-warn-bg);
    color: var(--ch-qr-warn-text);
}

.ch-qr-field input[type="range"] {
    width: 100%;
    margin-top: 0.5rem;
    accent-color: var(--ch-cta);
}

/* ==========================================================================
   5. TRACKING SWITCH
   ========================================================================== */
.ch-qr-track {
    margin-top: 1.4rem;
    padding-top: 1.3rem;
    border-top: 1px solid var(--ch-border);
}

.ch-qr-switch {
    display: flex;
    align-items: flex-start;
    gap: 0.8rem;
    cursor: pointer;
}

.ch-qr-switch input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.ch-qr-switch-ui {
    position: relative;
    flex-shrink: 0;
    width: 44px;
    height: 25px;
    margin-top: 1px;
    background-color: #CBD5E1;
    border-radius: 999px;
    transition: background-color 0.22s ease;
}

.ch-qr-switch:hover input:not(:checked) + .ch-qr-switch-ui {
    background-color: #94A3B8;
}

.ch-qr-switch-ui::after {
    content: '';
    position: absolute;
    top: 3px;
    left: 3px;
    width: 19px;
    height: 19px;
    background-color: #FFFFFF;
    border-radius: 50%;
    box-shadow: 0 1px 3px rgba(15, 23, 42, 0.3);
    transition: transform 0.22s cubic-bezier(0.34, 1.4, 0.64, 1);
}

.ch-qr-switch input:checked + .ch-qr-switch-ui {
    background-color: var(--ch-cta);
}

.ch-qr-switch input:checked + .ch-qr-switch-ui::after {
    transform: translateX(19px);
}

.ch-qr-switch input:focus-visible + .ch-qr-switch-ui {
    box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.3);
}

.ch-qr-switch-label {
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.35;
}

.ch-qr-switch-label small {
    display: block;
    margin-top: 0.2rem;
    color: var(--ch-text-muted);
    font-size: 0.8rem;
    font-weight: 400;
}

.ch-qr-track.is-unavailable .ch-qr-switch {
    opacity: 0.5;
    cursor: not-allowed;
}

.ch-qr-track-panel {
    margin-top: 1rem;
    padding: 0.9rem 1rem;
    background-color: var(--ch-qr-well);
    border: 1px solid var(--ch-border);
    border-radius: 10px;
}

.ch-qr-track-link {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.ch-qr-track-label {
    width: 100%;
    color: var(--ch-text-muted);
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.ch-qr-track-link code {
    flex: 1 1 auto;
    padding: 0.4rem 0.6rem;
    background-color: var(--ch-qr-surface);
    border: 1px solid var(--ch-border);
    border-radius: 7px;
    font-size: 0.84rem;
    word-break: break-all;
}

/* ==========================================================================
   6. DESIGN SUB-TABS AND SWATCHES
   ========================================================================== */
.ch-qr-dtabs {
    display: flex;
    gap: 1.4rem;
    margin-bottom: 1.25rem;
    border-bottom: 1px solid var(--ch-border);
}

/* An underline tab, so it must stay flat in every state. GP was filling it
   with a dark slate block on hover and focus, which buried the label. */
.ch-qr-dtab,
.ch-qr-dtab:hover,
.ch-qr-dtab:focus,
.ch-qr-dtab:active {
    padding: 0 0 0.7rem;
    border: 0;
    border-bottom: 2px solid transparent;
    background-color: transparent;
    font-family: inherit;
    font-size: 0.88rem;
    font-weight: 600;
    cursor: pointer;
    transition: color 0.18s ease, border-color 0.18s ease;
    margin-bottom: -1px;
}

.ch-qr-dtab,
.ch-qr-dtab:focus,
.ch-qr-dtab:active {
    color: var(--ch-text-muted);
}

.ch-qr-dtab:hover {
    color: var(--ch-qr-accent);
    border-bottom-color: var(--ch-qr-accent-line);
}

.ch-qr-dtab[aria-selected="true"],
.ch-qr-dtab[aria-selected="true"]:focus,
.ch-qr-dtab[aria-selected="true"]:active {
    color: var(--ch-text-main);
    border-bottom-color: var(--ch-cta);
}

.ch-qr-dtab[aria-selected="true"]:hover {
    color: var(--ch-text-main);
    border-bottom-color: var(--ch-cta);
}

/* Group label inside the design panels. Renamed from .ch-qr-sub, which used
   to collide with the hero paragraph class and silently reformatted the hero
   subtitle into a tiny uppercase mono label that lost its centring. */
.ch-qr-sublabel {
    margin: 1.25rem 0 0.6rem;
    color: var(--ch-text-muted);
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.71rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

.ch-qr-sublabel:first-child {
    margin-top: 0;
}

.ch-qr-swatches {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
    gap: 0.5rem;
}

/* The call to action field is a setting that belongs to the frame chosen
   above it, not a new section, so it gets a hairline and real breathing room
   rather than sitting flush against the last row of swatches. Without this the
   label reads as a caption for the swatch grid. */
.ch-qr-swatches + .ch-qr-field {
    margin-top: 1.35rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--ch-border);
}

.ch-qr-swatches--frames {
    grid-template-columns: repeat(auto-fill, minmax(94px, 1fr));
}

/* The thumbnails are drawn in currentColor with a punched-out "paper" panel,
   so the button's text colour IS the artwork. GP forcing colour:#FFF on hover
   is what turned Label below, Terminal and Shopping bag into white blobs.
   Colour is therefore pinned in every state and only the surface moves. */
.ch-qr-swatch,
.ch-qr-swatch:focus,
.ch-qr-swatch:active {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.4rem;
    padding: 0.7rem 0.4rem 0.55rem;
    border: 1px solid var(--ch-border);
    border-radius: 10px;
    background-color: var(--ch-qr-tile);
    color: var(--ch-text-main);
    font-family: inherit;
    cursor: pointer;
    transition: border-color 0.2s ease, background-color 0.2s ease, box-shadow 0.25s ease,
                transform 0.25s var(--ch-qr-ease);
}

.ch-qr-swatch svg {
    transition: transform 0.3s var(--ch-qr-ease);
}

/* The paper inside a frame thumbnail. Themed, never hardcoded white: on slate
   the body is drawn light, so a white panel would erase the whole silhouette. */
.ch-qr-swatch .ch-qr-void {
    fill: var(--ch-qr-void);
}

.ch-qr-swatch:hover {
    background-color: var(--ch-qr-tile-hover);
    color: var(--ch-text-main);
    border-color: var(--ch-qr-line-hard);
    transform: translateY(-2px);
    box-shadow: var(--ch-qr-shadow-lift);
}

.ch-qr-swatch:hover svg {
    transform: scale(1.08);
}

.ch-qr-swatch:hover .ch-qr-swatch-name {
    color: var(--ch-text-main);
}

.ch-qr-swatch[aria-pressed="true"],
.ch-qr-swatch[aria-pressed="true"]:focus,
.ch-qr-swatch[aria-pressed="true"]:active {
    border-color: var(--ch-cta);
    background-color: var(--ch-qr-accent-bg);
    color: var(--ch-text-main);
    box-shadow: inset 0 0 0 1px var(--ch-cta);
}

.ch-qr-swatch[aria-pressed="true"]:hover {
    background-color: var(--ch-qr-accent-bg-hi);
    color: var(--ch-text-main);
    border-color: var(--ch-cta);
    box-shadow: inset 0 0 0 1px var(--ch-cta), var(--ch-qr-shadow-lift);
}

/* A pressed tile paints an emerald wash, so its paper follows the wash rather
   than the flat tile or the punch-out reads as a hard white patch. */
.ch-qr-swatch[aria-pressed="true"] .ch-qr-void {
    fill: color-mix(in srgb, var(--ch-cta) 8%, var(--ch-qr-void));
}

.ch-qr-swatch-name {
    font-size: 0.68rem;
    font-weight: 600;
    color: var(--ch-text-muted);
    text-align: center;
    line-height: 1.25;
    transition: color 0.2s ease;
}

.ch-qr-swatch[aria-pressed="true"] .ch-qr-swatch-name {
    color: var(--ch-text-main);
}

/* ==========================================================================
   7. COLOURS AND UPLOAD
   ========================================================================== */
.ch-qr-colors {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 0.9rem;
}

.ch-qr-color-row {
    display: flex;
    gap: 0.5rem;
    align-items: center;
}

.ch-qr-color input[type="color"] {
    flex-shrink: 0;
    width: 42px;
    height: 40px;
    padding: 3px;
    border: 1px solid var(--ch-border);
    border-radius: 9px;
    background-color: var(--ch-qr-surface);
    cursor: pointer;
}

.ch-qr-color input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.ch-qr-color input[type="color"]::-webkit-color-swatch { border: 0; border-radius: 6px; }
.ch-qr-color input[type="color"]::-moz-color-swatch { border: 0; border-radius: 6px; }

.ch-qr-hex {
    text-transform: uppercase;
}

.ch-qr-upload {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.7rem;
    margin-bottom: 1.1rem;
}

.ch-qr-upload input[type="file"] {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    overflow: hidden;
}

.ch-qr-upload-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.65rem 1rem;
    border: 1px dashed var(--ch-border);
    border-radius: 10px;
    color: var(--ch-text-main);
    font-size: 0.88rem;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.18s ease, background-color 0.18s ease;
    margin-bottom: 0;
    text-transform: none;
    letter-spacing: normal;
}

.ch-qr-upload-btn:hover {
    border-color: var(--ch-cta);
    background-color: rgba(16, 185, 129, 0.06);
}

.ch-qr-upload input[type="file"]:focus-visible + .ch-qr-upload-btn {
    outline: 2px solid var(--ch-cta);
    outline-offset: 2px;
}

.ch-qr-upload-name {
    color: var(--ch-text-muted);
    font-size: 0.82rem;
    word-break: break-all;
}

.ch-qr-mini,
.ch-qr-mini:focus,
.ch-qr-mini:active {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 0.45rem 0.75rem;
    border: 1px solid var(--ch-border);
    border-radius: 8px;
    background-color: var(--ch-qr-tile);
    color: var(--ch-text-muted);
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 600;
    cursor: pointer;
    transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease,
                transform 0.2s ease, box-shadow 0.2s ease;
}

.ch-qr-mini:hover {
    background-color: var(--ch-qr-accent-bg);
    color: var(--ch-qr-accent);
    border-color: var(--ch-qr-accent-line);
    transform: translateY(-1px);
}

.ch-qr-mini:hover svg {
    color: var(--ch-qr-accent);
}

.ch-qr-mini--wide {
    width: 100%;
    margin-top: 0.6rem;
}

.ch-qr-mini[data-copied],
.ch-qr-mini[data-copied]:hover {
    color: var(--ch-qr-accent);
    border-color: var(--ch-cta);
}

/* ==========================================================================
   7B. CUSTOM COLOUR PICKER
   qr-picker.js hides the native input (which opens the OS dialog) and swaps
   in the trigger button + branded popover. With JS off the native control
   stays, so the OS dialog remains the fallback rather than a dead button.
   ========================================================================== */

/* Trigger: same footprint the native swatch had, house hover grammar */
.ch-qr-color-row.is-enhanced input[type="color"] {
    display: none;
}

/* qr-picker.js stamps this on the settings card while the popover is open:
   the card's overflow:hidden (rounded-corner clipping) must not clip it */
.ch-qr-card.ch-cp-host {
    overflow: visible;
}

.ch-qr-color-btn,
.ch-qr-color-btn:hover,
.ch-qr-color-btn:focus,
.ch-qr-color-btn:active {
    flex-shrink: 0;
    width: 42px;
    height: 40px;
    padding: 3px;
    border: 1px solid var(--ch-border);
    border-radius: 9px;
    background-color: var(--ch-qr-surface);
    color: var(--ch-text-main);
    cursor: pointer;
    transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}

.ch-qr-color-btn:hover {
    border-color: var(--ch-qr-line-hard);
    transform: translateY(-1px);
    box-shadow: var(--ch-qr-shadow);
}

.ch-qr-color-btn[aria-expanded="true"],
.ch-qr-color-btn[aria-expanded="true"]:hover {
    border-color: var(--ch-cta);
    box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.18);
}

.ch-qr-color-chipset {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 6px;
    box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.12);
}

/* --- Popover shell --- */
.ch-qr-color {
    position: relative;
}

.ch-qr-color-row {
    position: relative;
}

.ch-cp-veil {
    display: none;
}

.ch-cp-card {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    z-index: 340;
    width: 264px;
    padding: 0.9rem;
    background-color: var(--ch-qr-surface);
    border: 1px solid var(--ch-border);
    border-radius: 14px;
    box-shadow: 0 24px 48px -18px rgba(15, 23, 42, 0.35), 0 6px 18px -8px rgba(15, 23, 42, 0.12);
    opacity: 0;
    transform: translateY(6px) scale(0.98);
    transition: opacity 0.2s ease, transform 0.25s var(--ch-qr-ease);
}

/* The system hairline every CartHacker panel carries */
.ch-cp-card::before {
    content: '';
    position: absolute;
    top: 0;
    left: 12px;
    right: 12px;
    height: 2px;
    border-radius: 2px;
    background: linear-gradient(90deg, var(--ch-cta), rgba(16, 185, 129, 0));
}

.ch-cp.is-active .ch-cp-card {
    opacity: 1;
    transform: none;
}

.ch-cp.is-flip .ch-cp-card {
    left: auto;
    right: 0;
}

.ch-cp.is-up .ch-cp-card {
    top: auto;
    bottom: calc(100% + 8px);
}

.ch-cp-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}

.ch-cp-kicker {
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.64rem;
    font-weight: 700;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    color: var(--ch-cta);
}

.ch-cp-x,
.ch-cp-x:focus,
.ch-cp-x:active {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border: 0;
    border-radius: 7px;
    background-color: transparent;
    color: var(--ch-text-muted);
    cursor: pointer;
    transition: background-color 0.18s ease, color 0.18s ease;
}

.ch-cp-x:hover {
    background-color: var(--ch-qr-well);
    color: var(--ch-text-main);
}

/* --- Saturation / value field --- */
.ch-cp-sv {
    position: relative;
    height: 148px;
    border-radius: 10px;
    border: 1px solid var(--ch-border);
    cursor: crosshair;
    touch-action: none;
}

.ch-cp-sv::before,
.ch-cp-sv::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
}

.ch-cp-sv::before {
    background: linear-gradient(to right, #FFFFFF, rgba(255, 255, 255, 0));
}

.ch-cp-sv::after {
    background: linear-gradient(to top, #000000, rgba(0, 0, 0, 0));
}

.ch-cp-sv:focus-visible {
    outline: 2px solid var(--ch-cta);
    outline-offset: 2px;
}

.ch-cp-thumb {
    position: absolute;
    z-index: 1;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    border: 2px solid #FFFFFF;
    box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.35), 0 2px 6px rgba(15, 23, 42, 0.3);
    transform: translate(-50%, -50%);
    pointer-events: none;
}

/* --- Hue rail --- */
.ch-cp-hue {
    position: relative;
    height: 14px;
    margin-top: 0.7rem;
    border-radius: 999px;
    border: 1px solid rgba(15, 23, 42, 0.12);
    background: linear-gradient(90deg, #F00 0%, #FF0 17%, #0F0 33%, #0FF 50%, #00F 67%, #F0F 83%, #F00 100%);
    cursor: pointer;
    touch-action: none;
}

.ch-cp-hue:focus-visible {
    outline: 2px solid var(--ch-cta);
    outline-offset: 2px;
}

.ch-cp-thumb--hue {
    top: 50%;
    width: 18px;
    height: 18px;
    background: transparent;
}

/* --- Value row: chip + hex + eyedropper --- */
.ch-cp-io {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.75rem;
}

.ch-cp-chip {
    flex-shrink: 0;
    width: 34px;
    height: 34px;
    border-radius: 9px;
    border: 1px solid var(--ch-border);
    box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.08);
}

.ch-cp-hex {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.5rem 0.6rem;
    border: 1px solid var(--ch-border);
    border-radius: 8px;
    background-color: var(--ch-qr-surface);
    color: var(--ch-text-main);
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.84rem;
    font-weight: 600;
    text-transform: uppercase;
    transition: border-color 0.18s ease, box-shadow 0.18s ease;
}

.ch-cp-hex:hover {
    border-color: #CBD5E1;
}

.ch-cp-hex:focus {
    outline: none;
    border-color: var(--ch-cta);
    box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.18);
}

.ch-cp-eye,
.ch-cp-eye:focus,
.ch-cp-eye:active {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border: 1px solid var(--ch-border);
    border-radius: 9px;
    background-color: var(--ch-qr-surface);
    color: var(--ch-text-muted);
    cursor: pointer;
    transition: color 0.18s ease, background-color 0.18s ease, border-color 0.18s ease,
                transform 0.18s ease;
}

.ch-cp-eye:hover {
    background-color: var(--ch-qr-accent-bg);
    color: var(--ch-qr-accent);
    border-color: var(--ch-qr-accent-line);
    transform: translateY(-1px);
}

/* --- Preset + recent swatches --- */
.ch-cp-sec {
    margin-top: 0.8rem;
}

.ch-cp-sub {
    display: block;
    margin-bottom: 0.45rem;
    color: var(--ch-text-muted);
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

.ch-cp-swatches {
    display: grid;
    grid-template-columns: repeat(8, 1fr);
    gap: 0.35rem;
}

.ch-cp-swatch {
    aspect-ratio: 1;
    width: 100%;
    border: 1px solid rgba(15, 23, 42, 0.14);
    border-radius: 7px;
    cursor: pointer;
    padding: 0;
    transition: transform 0.2s var(--ch-qr-ease), box-shadow 0.2s ease;
}

.ch-cp-swatch:hover {
    transform: translateY(-2px) scale(1.08);
    box-shadow: 0 4px 10px -3px rgba(15, 23, 42, 0.35);
}

.ch-cp-swatch:focus-visible {
    outline: 2px solid var(--ch-cta);
    outline-offset: 2px;
}

/* --- Mobile: centred sheet over a veil, not a cramped anchored popover --- */
@media (max-width: 640px) {
    .ch-cp-veil {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 330;
        background-color: rgba(15, 23, 42, 0.5);
        -webkit-backdrop-filter: blur(4px);
        backdrop-filter: blur(4px);
        opacity: 0;
        transition: opacity 0.2s ease;
    }

    .ch-cp.is-active .ch-cp-veil {
        opacity: 1;
    }

    .ch-cp-card,
    .ch-cp.is-flip .ch-cp-card,
    .ch-cp.is-up .ch-cp-card {
        position: fixed;
        top: 50%;
        left: 50%;
        right: auto;
        bottom: auto;
        width: min(320px, calc(100vw - 2rem));
        transform: translate(-50%, -46%) scale(0.98);
    }

    .ch-cp.is-active .ch-cp-card {
        transform: translate(-50%, -50%);
    }

    .ch-cp-sv {
        height: 180px;
    }
}

/* ==========================================================================
   8. PREVIEW COLUMN
   ========================================================================== */
.ch-qr-preview-col {
    position: sticky;
    top: 100px;
}

.ch-qr-preview-card {
    padding: 1.35rem;
    background-color: var(--ch-qr-surface);
    border: 1px solid var(--ch-border);
    border-radius: var(--ch-qr-radius);
    box-shadow: var(--ch-qr-shadow);
}

.ch-qr-preview {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 260px;
    padding: 1rem;
    background-color: var(--ch-qr-well);
    border-radius: 10px;
    background-image:
        linear-gradient(45deg, rgba(148, 163, 184, 0.16) 25%, transparent 25%),
        linear-gradient(-45deg, rgba(148, 163, 184, 0.16) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, rgba(148, 163, 184, 0.16) 75%),
        linear-gradient(-45deg, transparent 75%, rgba(148, 163, 184, 0.16) 75%);
    background-size: 16px 16px;
    background-position: 0 0, 0 8px, 8px -8px, -8px 0;
}

/* Transparent exports need a backdrop that contrasts with the CODE, not with
   the page. A dark code over the dark theme's chequer was simply invisible,
   and a light code over the light one has the same problem. qr-tool.js reads
   the code colour's luminance and stamps data-backdrop, so the preview always
   shows the pattern while the downloaded file stays genuinely transparent.
   The chequer itself stays: it is the only honest signal of no background.
   Specificity is raised past html[data-theme="dark"] .ch-qr-preview (0,2,1)
   on purpose - the backdrop follows the artwork, not the theme. */
.ch-page-qr .ch-qr-preview[data-backdrop="light"] {
    background-color: #FFFFFF;
    background-image:
        linear-gradient(45deg, rgba(15, 23, 42, 0.10) 25%, transparent 25%),
        linear-gradient(-45deg, rgba(15, 23, 42, 0.10) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, rgba(15, 23, 42, 0.10) 75%),
        linear-gradient(-45deg, transparent 75%, rgba(15, 23, 42, 0.10) 75%);
}

.ch-page-qr .ch-qr-preview[data-backdrop="dark"] {
    background-color: #0F172A;
    background-image:
        linear-gradient(45deg, rgba(248, 250, 252, 0.13) 25%, transparent 25%),
        linear-gradient(-45deg, rgba(248, 250, 252, 0.13) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, rgba(248, 250, 252, 0.13) 75%),
        linear-gradient(-45deg, transparent 75%, rgba(248, 250, 252, 0.13) 75%);
}

.ch-qr-preview canvas {
    display: block;
    max-width: 100%;
    height: auto;
}

/* First render pops the code in; re-renders while typing stay still */
@media (prefers-reduced-motion: no-preference) {
    .ch-qr-preview:not(.is-empty) canvas {
        animation: ch-qr-pop 0.4s var(--ch-qr-ease);
    }
}

@keyframes ch-qr-pop {
    from { opacity: 0; transform: scale(0.94); }
    to   { opacity: 1; transform: none; }
}

.ch-qr-preview.is-empty canvas {
    display: none;
}

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

.ch-qr-empty svg {
    opacity: 0.5;
}

.ch-qr-empty p {
    margin: 0.6rem 0 0;
    font-size: 0.85rem;
    max-width: 22ch;
}

.ch-qr-status {
    margin: 0.9rem 0 0;
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.73rem;
    letter-spacing: 0.03em;
    color: var(--ch-text-muted);
}

.ch-qr-status[data-kind="error"] {
    color: #B91C1C;
}

.ch-qr-status[data-kind="ok"] {
    color: #047857;
}

/* Scanability notice. Two severities in the house alert grammar used by
   .ch-portal-alert: a low-percentage wash, a 35% hairline of the same hue and
   solid text, rather than the flat cream box this used to be. Amber advises,
   red blocks - a contrast failure and a logo warning are not the same event. */
.ch-qr-note {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    margin: 0.8rem 0 0;
    padding: 0.7rem 0.85rem;
    border: 1px solid var(--ch-qr-warn-line);
    border-left-width: 3px;
    border-radius: 9px;
    background-color: var(--ch-qr-warn-bg);
    color: var(--ch-qr-warn-text);
    font-size: 0.81rem;
    font-weight: 500;
    line-height: 1.55;
}

/* Moved out of the note block: this guard is tool-wide, see section 0 note. */

.ch-qr-note-ico {
    flex-shrink: 0;
    display: flex;
    margin-top: 0.08rem;
    color: var(--ch-qr-warn-ico);
}

.ch-qr-note[data-level="stop"] {
    border-color: var(--ch-qr-stop-line);
    background-color: var(--ch-qr-stop-bg);
    color: var(--ch-qr-stop-text);
}

.ch-qr-note[data-level="stop"] .ch-qr-note-ico {
    color: var(--ch-qr-stop-ico);
}

/* Confirmation, not a warning. Choosing transparency is a deliberate export
   decision, so it reads as a setting that is active - emerald and a tick -
   rather than borrowing the amber box reserved for scanning risks. */
.ch-qr-flag {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    margin: 0.8rem 0 0;
    padding: 0.7rem 0.85rem;
    border: 1px solid var(--ch-qr-accent-line);
    border-left-width: 3px;
    border-radius: 9px;
    background-color: var(--ch-qr-accent-bg);
    color: var(--ch-text-main);
    font-size: 0.81rem;
    line-height: 1.5;
}

.ch-qr-flag-ico {
    flex-shrink: 0;
    display: flex;
    margin-top: 0.08rem;
    color: var(--ch-qr-accent);
}

.ch-qr-flag strong {
    display: block;
    font-weight: 700;
}

.ch-qr-flag small {
    display: block;
    margin-top: 0.15rem;
    color: var(--ch-text-muted);
    font-size: 0.76rem;
    line-height: 1.5;
}

.ch-qr-download {
    margin-top: 1.2rem;
    padding-top: 1.2rem;
    border-top: 1px solid var(--ch-border);
}

.ch-qr-dl,
.ch-qr-dl:focus,
.ch-qr-dl:active {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.8rem 1rem;
    border: 1px solid var(--ch-border);
    border-radius: 10px;
    background-color: var(--ch-qr-tile);
    color: var(--ch-text-main);
    font-family: inherit;
    font-size: 0.9rem;
    font-weight: 700;
    cursor: pointer;
    transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease,
                color 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
}

.ch-qr-dl--primary,
.ch-qr-dl--primary:focus,
.ch-qr-dl--primary:active {
    background-color: var(--ch-cta);
    border-color: var(--ch-cta);
    color: #FFFFFF;
    box-shadow: 0 8px 20px -10px rgba(16, 185, 129, 0.8);
}

/* Secondary exports lift toward emerald instead of GP's slate-grey block,
   which was the muddy brown-looking hover on the dark theme. */
.ch-qr-dl:not(:disabled):hover {
    background-color: var(--ch-qr-accent-bg);
    color: var(--ch-qr-accent);
    border-color: var(--ch-qr-accent-line);
    transform: translateY(-2px);
    box-shadow: var(--ch-qr-shadow-lift);
}

.ch-qr-dl--primary:not(:disabled):hover {
    background-color: #059669;
    border-color: #059669;
    color: #FFFFFF;
    box-shadow: 0 12px 26px -10px rgba(16, 185, 129, 0.9);
}

.ch-qr-dl:disabled,
.ch-qr-dl:disabled:hover {
    opacity: 0.45;
    cursor: not-allowed;
    transform: none;
}

.ch-qr-dl-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.5rem;
    margin-top: 0.5rem;
}

.ch-qr-dl-row .ch-qr-dl {
    font-size: 0.82rem;
    padding: 0.65rem 0.5rem;
}

/* The print test is a utility action, not an export: it stays quieter than
   the three file buttons above it and separates from them with a hairline. */
.ch-qr-download .ch-qr-mini--wide {
    margin-top: 0.75rem;
    padding: 0.6rem 0.75rem;
    border-style: dashed;
}

/* ==========================================================================
   9. EDITORIAL CONTENT
   ========================================================================== */
.ch-qr-content {
    max-width: 820px;
    margin: 0 auto;
}

.ch-qr-section {
    margin-bottom: 3rem;
    /* Every section is a jump target for the contents list, and .site-header is
       position: sticky. Same 110px the tool shell already assumes. */
    scroll-margin-top: 110px;
}

.ch-qr-section h2 {
    margin: 0 0 1rem;
    font-size: clamp(1.5rem, 3vw, 1.9rem);
    letter-spacing: -0.015em;
}

.ch-qr-section h3 {
    margin: 1.8rem 0 0.6rem;
    font-size: 1.15rem;
}

.ch-qr-section p {
    margin: 0 0 1.1rem;
    line-height: 1.75;
}

.ch-qr-steps {
    list-style: none;
    margin: 1.6rem 0 0;
    padding: 0;
    display: grid;
    gap: 1rem;
}

.ch-qr-steps li {
    display: flex;
    gap: 1rem;
    padding: 1.2rem 1.3rem;
    background-color: var(--ch-qr-surface);
    border: 1px solid var(--ch-border);
    border-radius: var(--ch-qr-radius);
    transition: border-color 0.25s ease, box-shadow 0.25s ease, transform 0.25s var(--ch-qr-ease);
}

.ch-qr-steps li:hover {
    border-color: rgba(16, 185, 129, 0.35);
    box-shadow: var(--ch-qr-shadow-lift);
    transform: translateY(-2px);
}

.ch-qr-step-num {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    background-color: rgba(16, 185, 129, 0.12);
    color: #047857;
    border-radius: 8px;
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.85rem;
    font-weight: 700;
}

.ch-qr-steps h3 {
    margin: 0 0 0.35rem;
    font-size: 1.02rem;
}

.ch-qr-steps p {
    margin: 0;
    color: var(--ch-text-muted);
    font-size: 0.94rem;
    line-height: 1.65;
}

.ch-qr-table-wrap {
    overflow-x: auto;
    margin: 1.5rem 0;
    border: 1px solid var(--ch-border);
    border-radius: var(--ch-qr-radius);
}

.ch-qr-table {
    width: 100%;
    min-width: 520px;
    border-collapse: collapse;
    background-color: var(--ch-qr-surface);
    font-size: 0.92rem;
}

.ch-qr-table th,
.ch-qr-table td {
    padding: 0.8rem 1rem;
    text-align: left;
    border-bottom: 1px solid var(--ch-border);
    vertical-align: top;
}

.ch-qr-table tbody tr {
    transition: background-color 0.2s ease;
}

.ch-qr-table tbody tr:hover {
    background-color: color-mix(in srgb, var(--ch-cta) 5%, transparent);
}

.ch-qr-table thead th {
    background-color: var(--ch-qr-well);
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ch-text-muted);
}

.ch-qr-table tbody th {
    font-weight: 600;
    color: var(--ch-text-main);
}

.ch-qr-table tbody tr:last-child th,
.ch-qr-table tbody tr:last-child td {
    border-bottom: 0;
}

.ch-qr-uses {
    margin: 1.2rem 0 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: 0.8rem;
}

.ch-qr-uses li {
    padding: 0.9rem 1.1rem;
    background-color: var(--ch-qr-surface);
    border: 1px solid var(--ch-border);
    border-left: 3px solid var(--ch-cta);
    border-radius: 0 10px 10px 0;
    line-height: 1.65;
    font-size: 0.95rem;
    transition: border-color 0.25s ease, box-shadow 0.25s ease, transform 0.25s var(--ch-qr-ease);
}

.ch-qr-uses li:hover {
    box-shadow: var(--ch-qr-shadow-lift);
    transform: translateX(3px);
}

.ch-qr-faq {
    display: grid;
    gap: 0.6rem;
}

.ch-qr-faq-item {
    background-color: var(--ch-qr-surface);
    border: 1px solid var(--ch-border);
    border-radius: 10px;
    overflow: hidden;
    transition: border-color 0.25s ease, box-shadow 0.25s ease;
}

.ch-qr-faq-item:hover,
.ch-qr-faq-item[open] {
    border-color: rgba(16, 185, 129, 0.35);
}

.ch-qr-faq-item[open] {
    box-shadow: var(--ch-qr-shadow-lift);
}

.ch-qr-faq-item summary {
    padding: 1rem 1.2rem;
    font-weight: 600;
    cursor: pointer;
    list-style: none;
    position: relative;
    padding-right: 2.8rem;
    transition: color 0.2s ease;
}

.ch-qr-faq-item summary:hover {
    color: var(--ch-cta);
}

.ch-qr-faq-item summary::-webkit-details-marker {
    display: none;
}

.ch-qr-faq-item summary::after {
    content: '';
    position: absolute;
    right: 1.3rem;
    top: 50%;
    width: 8px;
    height: 8px;
    border-right: 2px solid var(--ch-text-muted);
    border-bottom: 2px solid var(--ch-text-muted);
    transform: translateY(-70%) rotate(45deg);
    transition: transform 0.22s ease;
}

.ch-qr-faq-item[open] summary::after {
    transform: translateY(-20%) rotate(-135deg);
}

.ch-qr-faq-item p {
    margin: 0;
    padding: 0 1.2rem 1.2rem;
    color: var(--ch-text-muted);
    line-height: 1.7;
    font-size: 0.95rem;
}

/* --- Lead paragraph ------------------------------------------------------
   One size up from body copy, muted, sitting directly under the tool. It is
   the handover between the app and the guide, so it reads as an abstract
   rather than as the first paragraph of section one. */
.ch-qr-lead {
    font-size: 1.08rem;
    line-height: 1.8;
    color: var(--ch-text-muted);
    margin: 0 0 1.8rem;
}

.ch-qr-lead strong {
    color: var(--ch-text-main);
    font-weight: 600;
}

/* --- Contents ------------------------------------------------------------
   Jump links, two columns on desktop. Numbered through a counter rather than
   an <ol> marker so the index can be coloured and spaced independently of
   the link text. */
.ch-qr-toc {
    margin: 0 0 2.8rem;
    padding: 1.25rem 1.4rem;
    background-color: var(--ch-qr-well);
    border: 1px solid var(--ch-border);
    border-radius: var(--ch-qr-radius);
}

.ch-qr-toc-title {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    margin: 0 0 0.9rem;
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ch-text-muted);
}

.ch-qr-toc-title svg {
    color: var(--ch-qr-accent);
}

.ch-qr-toc ol {
    counter-reset: ch-qr-toc;
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.3rem 1.6rem;
}

.ch-qr-toc li {
    counter-increment: ch-qr-toc;
    margin: 0;
}

.ch-qr-toc a {
    display: flex;
    align-items: baseline;
    gap: 0.55rem;
    padding: 0.3rem 0.4rem 0.3rem 0.2rem;
    border-radius: 7px;
    font-size: 0.92rem;
    line-height: 1.45;
    color: var(--ch-text-main);
    text-decoration: none;
    transition: color 0.2s ease, transform 0.25s var(--ch-qr-ease), background-color 0.2s ease;
}

.ch-qr-toc a::before {
    content: counter(ch-qr-toc, decimal-leading-zero);
    flex-shrink: 0;
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.72rem;
    font-weight: 700;
    color: var(--ch-qr-accent);
}

.ch-qr-toc a:hover {
    color: var(--ch-qr-accent);
    background-color: var(--ch-qr-accent-bg);
    transform: translateX(3px);
}

/* --- Annotated exhibits --------------------------------------------------
   A square photograph pinned at 320px beside its caption panel. Full bleed
   1:1 photos in an 820px reading column cost 820px of height each, and five
   of them turn a technical guide into a scroll. The plate keeps the vertical
   budget at roughly 320px and puts the caption where it is actually read.

   Hover runs three effects at once, all borrowed from house components: the
   frame lifts (steps/uses cards), a sheen sweeps across the photo (article
   images in style.css section 4) and an emerald line scans down the plate,
   which is the hero beam applied to a still image. */
.ch-qr-figure {
    margin: 2.2rem 0 2.6rem;
    display: grid;
    grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
    gap: 1.5rem;
    align-items: center;
}

.ch-qr-figure--flip .ch-qr-figure-frame {
    order: 2;
}

.ch-qr-figure-frame {
    position: relative;
    overflow: hidden;
    border-radius: var(--ch-qr-radius);
    border: 1px solid var(--ch-border);
    border-bottom: 3px solid var(--ch-cta);
    background-color: var(--ch-qr-well);
    box-shadow: var(--ch-qr-shadow);
    transition: transform 0.35s var(--ch-qr-ease), box-shadow 0.35s ease, border-color 0.35s ease;
}

.ch-qr-figure-img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    /* Held slightly back at rest so the emerald interface stays the loudest
       thing on the page, then released on hover. */
    filter: saturate(0.9);
    transform: scale(1.001);
    transition: transform 0.6s var(--ch-qr-ease), filter 0.4s ease;
}

.ch-qr-figure-frame::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 3;
    pointer-events: none;
    background: linear-gradient(105deg, transparent 42%, rgba(255, 255, 255, 0.24) 50%, transparent 58%);
    transform: translateX(-130%);
}

/* inset:0, so translateY(100%) is exactly the height of the plate whatever
   the rendered size turns out to be. No measured pixel value to desync. */
.ch-qr-figure-beam {
    position: absolute;
    inset: 0;
    z-index: 4;
    opacity: 0;
    pointer-events: none;
}

.ch-qr-figure-beam::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: 3px;
    background: linear-gradient(90deg, transparent, rgba(16, 185, 129, 0.95), transparent);
    box-shadow: 0 0 16px 3px rgba(16, 185, 129, 0.45);
}

.ch-qr-figure:hover .ch-qr-figure-frame {
    transform: translateY(-3px);
    box-shadow: var(--ch-qr-shadow-lift);
    border-color: var(--ch-qr-accent-line);
}

.ch-qr-figure:hover .ch-qr-figure-img {
    transform: scale(1.06);
    filter: saturate(1.04);
}

.ch-qr-figure:hover .ch-qr-figure-frame::after {
    transform: translateX(130%);
    transition: transform 0.9s ease;
}

.ch-qr-figure:hover .ch-qr-figure-beam {
    animation: ch-qr-fig-scan 1.5s var(--ch-qr-ease);
}

@keyframes ch-qr-fig-scan {
    0%   { transform: translateY(0);    opacity: 0; }
    12%  { opacity: 1; }
    88%  { opacity: 1; }
    100% { transform: translateY(100%); opacity: 0; }
}

.ch-qr-figure-cap {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}

.ch-qr-figure-idx {
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ch-qr-accent);
}

.ch-qr-figure-title {
    font-size: 1rem;
    font-weight: 700;
    line-height: 1.4;
    color: var(--ch-text-main);
}

.ch-qr-figure-desc {
    font-size: 0.92rem;
    line-height: 1.65;
    color: var(--ch-text-muted);
}

.ch-qr-figure-note {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    margin-top: 0.35rem;
    padding-top: 0.65rem;
    border-top: 1px dashed var(--ch-border);
    font-size: 0.85rem;
    line-height: 1.6;
    color: var(--ch-text-muted);
}

.ch-qr-figure-note svg {
    flex-shrink: 0;
    margin-top: 0.15rem;
    color: var(--ch-qr-accent);
}

/* --- Callouts ------------------------------------------------------------
   Same alert grammar as the tool's own notices, so an advisory in the article
   and an advisory under the preview read as the same voice. */
.ch-qr-callout {
    display: flex;
    align-items: flex-start;
    gap: 0.85rem;
    margin: 1.6rem 0;
    padding: 1.05rem 1.2rem;
    background-color: var(--ch-qr-accent-bg);
    border: 1px solid var(--ch-qr-accent-line);
    border-left-width: 3px;
    border-radius: 0 12px 12px 0;
    font-size: 0.94rem;
    line-height: 1.7;
    transition: transform 0.25s var(--ch-qr-ease), box-shadow 0.25s ease;
}

.ch-qr-callout:hover {
    transform: translateX(3px);
    box-shadow: var(--ch-qr-shadow-lift);
}

.ch-qr-callout > svg {
    flex-shrink: 0;
    margin-top: 0.25rem;
    color: var(--ch-qr-accent);
}

.ch-qr-callout strong {
    display: block;
    margin-bottom: 0.15rem;
    color: var(--ch-text-main);
}

.ch-qr-callout p {
    margin: 0;
    font-size: 0.94rem;
    line-height: 1.7;
}

.ch-qr-callout--warn {
    background-color: var(--ch-qr-warn-bg);
    border-color: var(--ch-qr-warn-line);
}

.ch-qr-callout--warn > svg {
    color: var(--ch-qr-warn-ico);
}

/* --- Do / do not ---------------------------------------------------------
   Two cards side by side. The colour is carried by the heading and the marker
   only: a red washed card reads as an error state, and neither of these is. */
.ch-qr-split {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 1rem;
    margin: 1.6rem 0;
}

.ch-qr-split-card {
    padding: 1.1rem 1.25rem;
    background-color: var(--ch-qr-surface);
    border: 1px solid var(--ch-border);
    border-radius: var(--ch-qr-radius);
    transition: border-color 0.25s ease, box-shadow 0.25s ease, transform 0.25s var(--ch-qr-ease);
}

.ch-qr-split-card:hover {
    box-shadow: var(--ch-qr-shadow-lift);
    transform: translateY(-2px);
}

.ch-qr-split-card h3 {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    margin: 0 0 0.7rem;
    font-size: 0.95rem;
}

.ch-qr-split-card ul {
    margin: 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: 0.5rem;
}

.ch-qr-split-card li {
    position: relative;
    padding-left: 1.1rem;
    font-size: 0.9rem;
    line-height: 1.6;
    color: var(--ch-text-muted);
}

.ch-qr-split-card li::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0.55em;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background-color: currentColor;
    opacity: 0.5;
}

.ch-qr-split-card--good h3 {
    color: var(--ch-qr-accent);
}

.ch-qr-split-card--good:hover {
    border-color: var(--ch-qr-accent-line);
}

.ch-qr-split-card--bad h3 {
    color: var(--ch-qr-stop-text);
}

.ch-qr-split-card--bad:hover {
    border-color: var(--ch-qr-stop-line);
}

/* Inline literals in the prose: short URLs, parameters, file names. Prism is
   not loaded on this page, so this is the only monospace rule the article
   body gets. */
.ch-qr-content :not(pre) > code {
    padding: 0.12em 0.4em;
    background-color: var(--ch-qr-well);
    border: 1px solid var(--ch-border);
    border-radius: 5px;
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.85em;
    word-break: break-word;
}

/* Scoped to .ch-qr-section rather than written as a :not() against the
   contents list: the jump links paint themselves, and a complex selector
   inside :not() drops the whole rule on older engines. */
.ch-qr-section a {
    color: var(--ch-qr-accent);
    text-underline-offset: 3px;
}

@media (max-width: 760px) {
    .ch-qr-figure {
        grid-template-columns: minmax(0, 1fr);
        gap: 1.1rem;
    }

    /* The flip only exists to break the rhythm on wide screens. Stacked, an
       image below its own caption is simply wrong. */
    .ch-qr-figure--flip .ch-qr-figure-frame {
        order: 0;
    }

    .ch-qr-toc ol {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ==========================================================================
   10. ACCOUNT GATE
   ========================================================================== */
.ch-qr-gate {
    --ch-qr-gate-surface: #FFFFFF;
    --ch-qr-gate-border: #E2E8F0;
    position: fixed;
    inset: 0;
    z-index: 10000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.25rem;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.28s ease;
}

.ch-qr-gate[hidden] {
    display: none;
}

.ch-qr-gate.is-active {
    opacity: 1;
    pointer-events: auto;
}

html.ch-qr-gate-open {
    overflow: hidden;
}

.ch-qr-gate-veil {
    position: absolute;
    inset: 0;
    background-color: rgba(15, 23, 42, 0.74);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
}

.ch-qr-gate-card {
    position: relative;
    overflow: hidden auto;
    width: min(540px, 100%);
    max-height: min(88vh, 780px);
    padding: 2rem 2rem 1.5rem;
    background-color: var(--ch-qr-gate-surface);
    border: 1px solid var(--ch-qr-gate-border);
    border-radius: 18px;
    box-shadow: 0 40px 80px -30px rgba(15, 23, 42, 0.55), 0 0 60px -20px rgba(16, 185, 129, 0.35);
    opacity: 0;
    transform: translateY(22px) scale(0.96);
    transition: opacity 0.3s ease, transform 0.55s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.ch-qr-gate.is-active .ch-qr-gate-card {
    opacity: 1;
    transform: none;
}

.ch-qr-gate-card:focus {
    outline: none;
}

.ch-qr-gate-card::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    background: linear-gradient(90deg, var(--ch-cta), #34D399 50%, transparent 100%);
}

.ch-qr-gate-x,
.ch-qr-gate-x:focus,
.ch-qr-gate-x:active {
    position: absolute;
    top: 0.9rem;
    right: 0.9rem;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border: 0;
    border-radius: 8px;
    background-color: transparent;
    color: var(--ch-text-muted);
    cursor: pointer;
    transition: background-color 0.18s ease, color 0.18s ease;
}

.ch-qr-gate-x:hover {
    background-color: #F1F5F9;
    color: var(--ch-text-main);
}

.ch-qr-gate-kicker {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--ch-cta);
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.ch-qr-gate-title {
    margin: 0.7rem 0 0.6rem;
    font-size: 1.5rem;
    line-height: 1.25;
    letter-spacing: -0.015em;
}

.ch-qr-gate-title span {
    color: var(--ch-cta);
}

.ch-qr-gate-desc {
    margin: 0 0 1.2rem;
    color: var(--ch-text-muted);
    font-size: 0.95rem;
    line-height: 1.65;
}

.ch-qr-gate-list {
    margin: 0 0 1.5rem;
    padding: 0;
    list-style: none;
    display: grid;
    gap: 0.8rem;
}

.ch-qr-gate-list li {
    display: flex;
    gap: 0.75rem;
    align-items: flex-start;
}

.ch-qr-gate-ico {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 8px;
    background-color: rgba(16, 185, 129, 0.12);
    color: #047857;
}

.ch-qr-gate-copy {
    font-size: 0.9rem;
    line-height: 1.55;
    color: var(--ch-text-muted);
}

.ch-qr-gate-copy strong {
    display: block;
    color: var(--ch-text-main);
    font-size: 0.94rem;
}

.ch-qr-gate-actions {
    display: grid;
    gap: 0.6rem;
}

.ch-qr-gate-cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.85rem 1.2rem;
    background-color: var(--ch-cta);
    border-radius: 10px;
    color: #FFFFFF;
    font-weight: 700;
    text-decoration: none;
    box-shadow: 0 10px 24px -12px rgba(16, 185, 129, 0.9);
    transition: transform 0.16s ease;
}

.ch-qr-gate-cta:hover {
    color: #FFFFFF;
    transform: translateY(-1px);
}

.ch-qr-gate-skip,
.ch-qr-gate-skip:hover,
.ch-qr-gate-skip:focus,
.ch-qr-gate-skip:active {
    padding: 0.6rem;
    border: 0;
    background-color: transparent;
    font-family: inherit;
    font-size: 0.88rem;
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
}

.ch-qr-gate-skip,
.ch-qr-gate-skip:focus,
.ch-qr-gate-skip:active {
    color: var(--ch-text-muted);
}

.ch-qr-gate-skip:hover {
    color: var(--ch-text-main);
}

.ch-qr-gate-fine {
    margin: 1.1rem 0 0;
    text-align: center;
    color: var(--ch-text-muted);
    font-size: 0.8rem;
}

/* ==========================================================================
   11. RESPONSIVE
   ========================================================================== */
/* Hero, tablet: the split collapses and the plate drops under the copy at a
   capped width, still centred with the panel. The spec strip halves. */
@media (max-width: 900px) {
    .ch-qrhero-inner {
        grid-template-columns: minmax(0, 1fr);
        gap: 1.75rem;
    }

    .ch-qrhero-plate {
        max-width: 300px;
        margin: 0 auto;
    }

    .ch-qrhero-sub {
        max-width: none;
    }

    .ch-qrhero-trust {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.65rem 1.25rem;
    }
}

/* Hero, phone: the plate is worth keeping - it is the only thing on the page
   that shows the output before the reader scrolls - but not at 320px tall in
   front of the generator. Below 782px it folds into a single row: an 88px
   matrix beside a stacked readout, roughly 110px of height for the whole
   thing. The plate head is dropped because a status pill on a thumbnail is
   chrome, not information. */
@media (max-width: 782px) {
    .ch-qrhero {
        padding: 1.5rem 1.25rem 1.6rem;
        border-radius: 18px;
    }

    /* Header collapses to a single row below the GP mobile breakpoint. */
    .ch-qr-app,
    .ch-qr-section {
        scroll-margin-top: 80px;
    }

    .ch-qrhero-trust {
        grid-template-columns: minmax(0, 1fr);
        gap: 0.5rem;
    }

    .ch-qrhero-plate {
        display: grid;
        grid-template-columns: 88px minmax(0, 1fr);
        align-items: center;
        gap: 0.9rem;
        max-width: none;
        padding: 0.75rem;
    }

    .ch-qrhero-plate-head {
        display: none;
    }

    /* Padding stays at 9%: the beam and the reticle are inset against that
       exact figure, so changing it here would desync all three. */
    .ch-qrhero-stage {
        border-radius: 11px;
    }

    .ch-qrhero-readout {
        grid-template-columns: minmax(0, 1fr);
        gap: 0.3rem;
        margin: 0;
    }

    .ch-qrhero-readout > div {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 0.75rem;
        padding: 0.28rem 0.55rem;
        text-align: left;
    }

    .ch-qrhero-readout dt {
        margin: 0;
    }
}

@media (max-width: 420px) {
    .ch-qrhero-actions {
        gap: 0.7rem;
    }

    .ch-qrhero-cta {
        width: 100%;
        justify-content: center;
    }

    .ch-qrhero-formats {
        width: 100%;
        text-align: center;
    }
}

@media (max-width: 1024px) {
    .ch-qr-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    /* The preview leads on narrow screens: it docks above the controls so the
       code stays visible while the fields below it are edited. */
    .ch-qr-preview-col {
        position: static;
        order: -1;
    }

    .ch-qr-preview {
        min-height: 220px;
    }
}

@media (max-width: 640px) {
    .ch-qr-tab span {
        font-size: 0.8rem;
    }

    .ch-qr-field-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    .ch-qr-card-body {
        padding: 0 1rem 1.1rem;
    }

    .ch-qr-card-head {
        padding: 1rem;
    }

    .ch-qr-dtabs {
        gap: 1rem;
    }

    .ch-qr-gate-card {
        padding: 1.6rem 1.25rem 1.25rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .ch-qrhero-cta,
    .ch-qrhero-cta svg,
    .ch-qr-gate,
    .ch-qr-gate-card,
    .ch-qr-dl,
    .ch-qr-swatch,
    .ch-qr-steps li,
    .ch-qr-uses li,
    .ch-qr-switch-ui,
    .ch-qr-switch-ui::after,
    .ch-qr-toc a,
    .ch-qr-callout,
    .ch-qr-split-card,
    .ch-qr-figure-frame,
    .ch-qr-figure-img {
        transition: none;
    }

    .ch-qr-app,
    .ch-qr-preview:not(.is-empty) canvas {
        animation: none;
    }

    /* The sweep and the scan line are pure ornament: removed outright rather
       than shortened, since both are motion across a photograph. */
    .ch-qr-figure:hover .ch-qr-figure-beam {
        animation: none;
        opacity: 0;
    }

    .ch-qr-figure:hover .ch-qr-figure-frame::after {
        transition: none;
        transform: translateX(-130%);
    }

    .ch-qr-figure:hover .ch-qr-figure-img {
        transform: none;
    }

    .ch-cp-card,
    .ch-cp-veil,
    .ch-cp-swatch,
    .ch-qr-color-btn {
        transition: none;
    }
}

/* ==========================================================================
   12. DARK THEME
   ========================================================================== */
html[data-theme="dark"] .ch-page-qr {
    --ch-qr-surface: #1E293B;
    --ch-qr-well: #0F172A;
    --ch-qr-tile: #172032;
    --ch-qr-tile-hover: #273449;

    /* Flipped: the thumbnails draw their bodies in Slate 200 on dark, so the
       punched-out panel has to be the tile, not white. */
    --ch-qr-void: #0F172A;

    --ch-qr-line-soft: #475569;
    --ch-qr-line-hard: #64748B;

    /* Emerald 500 holds AA on slate where Emerald 600 goes muddy */
    --ch-qr-accent: #34D399;
    --ch-qr-accent-bg: rgba(16, 185, 129, 0.13);
    --ch-qr-accent-bg-hi: rgba(16, 185, 129, 0.2);
    --ch-qr-accent-line: rgba(16, 185, 129, 0.5);

    --ch-qr-warn-bg: rgba(245, 158, 11, 0.13);
    --ch-qr-warn-line: rgba(245, 158, 11, 0.42);
    --ch-qr-warn-text: #FCD34D;
    --ch-qr-warn-ico: #FBBF24;
    --ch-qr-stop-bg: rgba(239, 68, 68, 0.13);
    --ch-qr-stop-line: rgba(239, 68, 68, 0.42);
    --ch-qr-stop-text: #FCA5A5;
    --ch-qr-stop-ico: #F87171;

    --ch-qr-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px -16px rgba(0, 0, 0, 0.7);
    --ch-qr-shadow-lift: 0 14px 26px -14px rgba(0, 0, 0, 0.6);
}

/* Hover borders lift toward Slate 600 on dark instead of Slate 300 */
html[data-theme="dark"] .ch-qr-card:hover,
html[data-theme="dark"] .ch-qr-field input:hover:not(:focus),
html[data-theme="dark"] .ch-qr-field select:hover:not(:focus),
html[data-theme="dark"] .ch-qr-field textarea:hover:not(:focus),
html[data-theme="dark"] .ch-qr-hex:hover:not(:focus),
html[data-theme="dark"] .ch-cp-hex:hover:not(:focus) {
    border-color: #475569;
}

html[data-theme="dark"] .ch-qr-tab:hover {
    background-color: rgba(51, 65, 85, 0.7);
}

/* Hero panel on dark. The ladder inverts: the panel becomes Slate 800, the
   plate steps DOWN to a darker tile and the stage darker still, so the matrix
   still reads as an inset screen rather than a flat patch. The module ink
   flips to Slate 200 - Slate 900 modules on a Slate 900 stage is an invisible
   hero, which is exactly what a naive dark override produces here. */
html[data-theme="dark"] .ch-qrhero {
    --ch-qh-panel: #1E293B;
    --ch-qh-panel-2: #172032;
    --ch-qh-plate: #172032;
    --ch-qh-stage: #0F172A;

    --ch-qh-line: #334155;

    --ch-qh-mod: #E2E8F0;

    --ch-qh-grid: rgba(148, 163, 184, 0.09);
    --ch-qh-glow: rgba(16, 185, 129, 0.22);

    --ch-qh-shadow: 0 30px 60px -40px rgba(0, 0, 0, 0.85);
    --ch-qh-plate-shadow: 0 18px 38px -24px rgba(0, 0, 0, 0.72);
}

/* The centre brand plate is the one surface that must stay lighter than what
   it sits on, in both themes: it is standing in for a logo punched into the
   matrix, and a logo the same colour as the code is not a logo. */
html[data-theme="dark"] .ch-qrhero-brand {
    background-color: #E2E8F0;
    box-shadow: 0 6px 16px -8px rgba(0, 0, 0, 0.75);
}

html[data-theme="dark"] .ch-qr-gate-x:hover {
    background-color: #273449;
}

html[data-theme="dark"] .ch-qr-switch:hover input:not(:checked) + .ch-qr-switch-ui {
    background-color: #64748B;
}

html[data-theme="dark"] .ch-qr-tabs {
    background-color: #0B1220;
}

html[data-theme="dark"] .ch-qr-tab[aria-selected="true"] {
    background-color: #1E293B;
    box-shadow: none;
}

html[data-theme="dark"] .ch-qr-field input::placeholder,
html[data-theme="dark"] .ch-qr-field textarea::placeholder {
    color: #64748B;
}

html[data-theme="dark"] .ch-qr-switch-ui {
    background-color: #475569;
}

html[data-theme="dark"] .ch-qr-step-num {
    background-color: rgba(16, 185, 129, 0.18);
    color: #6EE7B7;
}

html[data-theme="dark"] .ch-qr-gate-ico {
    background-color: rgba(16, 185, 129, 0.18);
    color: #6EE7B7;
}

html[data-theme="dark"] .ch-qr-status[data-kind="error"] {
    color: #FCA5A5;
}

html[data-theme="dark"] .ch-qr-status[data-kind="ok"] {
    color: #6EE7B7;
}

html[data-theme="dark"] .ch-qr-preview {
    background-image:
        linear-gradient(45deg, rgba(148, 163, 184, 0.09) 25%, transparent 25%),
        linear-gradient(-45deg, rgba(148, 163, 184, 0.09) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, rgba(148, 163, 184, 0.09) 75%),
        linear-gradient(-45deg, transparent 75%, rgba(148, 163, 184, 0.09) 75%);
}

html[data-theme="dark"] .ch-qr-gate {
    --ch-qr-gate-surface: #1E293B;
    --ch-qr-gate-border: #334155;
}

/* Editorial exhibits on slate. Photographs shot for a white page glare on a
   dark one, so the plate loses a little brightness at rest and the white
   sheen drops to roughly half strength - at full opacity it reads as a flash
   rather than a sweep. */
html[data-theme="dark"] .ch-qr-figure-img {
    filter: saturate(0.88) brightness(0.9);
}

html[data-theme="dark"] .ch-qr-figure:hover .ch-qr-figure-img {
    filter: saturate(1) brightness(1);
}

html[data-theme="dark"] .ch-qr-figure-frame::after {
    background: linear-gradient(105deg, transparent 42%, rgba(255, 255, 255, 0.12) 50%, transparent 58%);
}

/* The thumbnail bodies are drawn in currentColor. Slate 200 keeps them legible
   on the tile, and the punched-out panel follows --ch-qr-void, which flipped
   to Slate 900 above - a hardcoded white panel would erase the silhouette. */
html[data-theme="dark"] .ch-qr-swatch svg {
    color: #E2E8F0;
}

/* --- Colour picker on slate --- */
html[data-theme="dark"] .ch-cp-card {
    box-shadow: 0 24px 48px -18px rgba(0, 0, 0, 0.75), 0 6px 18px -8px rgba(0, 0, 0, 0.4);
}

html[data-theme="dark"] .ch-qr-color-chipset {
    box-shadow: inset 0 0 0 1px rgba(248, 250, 252, 0.14);
}

html[data-theme="dark"] .ch-cp-chip {
    box-shadow: inset 0 0 0 1px rgba(248, 250, 252, 0.1);
}

html[data-theme="dark"] .ch-cp-swatch {
    border-color: rgba(248, 250, 252, 0.16);
}

html[data-theme="dark"] .ch-cp-swatch:hover {
    box-shadow: 0 4px 10px -3px rgba(0, 0, 0, 0.6);
}

html[data-theme="dark"] .ch-cp-hue {
    border-color: rgba(248, 250, 252, 0.14);
}

/* ==========================================================================
   13. SAVED CONFIRMATION AND THE GATE VARIANTS
   Added with the saved-code library. Three surfaces:
     - the confirmation the generator owed the user after writing to their
       account, with the way back to it,
     - the sign-in link that sits under the guest CTA,
     - the slot dialog, which is an upgrade pitch rather than a signup pitch
       and therefore carries amber rather than emerald on its kicker.
   ========================================================================== */

.ch-qr-saved {
    display: flex;
    gap: 0.65rem;
    align-items: flex-start;
    margin-top: 0.85rem;
    padding: 0.85rem 0.95rem;
    border: 1px solid var(--ch-qr-accent-line);
    border-radius: 10px;
    background-color: var(--ch-qr-accent-bg);
}

.ch-qr-saved[hidden] {
    display: none;
}

.ch-qr-saved-ico {
    flex-shrink: 0;
    display: flex;
    color: var(--ch-qr-accent);
    margin-top: 0.05rem;
}

.ch-qr-saved-copy {
    display: grid;
    gap: 0.25rem;
    font-size: 0.85rem;
    line-height: 1.55;
    color: var(--ch-text-muted);
}

.ch-qr-saved-copy strong {
    color: var(--ch-text-main);
    font-size: 0.88rem;
}

.ch-qr-saved-copy a {
    color: var(--ch-qr-accent);
    font-weight: 700;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.ch-qr-saved-slots {
    font-family: "JetBrains Mono", "Roboto Mono", ui-monospace, monospace;
    font-size: 0.72rem;
    letter-spacing: 0.01em;
    opacity: 0.85;
}

.ch-qr-saved-slots[hidden] {
    display: none;
}

/* Amber kicker for the slot dialog: the emerald one reads as good news, and a
   full library is not good news. */
.ch-qr-gate-kicker.is-warn {
    color: var(--ch-qr-warn-ico);
}

.ch-qr-gate-alt {
    display: block;
    text-align: center;
    padding: 0.15rem;
    color: var(--ch-text-muted);
    font-size: 0.86rem;
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 3px;
}

.ch-qr-gate-alt:hover {
    color: var(--ch-qr-accent);
}

/* The skip button carries a second line now, because "download without
   registering" and "here is exactly what that file is" are two different
   promises and hiding the second one would be a bait. */
.ch-qr-gate-skip {
    display: grid;
    gap: 0.2rem;
    width: 100%;
    text-align: center;
}

.ch-qr-gate-skip[hidden] {
    display: none;
}

.ch-qr-gate-skip small {
    display: block;
    font-size: 0.76rem;
    font-weight: 500;
    line-height: 1.5;
    text-decoration: none;
    opacity: 0.85;
}

/* The slot dialog nests its own action block inside the mode body, so it needs
   the top margin the shared block gets from the list above it. */
[data-gate-mode="slots"] .ch-qr-gate-actions {
    margin-top: 0.25rem;
}

html[data-theme="dark"] .ch-qr-gate-alt:hover {
    color: var(--ch-qr-accent);
}

/* ==========================================================================
   14. TRACKING LOCKED NOTICE
   The in-place explanation for a tracking switch that refused to move. It sits
   directly under the switch, never in the status line beside the preview: on a
   phone those two are a full column apart, so a reason printed there is a
   reason nobody reads, and the control just looks broken.

   Two tones, both in the house alert grammar (soft wash, hairline at ~40%,
   solid text): "account" is emerald because signing up is a good outcome we
   are recommending, "quota" is amber because a full library is a constraint.
   Neither is red. Nothing here is an error - the user did nothing wrong.
   ========================================================================== */

/* Surface, not a wash. An amber or emerald tinted panel reads as a browser
   validation alert, which is wrong twice over: nothing here is an error, and a
   cream box is the least premium thing you can put next to an upgrade button.
   So the panel takes the tile colour the rest of the tool uses and carries its
   tone on a 3px edge instead - enough to identify the state, not enough to
   turn the whole block into a warning. */
.ch-qr-locked {
    position: relative;
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 0.85rem;
    margin-top: 0.9rem;
    padding: 1.05rem 1.1rem 1.05rem 1.25rem;
    border: 1px solid var(--ch-border);
    border-radius: 12px;
    background-color: var(--ch-qr-tile);
    box-shadow: var(--ch-qr-shadow);
    overflow: hidden;
    animation: ch-qr-locked-in 0.32s var(--ch-qr-ease) both;
}

.ch-qr-locked::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    background-color: var(--ch-cta);
}

/* A single wide, very low opacity emerald bloom off the left edge. It reads as
   depth rather than as a coloured panel, and it survives both themes because
   it is a translucent layer over whatever the tile happens to be. */
.ch-qr-locked::after {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 45%;
    pointer-events: none;
    background: linear-gradient(90deg, var(--ch-qr-accent-bg), transparent);
}

.ch-qr-locked > * {
    position: relative;
    z-index: 1;
}

.ch-qr-locked[hidden] {
    display: none;
}

@keyframes ch-qr-locked-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: none; }
}

.ch-qr-locked-ico {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: 10px;
    background-color: var(--ch-qr-surface);
    border: 1px solid var(--ch-qr-accent-line);
    color: var(--ch-qr-accent);
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
}

.ch-qr-locked-copy {
    display: grid;
    gap: 0.3rem;
    min-width: 0;
}

.ch-qr-locked-copy strong {
    color: var(--ch-text-main);
    font-size: 0.92rem;
    font-weight: 700;
    line-height: 1.4;
    letter-spacing: -0.01em;
}

.ch-qr-locked-copy > span:not(.ch-qr-locked-ico) {
    color: var(--ch-text-muted);
    font-size: 0.84rem;
    line-height: 1.6;
}

/* The CTA is a real button, not an underlined word. This is the single most
   important click on the page for a guest, and the previous version shipped
   without any link at all. */
.ch-qr-locked-cta,
.ch-qr-locked-cta:hover,
.ch-qr-locked-cta:focus {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    justify-self: start;
    margin-top: 0.45rem;
    padding: 0.65rem 1.05rem;
    border-radius: 9px;
    background-color: var(--ch-cta);
    color: #FFFFFF;
    font-size: 0.86rem;
    font-weight: 700;
    line-height: 1;
    text-decoration: none;
    box-shadow: 0 8px 18px -10px rgba(16, 185, 129, 0.85);
    transition: transform 0.16s var(--ch-qr-ease), filter 0.16s ease;
}

.ch-qr-locked-cta:hover {
    transform: translateY(-1px);
    filter: brightness(1.06);
}

.ch-qr-locked-cta svg {
    flex-shrink: 0;
    transition: transform 0.16s var(--ch-qr-ease);
}

.ch-qr-locked-cta:hover svg {
    transform: translateX(2px);
}

.ch-qr-locked-alt {
    justify-self: start;
    margin-top: 0.15rem;
    color: var(--ch-text-muted);
    font-size: 0.8rem;
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 3px;
}

.ch-qr-locked-alt[hidden] {
    display: none;
}

.ch-qr-locked-alt:hover {
    color: var(--ch-qr-accent);
}

/* --- quota tone ---
   Same panel, amber edge and amber bloom. The CTA stays emerald: it is still
   the action we want taken, and turning the button amber would read as a
   caution about clicking it. */
.ch-qr-locked[data-tone="quota"]::before {
    background-color: #F59E0B;
}

.ch-qr-locked[data-tone="quota"]::after {
    background: linear-gradient(90deg, var(--ch-qr-warn-bg), transparent);
}

.ch-qr-locked[data-tone="quota"] .ch-qr-locked-ico {
    border-color: var(--ch-qr-warn-line);
    color: var(--ch-qr-warn-ico);
}

/* On a phone the CTA takes the full width. A 44px target that spans the card
   is the difference between a tap and a mis-tap on the switch above it. */
@media (max-width: 560px) {
    .ch-qr-locked {
        grid-template-columns: minmax(0, 1fr);
        gap: 0.6rem;
    }

    .ch-qr-locked-ico {
        width: 30px;
        height: 30px;
    }

    .ch-qr-locked-cta,
    .ch-qr-locked-cta:hover,
    .ch-qr-locked-cta:focus {
        justify-self: stretch;
        width: 100%;
        padding: 0.8rem 1rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .ch-qr-locked {
        animation: none;
    }

    .ch-qr-locked-cta,
    .ch-qr-locked-cta svg {
        transition: none;
    }
}

/* --- dark ---
   The tile variable already flips, so the panel needs no colour override. What
   does need one is the icon chip: on slate it has to step down from the tile,
   not up, or it reads as a hole punched in the panel. */
html[data-theme="dark"] .ch-qr-locked {
    background-color: #172032;
    border-color: #334155;
}

html[data-theme="dark"] .ch-qr-locked-ico {
    background-color: rgba(2, 6, 23, 0.55);
    box-shadow: none;
}

html[data-theme="dark"] .ch-qr-locked-cta {
    color: #052E23;
    box-shadow: 0 8px 18px -10px rgba(16, 185, 129, 0.55);
}

/* Option rows and the range readout on slate */
html[data-theme="dark"] .ch-qr-check-box {
    background-color: rgba(2, 6, 23, 0.5);
}

html[data-theme="dark"] .ch-qr-check input:checked ~ .ch-qr-check-box::after {
    border-color: #052E23;
}

html[data-theme="dark"] .ch-qr-range-value {
    background-color: rgba(2, 6, 23, 0.5);
}
