/* =========================================================================
   PayMQR — FREE PUBLIC TOOLS STYLESHEET
   =========================================================================
   Everything for `/tools` and the tool pages, and nothing else. It is
   linked by PayMQR\Controllers\Public\ToolsController, so it reaches
   the tool pages and no other page on the site.

   WHY IT IS NOT IN site.css ANY MORE
   ----------------------------------
   site.css is 215KB and is served on every page of the site, and only
   these ~1100 lines of it belong to the tools. Every tools release
   therefore re-uploaded the whole file over a transport that has
   silently corrupted it once (2400-char base64 chunks; 900 is the size
   that works). The family is about to grow from three members to ten.
   Splitting it costs one careful move now, removes that upload from
   every release after it, and takes 215KB off the marketing pages.

   WHAT STAYED BEHIND, DELIBERATELY
   --------------------------------
   The design system itself: tokens, elevation, `.si-btn`, `.si-field`,
   `.si-label`, `.si-input`, `.si-fielderror`, `.si-prose`, `.si-ticks`,
   the header, the footer. The tools EXTEND that vocabulary and must not
   fork it. Compound rules like `.si-qrgen .si-btn` travel with the
   tools because they are scoped to a tools ancestor; the base `.si-btn`
   does not.

   ONE SELECTOR WAS LEFT BEHIND ON PURPOSE: `.si-fielderror[hidden]`.
   `[hidden] { display: none }` lives in the user-agent stylesheet and
   any author `display` rule beats it, so `.si-fielderror { display:
   flex }` un-hides every hidden error message unless something puts it
   back. That reset was written here, inside the tools section, and the
   LOGIN, RESET and INVITATION pages depend on it. It now sits next to
   the rule it corrects, in site.css, where it is used.

   THE PRINT BLOCK AT THE END IS A SCOPE FIX, NOT A MOVE. It hides the
   header, the footer and every `.si-section:not(.si-qrsec)` so that
   printing a tool page prints the poster alone. In site.css it applied
   to EVERY page, so printing /pricing hid its content too. Here it
   reaches only the pages it was written for.

   The cascade is unchanged for the tools: this file loads after
   site.css, nothing later in site.css targets a tools class, and every
   computed style on all four tool pages was captured before and after
   the move and compared property by property.
   ========================================================================= */

/* =========================================================================
   21. FREE PUBLIC TOOLS  (/tools and every tool page)
   =========================================================================
   The tools family is the same design system as the marketing site — the
   same tokens, the same elevation, the same 9px controls, the same form
   primitives the auth pages introduced (`.si-field`, `.si-label`,
   `.si-input`, `.si-fielderror`, reused here unchanged).

   WHAT IS DIFFERENT IS THE WEIGHT. A marketing page leads with a claim
   and supports it with a visual. A tool page leads with the tool: the
   heading is one line, there is no hero, no illustration and no band of
   colour above the fold, and the first thing below the H1 is a form the
   visitor can type into. Everything here is in service of that — the
   chrome is deliberately quiet so the product UI is the only thing on the
   page with any visual weight.

   Mobile-first like §19: the base rules describe a phone, and the two
   breakpoints below add columns.
   ========================================================================= */

/* --- `hidden` HAS TO OUT-RANK A LAYOUT RULE ------------------------------ */
/*
   The browser's own `[hidden] { display: none }` lives in the USER-AGENT
   stylesheet, and any author rule beats it however specific — so
   `.si-fielderror { display: flex }` un-hides every hidden error message
   on this page, and `.si-qrpanel__meta { display: flex }` prints a column
   of empty labels under an empty QR plate. It did exactly that on the
   first render.

   The tool toggles visibility with `el.hidden`, which is the property a
   screen reader reads, so the fix belongs here rather than in the
   JavaScript: these elements are hidden when the attribute says so, and
   the layout rule applies only when it does not.
*/
.si-qrpanel__meta[hidden],
.si-qrpanel__row[hidden],
.si-qrpanel__empty[hidden],
.si-qrdest[hidden],
.si-qractions .si-btn[hidden] { display: none; }

/* --- The breadcrumb and switcher bar ------------------------------------ */
/* One bar, two jobs, and on a phone they stack. It sits ABOVE the H1 and
   is set at the smallest type on the site: it is orientation, not
   content, and the moment it competes with the heading it has failed. */
.si-toolbar {
    max-width: var(--si-max);
    margin: 0 auto;
    padding: var(--si-s4) var(--si-gutter) 0;
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--si-s3) var(--si-s4);
}

.si-crumbs {
    display: flex; flex-wrap: wrap; align-items: center; gap: .35rem;
    list-style: none; font-size: .75rem; color: var(--si-subtle);
}
.si-crumbs a { color: var(--si-subtle); text-decoration: none; }
.si-crumbs a:hover { color: var(--si-navy); text-decoration: underline; }
/* The separator is generated, so it is never read aloud and never
   selected when the trail is copied. */
.si-crumbs li + li::before { content: "/"; margin-right: .35rem; color: var(--si-border-strong); }
.si-crumbs [aria-current="page"] { color: var(--si-navy-3); font-weight: 600; }

/* THE SWITCHER IS A ROW OF LINKS, NOT A MENU.
   With one tool live it is one chip and a link to the hub; at six it
   scrolls sideways on a phone rather than becoming a disclosure. A menu
   would cost a click on every page of a family whose whole point is that
   moving between tools is instant. */
/* Left-aligned under the breadcrumb on a phone, where the bar stacks and
   a right-aligned row would sit under nothing. It only moves to the right
   once the two share a line (see the 721px block below). */
.si-switch {
    display: flex; align-items: center; gap: .35rem;
    max-width: 100%;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}
.si-switch::-webkit-scrollbar { display: none; }
.si-switch__item {
    display: inline-flex; align-items: center; gap: .35rem; white-space: nowrap;
    padding: .3rem .6rem; border-radius: 7px;
    font-size: .75rem; font-weight: 600; color: var(--si-muted); text-decoration: none;
    transition: background-color .18s ease, color .18s ease, box-shadow .18s ease;
}
.si-switch__item svg { width: 14px; height: 14px; }
.si-switch__item:hover { color: var(--si-navy); background: rgba(var(--shadow-rgb), .045); }
/* The current tool is a filled chip. It is still a link — a visitor who
   clicks it reloads the tool they are on, which is harmless — but
   `aria-current` is what actually announces it. */
.si-switch__item.is-current {
    color: var(--si-navy); background: #fff;
    box-shadow: var(--si-ring-hair), 0 1px 2px rgba(var(--shadow-rgb), .06);
}
.si-switch__all {
    white-space: nowrap; padding: .3rem .5rem;
    font-size: .75rem; font-weight: 650; color: var(--si-action-ink); text-decoration: none;
}
.si-switch__all:hover { text-decoration: underline; }

/* --- The tool heading ---------------------------------------------------- */
/* ONE LINE AND ONE SENTENCE. No eyebrow, no badge, no pair of buttons: a
   visitor who searched for a UPI QR generator has already decided, and
   everything between them and the first field is friction. */
.si-toolhead { max-width: 46rem; margin-bottom: var(--si-s5); }
.si-toolhead .si-h2 { font-size: clamp(1.4375rem, 1.05rem + 1.5vw, 1.875rem); }
.si-toolhead .si-lede { margin-top: var(--si-s2); }

/* The tool section's own top padding is smaller than a marketing
   section's: the breadcrumb bar above it already supplies air, and the
   generator has to start inside the first viewport on a phone. */
.si-qrsec { padding-top: var(--si-s4); }

/* --- The generator ------------------------------------------------------- */
/* STACKED, AND BOUNDED WHILE IT IS.
   A phone is narrower than this and never sees the cap. A tablet is not:
   at 768px an uncapped stack gives 728px-wide text inputs, which is the
   look of a form that was never laid out for that width. The cap is
   removed at 961px, where the two columns take over. */
.si-qrgen {
    display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--si-s5);
    max-width: 34rem; margin-inline: auto;
}
.si-qrgen__form { display: grid; gap: var(--si-s4); align-content: start; min-width: 0; }

/* "Optional" IS A WORD, NOT A COLOUR. Marking the optional fields rather
   than the required one is the right way round here: four of the five are
   optional, so the mark that carries information is the one on them. */
.si-optional {
    margin-left: .35rem; font-size: .625rem; font-weight: 600;
    letter-spacing: .04em; text-transform: uppercase; color: var(--si-subtle);
}

/* The rupee sign is drawn INSIDE the control and is `aria-hidden`: the
   field is already labelled "Amount", and a screen reader announcing
   "rupees amount edit" adds nothing. The input carries the border so the
   prefix and the number read as one control. */
.si-inputwrap { position: relative; display: block; }
.si-inputwrap__prefix {
    position: absolute; left: .875rem; top: 50%; transform: translateY(-50%);
    font-size: .875rem; color: var(--si-subtle); pointer-events: none;
}
.si-input--prefixed { padding-left: 2rem; }

/* The folded reference field. `<details>` — keyboard operable, announced,
   and no script. */
.si-more > summary {
    display: inline-flex; align-items: center; gap: .35rem;
    cursor: pointer; list-style: none;
    font-size: .8125rem; font-weight: 650; color: var(--si-action-ink);
}
.si-more > summary::-webkit-details-marker { display: none; }
.si-more > summary::before { content: "+"; font-weight: 700; }
.si-more[open] > summary::before { content: "−"; }
.si-more > summary:hover { text-decoration: underline; }

/* --- The result column --------------------------------------------------- */
.si-qrgen__result { display: grid; gap: var(--si-s3); align-content: start; }

/* THE MODE SWITCH IS TWO WORDS IN A TRACK, not a dropdown.
   There are two outputs and a merchant should be able to see both in one
   glance and one click. A <select> would hide the poster behind a menu on
   the page whose whole point is the poster. */
.si-qrmodes {
    display: inline-flex; align-self: start; gap: .2rem; padding: .2rem;
    border-radius: 10px; background: var(--si-bg-alt);
    box-shadow: inset 0 0 0 1px rgba(var(--shadow-rgb), .06);
}
.si-qrmode {
    appearance: none; border: 0; cursor: pointer;
    padding: .38rem .8rem; border-radius: 8px;
    font: inherit; font-size: .78125rem; font-weight: 650; color: var(--si-muted);
    background: transparent;
    transition: background-color .18s ease, color .18s ease, box-shadow .18s ease;
}
.si-qrmode:hover { color: var(--si-navy); }
.si-qrmode.is-on {
    color: var(--si-navy); background: #fff;
    box-shadow: var(--si-ring-hair), 0 1px 2px rgba(var(--shadow-rgb), .07);
}

/*
   THE OUTPUT BOX IS A CONTAINER, and that is what makes the preview an
   exact scale model of the download.

   The poster is designed on a 680-unit canvas (public/assets/js/
   qr-poster.js). Here `1em` is set to ten of those units through
   `cqw`, so every size below is the design's own number divided by ten
   — 34-unit type is 3.4em, a 444-unit plate is 44.4em. The preview and
   the PNG cannot drift apart by a rounding, because they are the same
   arithmetic.

   The bare `font-size` before it is the fallback for an engine without
   container queries: it lands within a pixel of the computed value at
   the desktop column width.
*/
.si-qrout {
    container-type: inline-size;
    position: relative; width: 100%;
    display: flex; align-items: center; justify-content: center;
    min-height: 20rem;
    border-radius: var(--si-radius);
    background: var(--si-bg-alt);
    box-shadow: inset 0 0 0 1px rgba(var(--shadow-rgb), .06);
    transition: background-color .18s ease, box-shadow .18s ease;
}
/* Once there is a poster the box is just its ground: the poster brings
   its own card, and a second frame around it reads as a picture of a
   picture. */
/*
   `[hidden]` LIVES IN THE USER-AGENT SHEET AND LOSES TO ANY AUTHOR
   `display`. The rule above sets `display: flex`, so an output box the
   script had hidden stayed on screen -- an empty readback under an
   empty link panel, on the reader and on the payment link tool. This
   is the third time this family has hit it; the fix belongs beside
   every `display` on an element the page toggles.
*/
.si-qrout[hidden] { display: none; }

.si-qrout:has(.si-qrposter:not([hidden])),
.si-qrout:has(.si-urlcard:not([hidden])),
.si-qrout:has(.si-qrplain:not([hidden])) { background: transparent; box-shadow: none; min-height: 0; }

.si-qrpanel__empty {
    display: flex; flex-direction: column; align-items: center; gap: .6rem;
    padding: var(--si-s6) var(--si-s4); text-align: center;
    color: var(--si-muted); font-size: .8125rem; line-height: 1.55;
}
.si-qrpanel__empty svg { width: 26px; height: 26px; color: var(--si-border-strong); }

/* --- The poster ---------------------------------------------------------- */
/*
   `si-qrposter`, NOT `si-poster`.

   `.si-poster` was already taken — by the landing page's merchant-poster
   MOCK (§16), which is `position: relative; overflow: hidden` with an
   absolutely-positioned `.si-poster__plate`. Reusing the name inherited
   all of that: the QR plate left the flow, the lines after it drew on top
   of the symbol, and the card collapsed to a third of its height. It
   looked like a broken layout rather than a name collision, which is what
   makes this worth a comment.

   Everything below is a scale model of the download — see the note on
   `.si-qrout`.
*/
/*
   LENGTHS COME FROM `--u`, NOT FROM `em`. MEASURED, NOT PREFERRED.

   `font-size` in `em` is correct here: it resolves against the poster's
   own root, so 3.4em IS 34 design units. A MARGIN does not: it resolves
   against the element's OWN font-size, so `margin-top: .8em` on a 3.4em
   headline is 27.2 units, not 8. Every such number on this card was
   wrong in the same direction, and the preview stood 10.4% taller than
   the PNG it claims to be a scale model of -- measured by recording the
   canvas's own baselines and comparing them with the rendered box.

   So a length is `calc(var(--u) * N)` where N is the canvas number
   itself. One scale, one number, no division by ten to get wrong.
*/
.si-qrposter {
    font-size: 6.3px;
    font-size: calc(100cqw / 68);
    --u: .63px;
    --u: calc(100cqw / 680);
    width: 100%;
    padding: calc(var(--u) * 20);
    text-align: center;
    background: var(--si-surface);
    border-radius: 2.8em;
    box-shadow: var(--si-shadow);
    color: var(--si-navy);
}
.si-qrposter__label {
    font-size: 1.3em; font-weight: 700; letter-spacing: .14em;
    text-transform: uppercase; color: var(--si-subtle);
}
.si-qrposter__payee {
    margin-top: calc(var(--u) * 8); font-size: 3.4em; font-weight: 700;
    letter-spacing: -.02em; line-height: 1.24; overflow-wrap: anywhere;
}
.si-qrposter [data-upiqr-row="amount"] { margin-top: 2.2em; }
.si-qrposter__amount {
    margin-top: calc(var(--u) * 4); font-size: 5.4em; font-weight: 800; letter-spacing: -.03em; line-height: 1;
}
.si-qrposter__open { margin-top: calc(var(--u) * 22); font-size: 2.4em; font-weight: 600; }

/* The white plate is not decoration: the quiet zone is part of the QR
   specification, and a symbol drawn straight onto a tinted card
   intermittently fails to scan. */
.si-qrposter__plate {
    width: 43.6em; max-width: 100%; margin: 2.8em auto 0; padding: 1.8em;
    border-radius: 2.2em; background: #fff;
    box-shadow: 0 0 0 .15em var(--si-border);
}
.si-qrposter__plate svg { display: block; width: 100%; height: auto; }
.si-qrposter__scan { margin-top: calc(var(--u) * 26); font-size: 1.9em; font-weight: 600; }

/* The acceptance row on its own quiet well: four marks floating on a card
   read as four logos somebody pasted on; the same four inside a tinted,
   hairlined well read as a compatibility strip, which is what they are. */
.si-qrposter__apps {
    display: flex; align-items: center; justify-content: center; gap: 2.8em;
    margin: 1.8em auto 0; padding: 0 2em; height: 6.2em;
    border-radius: 1.6em; background: var(--si-bg-alt);
    box-shadow: inset 0 0 0 .1em var(--si-border);
}
/* Width and height come from PayMQR\Qr\QrPosterMarks::htmlRow(), so the
   box is reserved before the SVG arrives and the row cannot shift. */
.si-qrposter__app { display: block; height: 3em; width: auto; }

/* The detail block carries the margin the address used to, so the label
   and the address move together. Both numbers have twins in qr-poster.js
   at ten times the size — see the note on `.si-qrout`. */
.si-qrposter__detail { margin-top: 2.4em; }
.si-qrposter__upilabel {
    font-size: 1.1em; font-weight: 700; letter-spacing: .14em;
    text-transform: uppercase; color: var(--si-subtle);
}
.si-qrposter__upi { margin-top: calc(var(--u) * 5); font-size: 1.5em; color: var(--si-subtle); overflow-wrap: anywhere; }
.si-qrposter__note { margin-top: calc(var(--u) * 9); font-size: 1.4em; color: var(--si-subtle); overflow-wrap: anywhere; }
.si-qrposter__brand {
    margin-top: calc(var(--u) * 22); padding-top: calc(var(--u) * 22);
    border-top: calc(var(--u) * 1) solid var(--si-border);
    font-size: 1.3em; font-weight: 600; letter-spacing: .06em; color: var(--si-subtle);
}

/* --- The link card ------------------------------------------------------- */
/*
   THE URL TOOL'S CARD — a sibling of the payment poster above, not a mode
   of it.

   It shares the design language deliberately (same 680-unit canvas, same
   white plate, same hairline over the attribution) and shares none of the
   payment vocabulary: no payee, no amount, no acceptance row. A link is
   opened by a camera, and a row of payment logos under a menu link would
   be a plain lie.

   EVERY NUMBER BELOW HAS A TWIN IN qr-card.js AT TEN TIMES THE SIZE.
   `.si-qrout` is a `container-type: inline-size` and this `font-size` is
   `100cqw / 68`, so `1em` is ten design units and the preview is an exact
   scale model of the 2040px download. Change one, change both.
*/
.si-urlcard {
    font-size: 6.3px;
    font-size: calc(100cqw / 68);
    /* See the note above `.si-qrposter`: a margin in `em` resolves
       against the element's own size, and every one on this card was
       wrong by its own font-size factor. */
    --u: .63px;
    --u: calc(100cqw / 680);
    width: 100%;
    padding: calc(var(--u) * 20);
    text-align: center;
    background: var(--si-surface);
    border-radius: 2.8em;
    box-shadow: var(--si-shadow);
    color: var(--si-navy);
}
.si-urlcard__eyebrow {
    font-size: 1.3em; font-weight: 700; letter-spacing: .14em;
    text-transform: uppercase; color: var(--si-subtle);
}
/* WITH NO LABEL THE CARD CLOSES UP. The row is `hidden`, not emptied, so
   the margin below goes with it — an empty band where a heading would be
   reads as a broken render rather than as a plain card. */
.si-urlcard__title {
    margin-top: calc(var(--u) * 10); font-size: 3.4em; font-weight: 700;
    letter-spacing: -.02em; line-height: 1.24; overflow-wrap: anywhere;
}
/* The white plate is not decoration: the quiet zone is part of the QR
   specification, and a symbol drawn straight onto a tinted card
   intermittently fails to scan. */
.si-urlcard__plate {
    width: 43.6em; max-width: 100%; margin: 2.4em auto 0; padding: 1.8em;
    border-radius: 2.2em; background: #fff;
    box-shadow: 0 0 0 .15em var(--si-border);
}
.si-urlcard__plate svg { display: block; width: 100%; height: auto; }
/* THE DESTINATION IS THE SECOND-LARGEST THING ON THE CARD, under the
   symbol it belongs to. Somebody deciding whether to scan reads the host;
   a card that hides it is asking them to trust an anonymous square. */
.si-urlcard__dest {
    margin-top: calc(var(--u) * 22); font-size: 2.2em; font-weight: 600;
    letter-spacing: -.01em; line-height: 1.3; overflow-wrap: anywhere;
}
.si-urlcard__scan { margin-top: calc(var(--u) * 11); font-size: 1.5em; color: var(--si-subtle); }
.si-urlcard__brand {
    margin-top: calc(var(--u) * 22); padding-top: calc(var(--u) * 22);
    border-top: calc(var(--u) * 1) solid var(--si-border);
    font-size: 1.3em; font-weight: 600; letter-spacing: .06em; color: var(--si-subtle);
}

/* --- What the code actually contains ------------------------------------- */
/*
   The card shows the address as a person reads it — no scheme, no `www.`,
   a long query string trimmed. This is the unshortened original, so
   nobody has to trust that shortening or scan their own code to find out
   what is in it. It is a panel rather than a caption because a 300-
   character URL is a paragraph, and it must be able to wrap without
   pushing the card around.
*/
.si-qrdest {
    display: grid; gap: .25rem;
    padding: var(--si-s3) var(--si-s4);
    border-radius: 12px;
    background: var(--si-bg-alt);
    box-shadow: inset 0 0 0 1px rgba(var(--shadow-rgb), .06);
}
.si-qrdest__label {
    font-size: .6875rem; font-weight: 700; letter-spacing: .05em;
    text-transform: uppercase; color: var(--si-muted);
}
/* `--si-muted`, not `--si-subtle`: this sits on the tinted ground, where
   `--si-subtle` measures 4.35:1 and misses AA for small text. */
.si-qrdest__value { font-size: .8125rem; line-height: 1.55; color: var(--si-navy); overflow-wrap: anywhere; }


/* The raw exports, folded away. A designer's need, not the visitor's
   first decision — see the note in the view. */
.si-more--raw > summary { font-size: .8125rem; }


/* --- The bare symbol ----------------------------------------------------- */
.si-qrplain {
    width: 100%;
    background: var(--si-surface); border-radius: var(--si-radius);
    padding: var(--si-s5); box-shadow: var(--si-shadow);
}
.si-qrpanel__code {
    width: 100%; max-width: 19rem; margin: 0 auto;
    aspect-ratio: 1 / 1;
    display: flex; align-items: center; justify-content: center;
    border-radius: 12px; background: #fff;
}
.si-qrpanel__code svg { display: block; width: 100%; height: auto; border-radius: 8px; }

/* WHAT IS IN THE CODE, IN WORDS. A merchant cannot proof-read a QR
   symbol; they can proof-read five lines of text. */
.si-qrpanel__meta {
    margin-top: var(--si-s4); padding-top: var(--si-s4);
    border-top: 1px solid var(--si-border);
    display: flex; flex-direction: column; gap: .55rem;
}
.si-qrpanel__row { display: grid; grid-template-columns: 5.5rem minmax(0, 1fr); gap: var(--si-s3); align-items: baseline; }
.si-qrpanel__row dt { font-size: .6875rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--si-subtle); }
.si-qrpanel__row dd { font-size: .875rem; color: var(--si-navy); overflow-wrap: anywhere; }
.si-qrpanel__row dd.si-mono { font-size: .8125rem; }

/* --- Status, actions and the privacy line -------------------------------- */
/* The live region is a real line of text rather than a toast: a toast that
   has faded is a message a slow reader never received. */
.si-qrstatus { min-height: 1.15rem; font-size: .75rem; color: var(--si-subtle); }
.si-qrstatus:empty { min-height: 0; }

/* ONE PRIMARY ACTION, THEN THE REST. Six equal buttons is a toolbar, and a
   toolbar makes the merchant choose before they know what the choices
   mean. */
.si-qractions { display: grid; gap: var(--si-s2); }
.si-qractions__primary { width: 100%; }
.si-qractions__primary svg { width: 16px; height: 16px; }
.si-qractions__more { display: flex; flex-wrap: wrap; gap: var(--si-s2); }
.si-qractions__more .si-btn { flex: 1 1 auto; min-height: 2.375rem; font-size: .8125rem; padding-inline: .8rem; }
/*
   FOUR SHORT LABELS COLLAPSE INTO ONE CRAMPED ROW, AND THIS STOPS THEM.

   The base rule for a secondary row is `flex: 1 1 auto`, which was
   written for the payment tool's six buttons and its longer labels ("QR
   only (PNG)", "Copy UPI link") — those wrap on a phone and each one
   ends up wide. This tool has four short ones, and at 390px they all
   fitted on a single line: 71px targets separated by 8px of gap, which
   is exactly the cramped multi-button row a thumb cannot use.

   A flex-basis wide enough for two per row is what fixes it, rather
   than a column count: two fit at 390px and grow to 171px each, three
   fit once there is room, and nothing is left holding an empty cell the
   way a fixed two-column grid does when Share is present on a phone and
   absent on a desktop.
*/
.si-qractions__more--pairs .si-btn { flex: 1 1 8rem; min-height: 2.75rem; }
/* A disabled action is dimmed AND announced as disabled. Dimmed rather
   than hidden, so the row does not change shape when it fills in. */
.si-qractions .si-btn:disabled { opacity: .38; cursor: not-allowed; }
.si-qractions .si-btn:disabled:active { transform: none; }

.si-qrprivacy {
    display: flex; align-items: flex-start; gap: .5rem;
    font-size: .75rem; line-height: 1.55; color: var(--si-muted);
}
.si-qrprivacy svg { flex: 0 0 auto; width: 15px; height: 15px; margin-top: .1rem; color: var(--si-emerald); }

/* --- Import an existing QR ----------------------------------------------- */
/*
   It sits above the first field because the merchant it is for does not
   know their UPI ID — which is why they are uploading a code.

   ON A PHONE IT IS AN ACTION, NOT A CARD.

   The tinted panel, its ring and its 16px of waist are what made this
   read as a feature card — the largest object above the fold on the page
   whose first job is a form. A phone has no room to spend 32px of
   padding introducing one button. So the mobile treatment is the button
   itself with a quiet line under it, and the card comes back at 721px
   where the column is wide enough for it to read as composition rather
   than as bulk.

   NOTHING ABOUT THE CONTROL CHANGES: same label, same input, same focus
   ring, same copy.
*/
.si-import {
    display: grid; gap: .4rem; justify-items: start;
    padding: 0; border-radius: 0; background: none; box-shadow: none;
}
.si-import__btn {
    display: inline-flex; align-items: center; gap: .45rem; cursor: pointer;
    min-height: 2.375rem; padding: .45rem .9rem; border-radius: var(--si-radius-sm);
    background: #fff; color: var(--si-navy);
    font-size: .8125rem; font-weight: 650;
    box-shadow: 0 0 0 1px rgba(var(--shadow-rgb), .13), 0 1px 2px rgba(var(--shadow-rgb), .05);
    transition: box-shadow .18s ease;
}
.si-import__btn svg { width: 15px; height: 15px; color: var(--si-action-ink); }
.si-import__btn:hover { box-shadow: 0 0 0 1px rgba(var(--shadow-rgb), .24), 0 1px 3px rgba(var(--shadow-rgb), .08); }
/* The input keeps focus and keyboard operation (it is `.si-sr`, clipped
   rather than removed); the label carries the ring so the control still
   looks focused. */
.si-import input:focus-visible + .si-import__btn {
    box-shadow: 0 0 0 1px var(--si-blue), 0 0 0 3px rgba(var(--brand-rgb), .28);
}
/* `--si-muted`, NOT `--si-subtle`. The hint sits on the import card's
   tinted ground rather than on the page, and `--si-subtle` against that
   tint measures 4.35:1 — under the 4.5 a 12px line needs. It is the same
   trap the QR placeholder fell into. */
.si-import__hint { font-size: .6875rem; line-height: 1.45; color: var(--si-muted); max-width: 46ch; }
.si-import__msg { font-size: .75rem; line-height: 1.5; font-weight: 600; }
.si-import__msg.is-good { color: var(--state-ok-ink); }
.si-import__msg.is-bad { color: var(--si-danger); }
/*
   THE HAND-OFF: the offer that follows a REFUSED import.

   Drop a UPI QR code into the URL tool and it will not make a card from
   it — a `upi://` payload behind a web-address card is a scam shape —
   so it offers the tool that does handle one. It sits directly under
   `__msg` and reads as the next sentence of it, which is why it takes
   the same size and weight rather than the hint's smaller one.

   It carried NO rule at all until the family closeout, when a guard
   collecting every `si-` class the templates write found it styling
   nothing. Renaming it to an existing class was the wrong repair: the
   element has a role of its own, and a suite asserts the server writes
   this exact hand-off into the template.
*/
.si-import__handoff { font-size: .75rem; line-height: 1.5; font-weight: 600; }
.si-import__handoff a { color: var(--si-action-ink); }

/* --- The tools hub ------------------------------------------------------- */
/* The hub's column. See the note in views/public/tools/hub.php. */
.si-toolhub { max-width: 46rem; }

/* AUTO-FIT, SO THE COUNT DECIDES THE SHAPE. One tool fills the column as
   a wide card; two sit side by side; four wrap to two rows. Nothing here
   names a number of tools, so adding the second one changes no CSS. */
.si-toolcards {
    display: grid; gap: var(--si-s4); align-items: stretch;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}
/* ICON BESIDE THE CONTENT, not above it: a 46rem-wide card with the icon
   stacked on top leaves a band of empty white under a two-line summary,
   which is the "oversized empty area" a one-tool hub falls into first. */
.si-toolcard {
    display: grid; grid-template-columns: auto minmax(0, 1fr);
    gap: var(--si-s4); align-items: start;
    background: var(--si-surface); border-radius: var(--si-radius);
    padding: var(--si-s5); text-decoration: none;
    box-shadow: var(--si-shadow-sm);
    transition: box-shadow .18s ease;
}
.si-toolcard:hover { box-shadow: var(--si-shadow); }
.si-toolcard .si-card__icon { margin-bottom: 0; }
.si-toolcard__body { display: flex; flex-direction: column; align-items: flex-start; height: 100%; }
.si-toolcard__title { font-size: 1rem; font-weight: 700; color: var(--si-navy); letter-spacing: -.012em; }
.si-toolcard__text { margin-top: var(--si-s2); font-size: .875rem; line-height: 1.55; color: var(--si-muted); }
/* The card is one link, so the affordance at the foot is a word, not a
   nested control: a button inside an anchor is invalid markup and a
   second tab stop for the same destination. */
.si-toolcard__go { margin-top: auto; padding-top: var(--si-s4); font-size: .8125rem; font-weight: 650; color: var(--si-action-ink); }
.si-toolcard__go::after { content: "→"; display: inline-block; margin-left: .3rem; transition: transform .18s ease; }
.si-toolcard:hover .si-toolcard__go::after { transform: translateX(2px); }

/* The roadmap: one heading and a line of names. Names of things that do
   not exist deserve exactly this much page. */
.si-toolsoon { margin-top: var(--si-s6); padding-top: var(--si-s5); border-top: 1px solid var(--si-border); }
.si-toolsoon p { margin-top: var(--si-s2); font-size: .875rem; line-height: 1.7; color: var(--si-subtle); }

.si-toolfoot { max-width: 46rem; }
.si-toolfoot .si-lede { margin-top: var(--si-s2); }
.si-toolfoot .si-btn + .si-btn { margin-left: var(--si-s2); }

/* --- The explanatory article -------------------------------------------- */
/* `.si-prose` already sets the type; this only adjusts what a page with a
   code sample and a list of failure modes needs. */
.si-qrarticle .si-code { margin: var(--si-s4) 0; font-size: .6875rem; }
.si-qrarticle .si-prose ul { margin-top: var(--si-s3); padding-left: 1.1rem; display: flex; flex-direction: column; gap: .6rem; }
.si-qrarticle .si-prose li { line-height: 1.6; }
.si-qrarticle .si-prose strong { color: var(--si-navy); font-weight: 650; }
.si-qrbridge .si-ticks { margin-top: 0; }

/* --- >= 721px ------------------------------------------------------------ */
@media (min-width: 721px) {
    .si-switch { margin-left: auto; }

    /* The card, once there is room for it to be composition. */
    .si-import {
        gap: .45rem;
        padding: var(--si-s4); border-radius: 12px;
        background: var(--si-bg-alt);
        box-shadow: inset 0 0 0 1px rgba(var(--shadow-rgb), .06);
    }
    .si-import__hint { font-size: .75rem; line-height: 1.5; }

    /* On a phone every secondary action is a full-width row because thumbs
       need the target. With room they size to their labels, and Reset
       falls to the right of the group it is not part of. */
    .si-qractions__more .si-btn { flex: 0 0 auto; }
    .si-qractions__more #upiqr-reset,
    .si-qractions__more #urlqr-reset { margin-left: auto; }
}

/* --- >= 961px: form left, code right ------------------------------------- */
@media (min-width: 961px) {
    /*
       TWO BOUNDED COLUMNS, CENTRED — NOT ONE FLEXIBLE AND ONE FIXED.

       `1fr` for the form gives it 780px on a 1200px page, and a 780px-wide
       text input beside a 350px QR code reads as a form that has been
       stretched rather than laid out. Both columns take their natural
       size and the pair sits in the middle of the page, which is how a
       focused utility is composed and what keeps the generator inside the
       first viewport at every desktop width.
    */
    /*
       THE RESULT COLUMN IS NOW THE WIDER OF THE TWO.

       It was 27rem of form against 22rem of result, which made the form
       the page's main object — on a page whose main object is the
       payment poster a merchant is about to print. Reversing the ratio
       puts roughly 60px more into the poster and takes it off a column
       of 46px-tall inputs that did not need it.
    */
    .si-qrgen {
        grid-template-columns: minmax(0, 25rem) minmax(0, 27rem);
        justify-content: center;
        gap: var(--si-s6);
        max-width: none;
    }
    /* THE RESULT FOLLOWS THE MERCHANT DOWN THE FORM. `align-self: start`
       is what makes the sticky work inside a grid row; the offset clears
       the sticky header. */
    .si-qrgen__result { position: sticky; top: 5.5rem; align-self: start; }
    .si-qrpanel__code { max-width: none; }

    /* In a 430px column the secondary row is two even columns: five
       auto-width buttons wrapping there leave a different ragged edge on
       every line. Reset takes the full width beneath them, because it is
       destructive and should not sit in a pair with Print. */
    .si-qractions__more {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    .si-qractions__more .si-btn { flex: none; }
    .si-qractions__more #upiqr-reset,
    .si-qractions__more #urlqr-reset { grid-column: 1 / -1; margin-left: 0; }
}

/* --- Print: the poster, and nothing else --------------------------------- */
/*
   A merchant printing this page wants the card for their counter, not a
   web page with a QR code somewhere in it. So everything else is removed:
   the header, the footer, the breadcrumb, the form, the article and the
   product hand-off.

   WHAT PRINTS IS WHAT IS ON SCREEN. The poster and the bare symbol are
   both real markup, so the selected preview prints as itself — there is
   no third layout that could print an amount different from the one the
   merchant just checked.
*/
/* =========================================================================
   22. GOOGLE REVIEW QR GENERATOR  (/tools/google-review-qr-code-generator)
   =========================================================================
   The second QR tool, and deliberately a SIBLING of §21 rather than a
   second copy of it. Everything structural is reused unchanged — the
   breadcrumb, the switcher, `.si-qrgen`, `.si-qrout`, `.si-qrmodes`,
   `.si-qractions`, `.si-import`, the form primitives — because two tools
   in one family that lay out differently is the failure this whole
   section exists to prevent.

   What is new is only what this tool genuinely has and the UPI one does
   not: a review poster with its own hierarchy, a share panel, a handoff
   to another tool, and the quiet note about a listing link.

   THE CLASS IS `si-grp`, NOT `si-poster` OR `si-reviewposter`.
   `.si-poster` is the landing page's merchant-poster mock (§16) and
   reusing it collapsed the UPI card the first time — the third `si-*`
   collision on this codebase. Grep before naming.
   ========================================================================= */

/* --- `hidden` HAS TO OUT-RANK A LAYOUT RULE, AGAIN ----------------------- */
/*
   `[hidden] { display: none }` lives in the USER-AGENT stylesheet and any
   author rule beats it however specific. Every class below that sets a
   `display` and is toggled with `el.hidden` needs its own escape, or the
   page renders an empty share menu, an empty handoff and a note about a
   link nobody pasted — which is exactly what §21 shipped on its first
   render. The tool toggles `el.hidden` because that is the property a
   screen reader reads, so the fix belongs here.
*/
.si-grp[hidden],
.si-tpl[hidden],
.si-handoff[hidden],
.si-kindnote[hidden],
.si-sharemenu[hidden],
.si-sharemenu__item[hidden] { display: none; }

/* The result box is the poster's ground, not a frame around it — the same
   rule §21 applies to `.si-qrposter`, stated separately so the two tools
   do not share a selector that both must edit. */
.si-qrout:has(.si-grp:not([hidden])) { background: transparent; box-shadow: none; min-height: 0; }

/* --- The review poster, replayed from compiled primitives ---------------- */
/*
   THE PREVIEW IS THE DOWNLOAD AT A DIFFERENT SCALE.

   `review-poster.js` compiles a template into a list of primitives
   positioned in a 680-unit space, and paints that list twice: onto a
   canvas for the PNG, and into this element for the preview. Every
   position below is written by the script as `calc(var(--u) * N)` with
   the SAME N the canvas uses, so there is nothing here to keep in step —
   the two cannot disagree without the arithmetic itself being wrong.

   THE ONLY JOB THIS BLOCK HAS is to define one design unit and to make
   the children obey their inline geometry. `--u` comes from the
   container's own width, so the same markup is a 110px thumbnail and a
   430px preview with no second code path.

   The `px` fallback lands first for engines without container queries;
   `--pst-h` is set inline by the painter, because a poster's height
   depends on how the merchant's name wrapped.
*/
/*
   EVERY POSTER ROOT, NOT ONE OF THEM.

   `poster-kit.js` paints `si-pst__*` children into whatever element it
   is given, so the root rule is shared by selector list rather than by
   one class: the review poster is `.si-grp`, the Wi-Fi card is
   `.si-wfp`, and a seventh poster adds its name here. The alternative
   -- one root class for all of them -- would mean the review view's
   markup changing for the sake of a name, and a poster is entitled to
   its own hook for the things that are genuinely its own.
*/
.si-grp,
.si-wfp,
.si-lkp,
.si-vcp,
.si-txp {
    --u: .63px;
    --u: calc(100cqw / 680);
    position: relative;
    width: 100%;
    height: calc(var(--u) * var(--pst-h, 850));
    overflow: hidden;
    border-radius: calc(var(--u) * var(--pst-radius, 30));
    background: var(--pst-bg, #fff);
    box-shadow: var(--si-shadow);
    /* A poster is a picture of text, not a paragraph: selecting across it
       yields a jumble of absolutely-positioned fragments in DOM order. */
    user-select: none;
}

/* The decoration and shape layers. Painted from the same path data the
   canvas feeds to `Path2D`, in the same 680-unit viewBox. */
.si-pst__layer {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    overflow: visible;
    pointer-events: none;
}

.si-pst__rect { position: absolute; }

/* `line-height: 1` is load-bearing: the painter positions each line by
   subtracting the font's own ascent from the baseline it wants, and that
   calculation assumes a one-em line box. */
.si-pst__text {
    position: absolute;
    line-height: 1;
    white-space: nowrap;
    display: inline-block;
}

.si-pst__qr { position: absolute; }
.si-pst__qr svg { display: block; width: 100%; height: auto; }

/* --- The design gallery --------------------------------------------------- */
/*
   SIX DESIGNS, SHOWN AS SIX DESIGNS.

   A name is not a preview: "Warm Editorial" tells a merchant nothing
   about what they are about to print. Each option therefore carries a
   real miniature of its own poster, rendered by the same painter as the
   large one — which is also why there is no sprite sheet and no six
   exported images to ship.

   On a phone the rail scrolls sideways and snaps. Six cards stacked
   vertically would push the download button off the second screen, and a
   3x2 grid at 360px gives thumbnails too small to tell apart.
*/
/*
   `min-width: 0` IS WHAT MAKES THE RAIL SCROLL AT ALL.

   A grid item's `min-width` defaults to `auto`, which resolves to its
   MIN-CONTENT width — and the rail's min-content is six 110px cards that
   have been told `flex: 0 0 auto`, so ~710px. `overflow-x: auto` cannot
   help, because the box never gets asked to be narrower than its
   contents: instead the whole result column grew to 710px and pushed the
   page sideways at every phone width.

   Both ancestors need it. The rail is inside `.si-tpl`, which is inside
   `.si-qrgen__result`, and a single `auto` anywhere in that chain is
   enough to re-inflate the lot.
*/
.si-qrgen__result { min-width: 0; }
.si-tpl { margin-top: var(--si-s4); min-width: 0; }
.si-tpl[hidden] { display: none; }

.si-tpl__label {
    font-size: .6875rem; font-weight: 700; letter-spacing: .08em;
    text-transform: uppercase; color: var(--si-subtle);
    margin-bottom: .6rem;
}

.si-tpl__rail {
    display: flex; gap: .6rem;
    min-width: 0;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    /* Room for the focus ring and the lifted selected card, which a
       plain `overflow: auto` would otherwise clip. */
    padding: .3rem .2rem .55rem;
    margin: -.3rem -.2rem -.55rem;
}
.si-tpl__rail::-webkit-scrollbar { display: none; }

/* WIDE ENOUGH TO READ A DESIGN FROM.

   At 6.9rem the miniatures were legible as shapes and not as posters —
   a merchant could see that six things differed without being able to
   see how. 8.5rem is the width at which the headline, the plate and the
   footer treatment all resolve, and four still fit across a 360px phone
   with the fifth peeking in to say the row scrolls. */
.si-tpl__opt {
    flex: 0 0 auto;
    scroll-snap-align: center;
    width: 9.6rem;
    display: flex; flex-direction: column; gap: .5rem;
    padding: 0; border: 0; background: none;
    font: inherit; text-align: center; cursor: pointer;
    border-radius: 16px;
}
.si-tpl__opt:focus-visible { outline: 2px solid var(--si-blue); outline-offset: 3px; }

/* The miniature. Its own container, so `--u` inside resolves against
   this width and the poster scales with nothing else to change. */
/* The miniature sits on its own ground with a little air around it, so a
   poster whose own edge is pale does not dissolve into the page. Its own
   container, so `--u` inside resolves against this width and the poster
   scales with nothing else to change. */
.si-tpl__frame {
    container-type: inline-size;
    display: block; width: 100%;
    padding: .28rem;
    border-radius: 12px;
    background: var(--si-bg-alt);
    box-shadow: 0 0 0 1px var(--si-border);
    transition: box-shadow .16s ease, transform .16s ease, background-color .16s ease;
}
.si-tpl__frame .si-grp,
.si-tpl__frame .si-wfp {
    display: block;
    border-radius: 6px;
    box-shadow: 0 1px 3px rgba(var(--shadow-rgb), .1);
}

.si-tpl__name {
    font-size: .8125rem; font-weight: 600; color: var(--si-subtle);
    line-height: 1.3;
    overflow-wrap: anywhere;
}

.si-tpl__opt:hover .si-tpl__frame {
    background: var(--si-surface);
    box-shadow: 0 0 0 1px var(--si-border-strong), 0 4px 12px rgba(var(--shadow-rgb), .08);
}

/* THE SELECTED STATE IS THREE SIGNALS, NOT A COLOUR.

   `aria-checked` carries it to a screen reader, the ring carries it to
   everyone else, and the name goes solid and dark so the choice survives
   a monochrome print-out and a colour-blind reader. Selection that lives
   only in a border colour is selection a third of people cannot see. */
.si-tpl__opt.is-on .si-tpl__frame {
    background: var(--si-surface);
    box-shadow: 0 0 0 2px var(--si-blue), 0 8px 20px rgba(var(--shadow-rgb), .14);
    transform: translateY(-2px);
}
.si-tpl__opt.is-on .si-tpl__name { color: var(--si-navy); font-weight: 700; }

/* --- Desktop: the gallery becomes a rail beside the preview -------------- */
/*
   A TEMPLATE BROWSER, NOT A ROW OF SETTINGS.

   Below 1200px the rail stays where the markup puts it — under the
   preview, scrolling sideways — because a third column at 1024px leaves
   the poster too small to judge, which is the one thing the page exists
   to let a merchant do.

   Above it there is room for both: the preview takes the width it needs
   and the six designs sit beside it, all visible at once with nothing to
   scroll. The areas are named so the rail can span the whole right-hand
   side without the status line, the actions and the privacy note moving.
*/
@media (min-width: 1200px) {
    /*
       THE POSTER IS THE PRODUCT, SO IT TAKES THE SPACE.

       The form is two fields and an import button; at 22rem it was a
       narrow column with a lot of air under it while the preview — the
       thing a merchant is actually judging — was capped well below the
       width available. The form shrinks to what it needs and the poster
       and its rail take the rest.
    */
    .si-qrgen {
        grid-template-columns: minmax(0, 20.5rem) minmax(0, 46rem);
        gap: var(--si-s6);
        align-items: start;
    }

    /*
       THE LAST ROW IS A SPACER, AND IT IS LOAD-BEARING.

       The rail spans all four rows and is taller than they are, so the
       grid had nowhere to put the leftover height except BETWEEN them:
       measured at 1280px, 156-165px of nothing between the card and
       the status line, and the same again before the buttons. It read
       as a broken render on both tools, and `align-items: start` could
       not fix it -- that aligns an item inside its row, and the rows
       themselves were the thing being stretched.

       A final `1fr` row takes the slack, so the four content rows size
       to their content and the gap between them is the 0 this rule
       already asked for.
    */
    .si-qrgen__result {
        display: grid;
        grid-template-columns: minmax(0, 1fr) 10.4rem;
        grid-template-areas:
            "out     rail"
            "meter   rail"
            "status  rail"
            "actions rail"
            "privacy rail"
            ".       rail";
        grid-template-rows: auto auto auto auto auto 1fr;
        gap: 0 1rem;
        align-items: start;
    }

    /*
       SCOPED TO THE RESULT GRID, which is the only place these names
       exist. As bare class selectors they reached any element with
       one of these classes anywhere on the page -- and a
       `.si-qrprivacy` placed in the FORM column took `grid-area:
       privacy` into a grid with no such area, which creates implicit
       tracks: the reader's form laid itself out in two columns with
       the camera beside the drop zone, for no reason visible in
       either element's own CSS.
    */
    .si-qrgen__result > .si-qrout     { grid-area: out; }
    .si-qrgen__result > .si-qrstatus  { grid-area: status; }
    .si-qrgen__result > .si-qractions { grid-area: actions; }
    .si-qrgen__result > .si-qrprivacy { grid-area: privacy; }
    /*
       THE METER IS A RESULT, NOT A SIDEBAR.

       `rail` is the design gallery's column and it is 10.4rem wide. A
       tool with no gallery leaves it empty, and an element with no
       `grid-area` of its own auto-places into the first free cell --
       which put the capacity panel in that column, 166px wide, with
       "24.5 mm" wrapping and the three print sizes stacked one word
       per line. It looked deliberate, which is what makes this class
       of bug expensive.

       So the meter has a row of its own under the card. An area no
       element claims collapses to nothing and there is no row gap, so
       the five tools without a meter are unchanged.
    */
    .si-qrgen__result > .si-cap { grid-area: meter; margin-top: var(--si-s3); }

    /* The rail turns vertical and stops scrolling: six options fit the
       column, so snapping and an overflow are machinery with nothing to
       do. */
    .si-tpl {
        grid-area: rail;
        margin-top: 0;
        position: sticky; top: 5.5rem;
    }
    .si-tpl__rail {
        flex-direction: column;
        overflow: visible;
        scroll-snap-type: none;
        gap: .55rem;
    }
    .si-tpl__opt { width: 100%; }
}

/* --- The note under the link field --------------------------------------- */
/*
   NOT an error and not styled as one. A Maps share link produces a
   working poster; it is simply one tap longer for the customer. Amber
   would make a merchant think they had done something wrong and go
   looking for a fix they do not need.
*/
.si-kindnote {
    display: flex; align-items: flex-start; gap: .45rem;
    margin-top: .5rem;
    font-size: .75rem; line-height: 1.5; color: var(--si-muted);
}
.si-kindnote svg { flex: none; width: 15px; height: 15px; margin-top: .1rem; color: var(--si-border-strong); }
.si-kindnote strong { font-weight: 650; color: var(--si-navy-3); }

/* --- The handoff to another tool ----------------------------------------- */
/*
   Shown only when what was pasted is something ANOTHER live PayMQR tool
   handles. The view renders its targets from ToolCatalog, so this cannot
   point at a page that does not exist.
*/
.si-handoff { display: block; margin-top: .45rem; font-size: .75rem; }
.si-handoff a { color: var(--si-action-ink); font-weight: 600; text-decoration: none; }
.si-handoff a:hover { text-decoration: underline; }

/* --- Share ---------------------------------------------------------------- */
/*
   ONE CONTROL THAT OPENS A PANEL, NOT FOUR MORE BUTTONS IN THE ROW.

   A merchant sharing a poster has one intention and the channel is a
   detail inside it. Four equal-weight buttons make them answer the detail
   first, and on a phone they become a squeezed grid that pushes the
   download out of the first screen.
*/
/* THE WRAP FLEXES LIKE A BUTTON, BECAUSE IT CONTAINS ONE.

   `.si-qractions__more .si-btn { flex: 1 1 auto }` (§21) only reaches
   DIRECT children, and Share is one level down inside this wrapper — so
   without these two lines the wrapper collapses to its content and sits
   at 63px next to a stretched Print and Reset. */
.si-sharewrap { position: relative; display: inline-flex; flex: 1 1 auto; }
.si-sharewrap > .si-btn { width: 100%; }

.si-sharemenu {
    position: absolute; z-index: 20; left: 0; bottom: calc(100% + .4rem);
    min-width: 14rem; padding: .35rem;
    display: flex; flex-direction: column;
    background: var(--si-surface);
    border-radius: 12px;
    box-shadow: 0 10px 30px rgba(var(--shadow-rgb), .14), inset 0 0 0 1px var(--si-border);
}

.si-sharemenu__item {
    display: flex; align-items: center; gap: .55rem;
    padding: .55rem .6rem;
    border: 0; border-radius: 9px; background: none;
    font: inherit; font-size: .8125rem; font-weight: 550; text-align: left;
    color: var(--si-navy); text-decoration: none; cursor: pointer;
}
.si-sharemenu__item:hover { background: var(--si-bg-alt); }
.si-sharemenu__item:focus-visible { outline: 2px solid var(--si-blue); outline-offset: -2px; }
.si-sharemenu__item svg { flex: none; width: 17px; height: 17px; color: var(--si-subtle); }

/* --- The "where do I find my link" disclosure ---------------------------- */
.si-more__body { margin-top: .6rem; font-size: .8125rem; line-height: 1.6; color: var(--si-muted); }
.si-more__body p + p { margin-top: .5rem; }
.si-more__body strong { font-weight: 650; color: var(--si-navy-3); }

/* --- Phone ---------------------------------------------------------------- */
/*
   The panel opens ABOVE the button on a phone and is pinned to the row's
   width, because a 14rem menu anchored to a button near the right edge
   hangs off the screen — and a share menu that causes a horizontal scroll
   is a share menu that gets closed.
*/
@media (max-width: 30rem) {
    /*
       THE PANEL ANCHORS TO THE ACTIONS ROW, NOT TO THE BUTTON.

       `left: 0; right: 0` resolves against the nearest POSITIONED
       ancestor, which was `.si-sharewrap` — so on a phone the panel came
       out exactly as wide as the Share button: 63px, with "Share on
       WhatsApp" wrapping to four lines inside it. Measured at 390px and
       360px; the earlier `min-width: 0` made it worse rather than better,
       which is the tell that the anchor was wrong and not the width.

       Making the row the containing block gives the panel the row's full
       width, and costs nothing above this breakpoint where the 14rem
       minimum already fits beside the button.
    */
    .si-qractions__more { position: relative; }
    .si-sharewrap { position: static; }
    .si-sharemenu { left: 0; right: 0; min-width: 0; }
}

/* At the two-column step (§21) the row is a grid, so Reset — the one
   action that is not a sibling of the other two in meaning — takes the
   full width rather than stranding itself in a half-width cell. */
@media (min-width: 961px) {
    .si-qractions__more #grqr-reset { grid-column: 1 / -1; }
}

/* =========================================================================
   23. Wi-Fi QR GENERATOR  (/tools/wifi-qr-code-generator)
   =========================================================================
   The third QR tool and the first to use the shared poster kit, so
   there is almost nothing here: the card itself is painted from the
   compiled layout (`.si-wfp` takes the shared root rule above, and
   every child is a `si-pst__*` the kit emits). What is left is the
   three controls this form has that no other tool in the family does.
   ========================================================================= */

/* --- Security: four options, visible at once ------------------------------ */
/*
   A SELECT WOULD HIDE THE ONE THAT CHANGES THE FORM. Choosing "Open"
   removes the password field, and a control that reshapes the form
   should not be behind a click. Four short labels fit on one row at
   every width this page is read at, and wrap rather than scroll.
*/
.si-wfsec { border: 0; padding: 0; margin: 0; min-width: 0; }
.si-wfsec__row {
    display: flex; flex-wrap: wrap; gap: var(--si-s2);
    margin-top: var(--si-s2);
}
.si-wfsec__opt {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .5rem .75rem;
    border-radius: 9px;
    background: var(--si-bg-alt);
    box-shadow: inset 0 0 0 1px var(--si-border);
    font-size: .875rem; font-weight: 600; color: var(--si-navy);
    cursor: pointer;
    transition: box-shadow .16s ease, background-color .16s ease;
}
.si-wfsec__opt:hover { background: var(--si-surface); }
/* THE RING IS NOT THE ONLY SIGNAL: the radio itself stays visible and
   checked, so the state is never carried by colour alone. */
.si-wfsec__opt:has(input:checked) {
    background: var(--si-surface);
    box-shadow: inset 0 0 0 2px var(--si-navy);
}
.si-wfsec__opt:has(input:focus-visible) { outline: 2px solid var(--si-blue); outline-offset: 2px; }
.si-wfsec__opt input { accent-color: var(--si-navy); margin: 0; }

/* --- The password label row ----------------------------------------------- */
/*
   THE FIELD STARTS VISIBLE AND THE TOGGLE SAYS "HIDE". This is not a
   login: the merchant is copying a key in order to PRINT it, and a
   field of dots means the card comes back from the laminator wrong.
*/
.si-wflabel {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--si-s2);
}
.si-wflabel__toggle {
    border: 0; background: none; padding: 0;
    font: inherit; font-size: .8125rem; font-weight: 600;
    color: var(--si-blue); cursor: pointer;
}
.si-wflabel__toggle:hover { text-decoration: underline; }
.si-wflabel__toggle:focus-visible { outline: 2px solid var(--si-blue); outline-offset: 3px; border-radius: 4px; }

/* --- Hidden network ------------------------------------------------------- */
.si-wfcheck {
    display: inline-flex; align-items: center; gap: .5rem;
    font-size: .875rem; color: var(--si-navy); cursor: pointer;
}
.si-wfcheck input { accent-color: var(--si-navy); margin: 0; }

/* --- The gallery says which cards print the key --------------------------- */
/*
   The difference between the two kinds of design is not visible in a
   110px miniature, and it is the single most consequential thing about
   the card: one of them prints the password where anyone can read it.
   So each option says so in words under its name.
*/
.si-tpl__kind {
    font-size: .6875rem; font-weight: 600; letter-spacing: .01em;
    color: var(--si-subtle); line-height: 1.3;
}


/* =========================================================================
   24. UPI PAYMENT LINK GENERATOR  (/tools/upi-payment-link-generator)
   =========================================================================
   The only tool in the family whose primary output is a LINE OF TEXT.
   The card below it is a convenience and takes the shared poster root
   above (`.si-lkp`); everything here is the readback panel, which has
   one job: let a merchant proof-read the exact link before they send
   it to somebody and ask them for money.
   ========================================================================= */

.si-linkout {
    display: grid; gap: .45rem;
    padding: var(--si-s4);
    border-radius: 14px;
    background: var(--si-bg-alt);
    box-shadow: inset 0 0 0 1px var(--si-border);
}
.si-linkout__label {
    font-size: .6875rem; font-weight: 700; letter-spacing: .08em;
    text-transform: uppercase; color: var(--si-subtle);
}
/*
   THE WHOLE LINK, WRAPPED, NEVER TRUNCATED AND NEVER ELLIPSISED.

   An ellipsis in the middle of a payment destination is the one piece
   of "tidying" this page may not do: `upi://pay?pa=ramesh@ok…&am=450`
   hides the half that says where the money goes. It wraps instead, and
   `anywhere` rather than `break-word` because a UPI intent is one long
   token with no spaces to break at.
*/
.si-linkout__value {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .875rem; line-height: 1.5; color: var(--si-navy);
    overflow-wrap: anywhere;
    user-select: all;
}
.si-linkout__note { font-size: .8125rem; line-height: 1.5; color: var(--si-subtle); }

/*
   The card is secondary here, so it is bounded rather than filling the
   column the way a poster does on the tools that make posters -- and
   the WRAPPER is what is bounded, because it is also what declares the
   container. `--u` is `100cqw`: bound the card itself and its contents
   go on being sized for the full column and spill out of it.
*/
/*
   TWO THINGS IN THE OUTPUT BOX, STACKED. The base `.si-qrout` is a
   centred flex ROW holding one poster; this tool puts the link panel
   above the card.
*/
.si-qrout--stack {
    flex-direction: column;
    align-items: stretch;
    gap: var(--si-s4);
    padding: var(--si-s4);
}

/*
   THE WIDTH IS EXPLICIT, AND THAT IS NOT BELT AND BRACES.

   `container-type: inline-size` makes an element size-contained in the
   inline axis, so its CONTENTS no longer contribute an intrinsic
   width. As a flex item with `width: auto` it therefore resolved to
   ZERO -- the card had seventeen painted children, correct geometry
   and a box 0px wide, which looks exactly like a render that failed.
   A container query element must be given its width by its parent.
*/
.si-lkpwrap {
    container-type: inline-size;
    width: min(25rem, 100%);
    margin-inline: auto;
}
.si-lkp { width: 100%; }


/* =========================================================================
   25. QR CODE READER  (/tools/qr-code-reader)
   =========================================================================
   The only tool whose INPUT is an image somebody else made, so the
   left column is a drop target rather than a form, and the right one
   is a readback rather than a poster.
   ========================================================================= */

/* --- The drop target ------------------------------------------------------ */
/*
   THE WHOLE BOX TAKES A DROP, and the label inside it is the keyboard
   path to the same file input. A `<button>` calling `.click()` would
   need the input taken out of the tab order, which is how this control
   usually ends up unreachable.
*/
.si-dropzone {
    display: grid; justify-items: center; gap: var(--si-s3);
    padding: var(--si-s6) var(--si-s4);
    border-radius: var(--si-radius);
    background: var(--si-bg-alt);
    box-shadow: inset 0 0 0 2px var(--si-border);
    text-align: center;
    transition: box-shadow .16s ease, background-color .16s ease;
}
.si-dropzone.is-over {
    background: var(--si-surface);
    box-shadow: inset 0 0 0 2px var(--si-blue);
}
.si-dropzone > svg { width: 30px; height: 30px; color: var(--si-subtle); }
/* The focus ring belongs to the INPUT, which is what is focused, and is
   drawn on the box the user can see. */
.si-dropzone:has(#qrread-file:focus-visible) { box-shadow: inset 0 0 0 2px var(--si-blue), 0 0 0 3px rgba(var(--shadow-rgb), .12); }

.si-dropzone__btn {
    display: inline-flex; align-items: center; justify-content: center;
    min-height: 2.5rem; padding: 0 1.1rem;
    border-radius: 9px;
    background: var(--si-navy); color: var(--si-surface);
    font-size: .875rem; font-weight: 650;
    cursor: pointer;
}
.si-dropzone__btn:hover { background: var(--si-navy-3); }
.si-dropzone__hint { font-size: .8125rem; line-height: 1.5; color: var(--si-subtle); }

/* A PICTURE OF WHAT WAS READ, drawn onto a canvas rather than linked:
   there is no URL for the chosen file and there should not be one. */
.si-dropzone__preview {
    display: block; max-width: 100%; height: auto;
    border-radius: 10px;
    box-shadow: 0 0 0 1px var(--si-border);
}
.si-dropzone__preview[hidden] { display: none; }

/* --- The readback --------------------------------------------------------- */
.si-readout {
    display: grid; gap: var(--si-s3);
    padding: var(--si-s4);
    border-radius: 14px;
    background: var(--si-surface);
    box-shadow: 0 0 0 1px var(--si-border);
}
.si-readout__kind {
    font-size: .6875rem; font-weight: 700; letter-spacing: .08em;
    text-transform: uppercase; color: var(--si-subtle);
}
.si-readout__rows { display: grid; gap: .6rem; }

/*
   LABEL ABOVE VALUE, NOT BESIDE IT. A two-column row looks tidier and
   breaks the moment a value is a 60-character address or a vCard's
   three phone numbers — which is most of what this page shows.
*/
.si-readrow { display: grid; gap: .1rem; }
.si-readrow__label {
    font-size: .6875rem; font-weight: 700; letter-spacing: .06em;
    text-transform: uppercase; color: var(--si-subtle);
}
/*
   `pre-line` because a value may be several lines -- a card's phone
   numbers, an email body -- and `anywhere` because the other common
   case is one unbroken 200-character address with nothing to break at.
*/
.si-readrow__value {
    font-size: .9375rem; line-height: 1.5; color: var(--si-navy);
    white-space: pre-line; overflow-wrap: anywhere;
}


/* --- The camera ----------------------------------------------------------- */
/*
   HIDDEN UNTIL THE SCRIPT SAYS THE BROWSER HAS ONE, so the controls
   never appear where pressing them would only produce a refusal.
*/
.si-camera { display: grid; gap: var(--si-s3); margin-top: var(--si-s4); }
.si-camera[hidden] { display: none; }

/*
   `object-fit: cover` on a bounded box: a camera gives whatever aspect
   it feels like and a preview that changes the page's height every
   time it opens makes the whole column jump.
*/
.si-camera__view {
    display: block; width: 100%; max-height: 18rem;
    object-fit: cover;
    border-radius: 12px;
    background: #000;
}
.si-camera__view[hidden] { display: none; }

.si-camera__row { display: flex; flex-wrap: wrap; gap: var(--si-s2); align-items: center; }
.si-camera__pick { width: auto; min-width: 10rem; }
.si-camera__pick[hidden] { display: none; }
.si-camera__note { font-size: .8125rem; line-height: 1.5; color: var(--si-subtle); }
.si-camera__note[hidden] { display: none; }


/* =========================================================================
   26. vCARD QR GENERATOR  (/tools/vcard-qr-code-generator)
   =========================================================================
   Structurally a sibling of §21-§25 and nothing here repeats them: the
   breadcrumb, the switcher, `.si-qrgen`, `.si-qrout`, `.si-qractions`,
   `.si-qrprivacy` and the form primitives are the family's and are used
   unchanged.

   TWO THINGS ARE GENUINELY NEW. A multi-line field, because this is the
   first tool that takes an address; and the CAPACITY METER, because this
   is the first payload big enough that the size of the symbol is a
   decision rather than a detail.
   ========================================================================= */

/* --- `hidden` HAS TO OUT-RANK A LAYOUT RULE, A FOURTH TIME --------------- */
/*
   `[hidden] { display: none }` is in the user-agent stylesheet and any
   author rule beats it. `.si-cap` sets a `display`, the script toggles
   `el.hidden` because that is the property a screen reader reads, and
   without this line the meter renders on arrival with nothing in it --
   which is what §24 shipped to production, as a 651px empty box, before
   `.si-qrout[hidden]` was added. Grep for `[hidden]` before adding any
   `display` on this page.
*/
.si-cap[hidden],
.si-cap__blame[hidden] { display: none; }

/* --- A MULTI-LINE FIELD -------------------------------------------------- */
/*
   `.si-input` is a 46px box with no vertical padding, which is right for
   every single-line field on the site and clips a textarea's second line
   entirely. The height has to become automatic, the padding has to come
   back, and the resize handle is vertical only -- a field a visitor can
   drag wider than its column breaks the two-column grid.
*/
.si-input--area {
    height: auto;
    min-height: 4.25rem;
    padding: .6rem .875rem;
    line-height: 1.55;
    resize: vertical;
}

/* --- THE CAPACITY METER -------------------------------------------------- */
/*
   THE MILLIMETRE FIGURE IS THE HEADLINE, set at the size of a number
   somebody reads across a desk rather than the size of body text,
   because it is the one thing on this panel a merchant acts on. The
   module count and the version are facts for whoever wants them and are
   set accordingly: small, muted, last.

   The panel is the only element on these pages with a tinted ground.
   `.si-qrout` goes transparent under a card (below), so the meter sits
   directly on the page and needs an edge of its own to read as one
   thing.
*/
.si-cap {
    display: grid;
    gap: var(--si-s3);
    padding: var(--si-s4);
    background: var(--si-surface);
    border: 1px solid var(--si-border);
    border-radius: 14px;
}

.si-cap__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .45rem; }
.si-cap__size {
    font-size: 1.5rem; font-weight: 800; letter-spacing: -.02em;
    color: var(--si-navy); font-variant-numeric: tabular-nums;
}
.si-cap__sub { font-size: .75rem; color: var(--si-subtle); }

/* --- Which print sizes it scans at --------------------------------------- */
/*
   EVERY SIZE IS ALWAYS LISTED, including the ones this code is too big
   for. A row that disappears when it stops applying is a row nobody
   notices they have lost, and the whole point of this panel is to say
   that the 25mm sticker is now out.

   THE STATE IS IN THE MARK, NOT IN THE COLOUR. A tick or a cross is read
   out by a screen reader and seen by a colour-blind reader; the green
   and the red are reinforcement. See the painter in vcard-qr-tool.js.
*/
.si-cap__fits { display: grid; gap: .35rem; margin: 0; padding: 0; list-style: none; }
.si-cap__fit {
    display: flex; align-items: flex-start; gap: .45rem;
    font-size: .8125rem; line-height: 1.45; color: var(--si-navy);
}
.si-cap__mark { flex: 0 0 auto; width: .9rem; font-weight: 700; text-align: center; }
.si-cap__fit.is-yes .si-cap__mark { color: var(--state-ok-ink); }
.si-cap__fit.is-no { color: var(--si-subtle); }
.si-cap__fit.is-no .si-cap__mark { color: var(--si-danger); }

/*
   THE SENTENCE THAT NAMES A FIELD. Deliberately not red: nothing is
   wrong -- the merchant typed a long address and is being told what it
   cost. An error colour here would read as a mistake to correct.
*/
.si-cap__blame {
    font-size: .8125rem; line-height: 1.55; color: var(--si-navy);
    padding-top: var(--si-s3);
    border-top: 1px solid var(--si-border);
}

.si-cap__facts {
    display: flex; flex-wrap: wrap; gap: var(--si-s3) var(--si-s5);
    margin: 0;
    padding-top: var(--si-s3);
    border-top: 1px solid var(--si-border);
}
.si-cap__fact dt {
    font-size: .625rem; font-weight: 700; letter-spacing: .06em;
    text-transform: uppercase; color: var(--si-subtle);
}
.si-cap__fact dd {
    margin: 0; font-size: .8125rem; color: var(--si-navy);
    font-variant-numeric: tabular-nums;
}

/* The result box is the card's ground, not a frame around it -- the same
   rule §22 states for `.si-grp`, written separately so the two tools do
   not share a selector both must edit. */
.si-qrout:has(.si-vcp:not([hidden])) { background: transparent; box-shadow: none; min-height: 0; }


/* =========================================================================
   27. TEXT QR GENERATOR  (/tools/text-qr-code-generator)
   =========================================================================
   The last member, and almost entirely other sections' CSS: the form
   primitives, `.si-qrout`, `.si-qractions`, `.si-qrprivacy`, and §26's
   whole capacity meter unchanged — including the `meter` grid row,
   because `.si-cap` is the same element here.

   Three things are its own: a taller text box, the note that says the
   card is showing an extract, and the card's own root class.
   ========================================================================= */

.si-clipnote[hidden] { display: none; }

/* --- The text box -------------------------------------------------------- */
/*
   TALLER THAN THE vCARD TOOL'S, because this is the payload rather than
   one field of nine. Five rows is enough to see a short note whole,
   which is the length most of this tool's traffic will be.
*/
.si-input--tall { min-height: 7.5rem; }

/* --- The card is showing an extract -------------------------------------- */
/*
   NOT STYLED AS AN ERROR. Nothing is wrong: somebody typed more than a
   printed card can carry, the code has all of it, and they are being
   told which is which. Amber or red here would send them looking for a
   fix they do not need — the same judgement §22's `.si-kindnote` makes.
*/
.si-clipnote {
    font-size: .75rem; line-height: 1.5; color: var(--si-muted);
}

/* The result box is the card's ground, not a frame around it. */
.si-qrout:has(.si-txp:not([hidden])) { background: transparent; box-shadow: none; min-height: 0; }


/* =========================================================================
   28. UPI ID CHECKER  (/tools/upi-id-checker)
   =========================================================================
   THE ONLY TOOL IN THE FAMILY THAT DRAWS NOTHING. No symbol, no card,
   no download, no `.si-qrout` — the result is a judgement and the words
   are the product. So this section is a readback panel and nothing
   else.

   THE THREE STATES ARE yes / no / NOTE, and the third is why this is
   not §26's meter with different labels. A print size either fits or it
   does not; a handle can be recognised, malformed, or simply absent
   from a list nobody can keep complete — and drawing that third case in
   red would be the page calling a merchant's working ID broken.
   ========================================================================= */

.si-idout[hidden],
.si-idmask[hidden],
.si-idnorm[hidden] { display: none; }

/* --- A NARROWER PAGE, because there is no preview to balance ------------- */
/*
   `.si-qrgen` is a form column beside a poster column. This tool has one
   short field and a paragraph of answer; at the full two-column width
   the field sits alone in 20rem with 46rem of empty page beside it. One
   column, bounded to a reading measure.
*/
/*
   `margin-inline: 0` UNDOES AN AUTO MARGIN THAT IS INVISIBLE EVERYWHERE
   ELSE.

   §21 gives `.si-qrgen` `max-width: 34rem; margin-inline: auto` for the
   narrow layout, and at 1200px the grid grows to fill the wrap so the
   auto margin has nothing left to centre. This tool keeps a bounded
   width at every size, which brings the auto margin back to life:
   measured at 1280px the panel sat 248px in from the heading above it,
   looking like a page that had lost its left column.
*/
.si-qrgen--narrow { display: block; max-width: 44rem; margin-inline: 0; }
.si-qrgen--narrow .si-qrgen__form { margin-bottom: var(--si-s4); }

/*
   AND THE RESULT IS ONE COLUMN HERE, because there is no design
   gallery to put in the second one.

   At 1200px §21 gives `.si-qrgen__result` the `"out rail"` areas and a
   10.4rem rail track for the template picker. A tool without one
   leaves that track empty — measured at 1280px the panel came out
   522px wide under a 704px input, with 166px of nothing beside it.
   This is the THIRD new tool that grid has caught out: P4's camera
   landed in an implicit column, P5's capacity meter auto-placed into
   the rail, and this one simply paid for a track it does not use.
   `UpiIdToolStaticTest` asserts the single column.
*/
.si-qrgen--narrow .si-qrgen__result {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: none;
    gap: var(--si-s3);
}

/*
   AND THE CHILDREN GIVE UP THEIR NAMED AREAS, which is the half that
   is easy to forget and immediately punishing.
   `.si-qrgen__result > .si-qrstatus { grid-area: status }` and its
   siblings are still in force from §21; with the areas removed above,
   each of those names refers to nothing and the grid invents an
   implicit track for it. Written without this rule, the columns came
   out `0px 0px 680px` and the result panel was 34px wide — a worse
   render than the one being fixed, and the same implicit-track
   mechanism P4 hit from the other direction.
*/
.si-qrgen--narrow .si-qrgen__result > * { grid-area: auto; }

.si-input--mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
    font-size: .9375rem;
}

/* --- The verdict --------------------------------------------------------- */
/*
   A SENTENCE, NOT A BADGE. "Valid" in a green pill is exactly the claim
   this tool cannot make, and a badge makes it in one glance before any
   qualifier is read. The state is carried by a word, with colour as
   reinforcement.
*/
.si-idout {
    display: grid;
    gap: var(--si-s3);
    padding: var(--si-s4);
    background: var(--si-surface);
    border: 1px solid var(--si-border);
    border-radius: 14px;
}

.si-idverdict { font-size: 1.0625rem; font-weight: 700; line-height: 1.35; letter-spacing: -.01em; }
.si-idverdict.is-yes { color: var(--state-ok-ink); }
.si-idverdict.is-no { color: var(--si-danger); }
.si-idverdict.is-warn { color: var(--state-warn-ink); }

.si-iddetail { font-size: .8125rem; line-height: 1.6; color: var(--si-muted); }

/* --- The readback rows --------------------------------------------------- */
.si-idrows {
    display: grid; gap: .45rem;
    padding-top: var(--si-s3);
    border-top: 1px solid var(--si-border);
}
.si-idrow {
    display: grid;
    grid-template-columns: .9rem 6.5rem minmax(0, 1fr);
    gap: .45rem;
    font-size: .8125rem; line-height: 1.5;
    color: var(--si-navy);
}
.si-idrow__mark { font-weight: 700; text-align: center; color: var(--si-subtle); }
.si-idrow.is-yes .si-idrow__mark { color: var(--state-ok-ink); }
.si-idrow.is-no .si-idrow__mark { color: var(--si-danger); }
.si-idrow__label {
    font-size: .6875rem; font-weight: 700; letter-spacing: .05em;
    text-transform: uppercase; color: var(--si-subtle); align-self: center;
}
.si-idrow__value {
    overflow-wrap: anywhere;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/*
   THE NEUTRAL ROW IS NOT GREY-ON-GREY. An unrecognised handle is the
   most common thing this panel says about a perfectly good ID, so it
   reads at the same weight as the others and only its mark is muted.
*/
.si-idrow.is-note .si-idrow__value { color: var(--si-navy); }

.si-idnorm {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: .45rem;
    font-size: .8125rem; color: var(--si-navy);
}
.si-idnorm__label {
    font-size: .6875rem; font-weight: 700; letter-spacing: .05em;
    text-transform: uppercase; color: var(--si-subtle);
}

/* --- The masked warning -------------------------------------------------- */
/*
   AMBER, NOT RED. Nothing is broken: somebody pasted what their app
   showed them, which is a reasonable thing to have done and is the
   single most useful correction this tool makes. Red reads as "you did
   something wrong".
*/
.si-idmask {
    display: flex; align-items: flex-start; gap: .55rem;
    padding: var(--si-s3);
    background: rgba(234, 160, 60, .10);
    border: 1px solid rgba(234, 160, 60, .32);
    border-radius: 10px;
}
.si-idmask svg { flex: 0 0 auto; width: 16px; height: 16px; margin-top: .15rem; color: var(--state-warn-ink); }
.si-idmask p { font-size: .8125rem; line-height: 1.6; color: var(--si-navy); }
.si-idmask p + p { margin-top: .35rem; }

/* --- The call to action -------------------------------------------------- */
.si-idcta .si-lede { margin-top: var(--si-s2); }
.si-idcta p + p { margin-top: var(--si-s3); font-size: .9375rem; line-height: 1.7; color: var(--si-muted); }
.si-idcta__row { display: flex; flex-wrap: wrap; gap: var(--si-s3); margin-top: var(--si-s4); }


/* =========================================================================
   29. TABLE / MENU QR SET  (/tools/table-qr-code-generator)
   =========================================================================
   THE ONE PRINTABLE IN THIS FAMILY THAT IS NOT A `--u` POSTER.

   Every other card here is drawn in a 680-unit space and scaled to its
   container. This output is an A4 SHEET and every number in it is a
   physical length — so `table-sheet.js` owns the geometry in
   MILLIMETRES and writes it as inline styles on each element.

   WHICH MEANS THIS SECTION CARRIES NO MEASUREMENT OF ITS OWN. Not the
   page size, not the margin, not the gutter, not the card or the
   symbol. Everything here is either a rule that cannot be inline —
   `@page`, page breaks, the screen-only scaling — or a colour. If a
   millimetre appears below, two places now decide the same number and
   the printed sheet is the one that will be wrong.
   ========================================================================= */

.si-setout[hidden],
.si-setcheck__rule[hidden] { display: none; }

/* --- The sheet on screen ------------------------------------------------- */
/*
   A SHEET IS A4 AT ITS REAL SIZE, which is 794px wide at 96dpi and far
   wider than the result column. It is shown smaller WITHOUT changing a
   single millimetre inside it — the alternative, resizing the cards for
   the screen, would make the preview and the print two different
   sheets, which is the defect the poster kit exists to prevent
   everywhere else.

   `zoom`, NOT `transform: scale()`.

   A transform does not affect layout: the sheet keeps its full
   unscaled box, and the usual repair is a negative margin. That repair
   is wrong and was written here first — A PERCENTAGE MARGIN RESOLVES
   AGAINST THE CONTAINING BLOCK'S WIDTH, INCLUDING `margin-bottom`. So
   `margin-bottom: -52%` took 52% of the WIDTH off a box whose excess
   was in its HEIGHT, and the measured result was 253px of phantom
   space under a two-page preview and a 142px gap between sheets that
   nothing in the CSS asked for.

   `zoom` scales layout as well as paint, so there is nothing to
   repair: the container's height is the sheets' real height and the
   gap between them is the grid gap. Print sets it back to 1, where
   the millimetres are true.
*/
.si-sheets {
    --sheet-scale: .52;
    display: grid;
    gap: var(--si-s4);
    justify-items: start;
}

.si-sheet {
    display: grid;
    box-sizing: border-box;
    background: #fff;
    box-shadow: var(--si-shadow);
    zoom: var(--sheet-scale);
    /* The attribution in the bottom margin is positioned against this.
       `relative` adds no offset of its own, so the grid is unmoved. */
    position: relative;
}

/*
   THE ATTRIBUTION LINE. Its POSITION is written inline by
   table-sheet.js from the sheet's own margin — this names only the type
   and the colour, which is the same division the card's caption follows.

   2.2mm is the smallest type on the sheet — quieter than the address
   line under each symbol, because that line is the merchant's and this
   one is ours.
*/
.si-sheet__brand {
    /* The band is the paper's bottom margin; its HEIGHT is written
       inline by table-sheet.js from that margin. The text sits at the
       band's top — immediately under the grid and as far from the
       paper's edge as the margin allows, because the outer few
       millimetres are where a consumer printer puts no ink. */
    position: absolute; left: 0; right: 0; bottom: 0;
    display: flex; align-items: flex-start; justify-content: center;
    font-size: 2.2mm; font-weight: 600; line-height: 1.1;
    letter-spacing: .02em;
    color: #94A3B8;
}

.si-sheet__card {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2mm;
    box-sizing: border-box;
    border: 1px dashed var(--si-border-strong);
    border-radius: 2mm;
    text-align: center;
}

.si-sheet__plate { flex: 0 0 auto; }
.si-sheet__plate svg { display: block; width: 100%; height: 100%; }

/*
   THE CAPTION IS SIZED IN MILLIMETRES because everything on this card
   is, and because a card's type has to hold its size when the sheet is
   scaled for the screen — `rem` would not.
*/
.si-sheet__label {
    font-size: 4mm; font-weight: 700; line-height: 1.1;
    color: #111827;
}
.si-sheet__url {
    font-size: 2.4mm; line-height: 1.2; color: #6B7280;
    overflow-wrap: anywhere;
}

/* --- The check, which is the point of the page --------------------------- */
.si-setcheck { display: grid; gap: var(--si-s3); }

/*
   THE RULE IN ONE LINE, above the list. It is what makes twenty-four
   addresses checkable at a glance instead of one at a time.
*/
.si-setcheck__rule {
    font-size: .8125rem; line-height: 1.5; font-weight: 650;
    color: var(--si-navy);
}

/*
   A BOUNDED, SCROLLING LIST. Two hundred rows must all be READABLE —
   this is the verification surface — but they may not push the print
   button off the page.
*/
.si-setlist {
    display: grid; gap: .3rem;
    margin: 0; padding: var(--si-s3);
    list-style: none;
    max-height: 22rem; overflow-y: auto;
    background: var(--si-surface);
    border: 1px solid var(--si-border);
    border-radius: 12px;
}

.si-setrow {
    display: grid;
    grid-template-columns: 7rem minmax(0, 1fr) auto;
    gap: .5rem; align-items: baseline;
    font-size: .75rem; line-height: 1.5;
}
.si-setrow__label { font-weight: 650; color: var(--si-navy); }
.si-setrow__url {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    color: var(--si-muted); overflow-wrap: anywhere;
}
.si-setrow__png {
    justify-self: end;
    font: inherit; font-size: .6875rem; font-weight: 700;
    padding: .1rem .4rem;
    color: var(--si-action-ink); background: none;
    border: 1px solid var(--si-border-strong); border-radius: 6px;
    cursor: pointer;
}
.si-setrow__png:hover { border-color: var(--si-action-graphic); }
.si-setrow__png:focus-visible { outline: 2px solid var(--si-blue); outline-offset: 2px; }

/* --- The result column is one track here too ----------------------------- */
/*
   The same reasoning as §28: no design gallery, so no 10.4rem rail —
   and the children have to give up §21's named areas or each name
   refers to nothing and the grid invents an implicit track.
*/
.si-qrgen--sheet .si-qrgen__result {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: none;
    gap: var(--si-s3);
}
.si-qrgen--sheet .si-qrgen__result > * { grid-area: auto; }

/* =========================================================================
   30. THE FAMILY CLOSEOUT  (the hub at ten tools, the steps, the siblings)
   =========================================================================
   Nothing here belongs to one tool. Three pieces:

   THE HUB'S GROUPS. The hub opened as a 46rem column with one card in
   it, which was right and said in its own comment that it held until
   six. At ten it is a page you scroll for a screen and a half. So the
   wrap goes full width, the existing auto-fit grid takes as many cards
   per row as the measure allows, and the tools sit under the headings
   ToolCatalog::GROUPS declares.

   THE STEP LIST, at the foot of every tool page. The same array
   ToolsController emits as HowTo structured data — the steps have to be
   on the page for that markup to describe anything real.

   THE SIBLING ROW, also at the foot of every tool page, from
   ToolCatalog::related(). It reuses `.si-toolcard` rather than inventing
   a second card: the hub and the foot are showing the same object, and a
   near-identical second card is how two things that should look alike
   start to drift.
   ========================================================================= */

/* THE WIDE HUB IS A MODIFIER, NOT AN EDIT TO `.si-toolhub`. The narrow
   column is still the right measure for a hub with few cards, and the
   next thing built on this family may want it. */
.si-toolhub--wide { max-width: none; }

/* A GROUP IS A HEADING AND A GRID. The gap between groups is larger than
   the gap between cards, which is what makes the heading belong to the
   row under it rather than float between two. */
.si-toolgroup + .si-toolgroup { margin-top: var(--si-s7); }
.si-toolgroup__head { margin-bottom: var(--si-s4); }

/* The lede keeps a reading measure even though the wrap no longer does:
   a 1200px-wide paragraph is not a paragraph anybody reads. */
.si-toolhub--wide .si-head .si-lede { max-width: 46rem; }

/* --- The three steps ----------------------------------------------------- */
/*
   A NUMBERED LIST, DRAWN FROM A COUNTER RATHER THAN `list-style`.

   `list-style-position: inside` wraps the second line under the number
   and `outside` hangs the number off the section's left edge; both read
   as a list that was not laid out. The counter puts the number in its own
   track, so every step's text starts on one axis.
*/
.si-toolsteps {
    list-style: none; margin-top: var(--si-s4); padding: 0;
    display: grid; gap: var(--si-s3); counter-reset: si-toolstep;
    max-width: 46rem;
}
.si-toolsteps li {
    counter-increment: si-toolstep;
    display: grid; grid-template-columns: 1.6rem minmax(0, 1fr);
    gap: var(--si-s3); align-items: baseline;
    font-size: .9375rem; line-height: 1.6; color: var(--si-text);
}
.si-toolsteps li::before {
    content: counter(si-toolstep);
    font-size: .8125rem; font-weight: 700; font-variant-numeric: tabular-nums;
    color: var(--si-action-ink);
    text-align: center;
}

/*
   THE SPACE BETWEEN THE STEPS AND THE SIBLINGS IS DECLARED HERE, not
   taken from a margin utility in the template.

   It was `si-mt-7` for one draft -- a class that does not exist: the
   utilities stop at `si-mt-6`. The markup validated, the page rendered,
   and the two blocks sat on top of each other. A spacing that belongs
   to a block belongs in the block's own rule, where it cannot be a
   class name nobody defined.
*/
.si-toolfoot__next { margin-top: var(--si-s6); }

/* --- The sibling row ----------------------------------------------------- */
/*
   13rem, NOT THE HUB'S 17rem.

   `.si-toolfoot` is a 46rem column and three 17rem minimums do not fit
   in it, so the auto-fit grid would wrap the third card onto a row of
   its own — a row of two and a row of one, which reads as a layout that
   broke rather than as three related tools. Three 13rem tracks and two
   gaps fit 46rem with room to spare, and `min(100%, …)` still collapses
   them to one column on a phone.

   The number of cards is NOT named here. ToolCatalog::RELATED_LIMIT
   decides how many there are; this decides how narrow one may get.
*/
.si-toolcards--related {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
    gap: var(--si-s3);
    margin-top: var(--si-s4);
}
/* The sibling card is a destination, not a pitch: no "Open tool" row, so
   the padding and the type step down with it. */
.si-toolcards--related .si-toolcard { padding: var(--si-s4); gap: var(--si-s3); }
.si-toolcards--related .si-toolcard__title { font-size: .9375rem; }
.si-toolcards--related .si-toolcard__text { font-size: .8125rem; margin-top: var(--si-s1); }

/* =========================================================================
   PRINT — LAST IN THE FILE, AND IT STAYS LAST
   =========================================================================
   A new section was once appended BELOW this block, which read fine and
   rotted a guard three files away: §29's "the print block names no
   length" assertion bounded its slice at the end of the FILE, because
   the print block had always been the end of the file. It then read the
   new section's lengths and reported them as print measurements.

   So: sections go above this. The guard no longer depends on it — it now
   cuts at this block's own closing brace — but the convention is still
   the reason the cascade works, since everything here overrides what is
   above it.
   ========================================================================= */
@media print {
    .si-skip,
    .si-header,
    .si-footer,
    .si-toolbar,
    .si-section:not(.si-qrsec) { display: none !important; }

    .si-qrsec { padding: 0 !important; }
    .si-qrsec .si-toolhead,
    .si-qrgen__form,
    .si-qrmodes,
    .si-qrstatus,
    .si-qractions,
    .si-qrdest,
    .si-qrprivacy { display: none !important; }

    .si-qrgen { display: block !important; max-width: none !important; }
    .si-qrout {
        background: none !important; box-shadow: none !important;
        min-height: 0 !important; display: block !important;
    }

    /* 150mm of a 680-unit design: an A4 sheet with a printable counter
       card in the middle of it.

       THE FONT SIZE ALONE IS NO LONGER ENOUGH. Lengths on this poster
       come from `--u`, which derives from `100cqw` -- the CONTAINER's
       width -- so bounding the poster without bounding `.si-qrout`
       would print mm-sized type with screen-sized gaps. The
       `.si-qrout { width: 150mm }` rule further down this block is what
       actually resizes all three tools' cards, and it is load-bearing
       for this one: measured in print emulation, the payee's margin
       comes out at 8.00 units by the container and 8.02 by the font, so
       the two scales agree. `PosterUnitScaleTest` asserts the bound
       exists rather than leaving it to be rediscovered. */
    .si-qrposter {
        font-size: 2.2mm;
        width: 150mm; margin: 0 auto;
        box-shadow: 0 0 0 .15em var(--si-border) !important;
        break-inside: avoid;
    }
    .si-qrplain { box-shadow: none !important; max-width: 120mm; margin: 0 auto; }

    /* The link card, at the same 150mm of a 680-unit design. */
    .si-urlcard {
        font-size: 2.2mm;
        width: 150mm; margin: 0 auto;
        box-shadow: 0 0 0 .15em var(--si-border) !important;
        break-inside: avoid;
    }

    /* Backgrounds are dropped by default in print, and the acceptance
       well and the plate are both defined by theirs. */
    .si-qrposter, .si-qrposter__plate, .si-qrposter__apps,
    .si-urlcard, .si-urlcard__plate {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    /* THE REVIEW POSTER PRINTS ON THE SAME TERMS (§22).

       The tools share `.si-qrsec`, `.si-qrgen__form`, `.si-qractions` and
       the rest, so everything above already hides the right things on
       this page too — a second tool inherits the print stylesheet by
       using the family's class names. Only the poster itself is its own
       element, and it needs the same 150mm and the same colour
       adjustment, or the plate prints as white-on-white and the symbol
       loses the quiet zone that makes it scannable. */
    /* THE DESIGN PICKER IS NOT PART OF THE POSTER. It sits outside
       `.si-qractions`, so it needs its own line here or a merchant gets a
       sheet of paper with six thumbnails on it. */
    .si-tpl { display: none !important; }

    /* 150mm ON THE CONTAINER, NOT ON THE POSTER.

       `.si-grp` derives its design unit from `100cqw` — its container's
       width — so constraining the poster itself would leave every
       position inside it scaled to the screen. Bounding `.si-qrout` is
       what actually resizes the card, and the poster follows at 100%. */
    .si-qrout { width: 150mm !important; margin: 0 auto !important; }

    .si-grp,
    .si-wfp,
    .si-lkp,
    .si-vcp,
    .si-txp {
        box-shadow: 0 0 0 .3mm var(--si-border) !important;
        break-inside: avoid;
    }

    /* Backgrounds are dropped by default in print, and on these six
       designs the ground, the plate and every band are defined by
       theirs — without this the symbol prints white on white. */
    .si-grp, .si-wfp, .si-lkp, .si-vcp, .si-txp, .si-pst__rect, .si-pst__layer, .si-pst__qr {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    /* --- §29, THE A4 SHEET ----------------------------------------------
       The one printable here that is NOT bounded to 150mm. It is already
       A4 at its true size, in millimetres, from table-sheet.js — so
       printing it means UNDOING the screen presentation and nothing
       else. Every rule below removes something; not one adds a length.

       `@page { margin: 0 }` cannot live in this file: it is not
       scopable to a selector and would change what every other tool
       prints. It is in the sheet tool's own view, with the reason. */
    .si-qrgen--sheet .si-setcheck,
    .si-qrgen--sheet .si-qrstatus,
    .si-qrgen--sheet .si-qractions,
    .si-qrgen--sheet .si-qrprivacy,
    .si-qrgen--sheet .si-qrpanel__empty { display: none !important; }

    /* The screen's 150mm bound belongs to the single-card tools; a sheet
       that inherited it would print A4 content inside a 150mm box. */
    .si-qrgen--sheet,
    .si-qrgen--sheet .si-qrgen__result,
    .si-qrgen--sheet .si-setout,
    .si-qrgen--sheet .si-sheets {
        display: block !important;
        width: auto !important;
        max-width: none !important;
        margin: 0 !important;
        padding: 0 !important;
        gap: 0 !important;
        overflow: visible !important;
    }

    /* THE SHEET AT ITS REAL SIZE: the screen's zoom comes off, which is
       what makes every millimetre inside it true, and each sheet takes
       a page of its own. */
    .si-sheet {
        zoom: 1 !important;
        margin: 0 !important;
        box-shadow: none !important;
        break-after: page;
        break-inside: avoid;
    }
    .si-sheet:last-child { break-after: auto; }

    /* The dashed rule is a cutting line and should print; the symbols
       are black on white and need the colour kept either way. */
    .si-sheet__card,
    .si-sheet__plate svg {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }
}
