/* ==========================================================================
   qrcodes.css — the QR workbench (conversion/templates/qrcode/qrcode.html).

   Five screens used to sit here: a launcher of four cards and one page each for
   WiFi, links, plain text and reading a code. Between them they carried 702
   lines of markup, four near-identical `<style>` blocks, four cdnjs Font Awesome
   `<link>`s, a `#4CAF50` green and a `#2196F3` blue belonging to no preset the
   admin can choose, `onclick="copyText()"` on every copy button, and — in
   generate_plain_text_qr.html:16 — a paste accident that had left
   `@media (max-width: 768px) {WEBSITE BOOKMARK SYSTEM` inside the stylesheet.

   They are one screen with four tabs now, per the decision on this slice, and
   this is its only stylesheet. Same contract as banglish.css, youtube.css and
   cropcut.css beside it in this app:

     * every selector is prefixed .qr-scope, matching the wrapper <div> the
       template opens with. accounts/base.html renders {% block extra_css %}
       BEFORE the theme, MDB, the Bootstrap 4.3.1 build and custom.css, so this
       file can only ever win on specificity, never on order;
     * no :root block — main.models.ColorPreset injects the palette after every
       stylesheet, and a :root here would override the admin's chosen colours;
     * --color-* is never redeclared, only read with a literal fallback;
     * only Montserrat and Open Sans 400/700/800 are loaded, so there is no
       font-weight 500 or 600 below;
     * layout is CSS grid, never Bootstrap's row/col;
     * nothing is positioned by z-index.

   Tokens are banglish.css's verbatim, which are voicetyping.css's verbatim. A
   visitor crossing between the tools in this app is inside one account dashboard
   and should not cross a seam.
   ========================================================================== */
.qr-scope {
  --qr-surface: #ffffff;
  --qr-surface-muted: #fbfbfc;
  --qr-surface-sunken: #f4f5f8;
  --qr-border: #eceef4;
  --qr-border-strong: #e4e6ee;

  --qr-text: var(--color-default, #2f313a);
  --qr-text-soft: #6c707a;
  --qr-text-muted: #9097a1;

  --qr-brand: var(--color-primary, #c33332);
  --qr-brand-hover: var(--color-primary-hover, #a82b2a);
  --qr-brand-bg: var(--color-primary-light, #fbf2f2);
  --qr-good: #1e8e3e;
  --qr-good-bg: #eaf5ee;
  --qr-good-border: #bfe0c9;
  --qr-warn: #9a6a00;
  --qr-warn-bg: #fdf6e6;
  --qr-warn-border: #ecd9a6;
  --qr-danger: #c33332;
  --qr-danger-bg: #fdf1f1;
  --qr-danger-border: #f0cccc;
  --qr-font-display: "Montserrat", "Noto Sans Bengali", -apple-system,
    BlinkMacSystemFont, "Segoe UI", sans-serif;
  --qr-font-body: "Open Sans", "Noto Sans Bengali", -apple-system,
    BlinkMacSystemFont, "Segoe UI", sans-serif;
  /* What a code read off a poster contains is arbitrary — a URL with a long
     query, a WiFi password, a serial number. It is set in a monospace face so
     that l/1/I and 0/O are told apart by the person copying it down. */
  --qr-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;

  --qr-radius-sm: 4px;
  --qr-radius-md: 5px;

  padding: 4px 0 32px;
  font-family: var(--qr-font-body);
  color: var(--qr-text);
}

/* mdb.min.css uppercases and drop-shadows every button on the page and
   Bootstrap 4.3.1, loading after it, resets neither. This screen uses .qr-btn
   rather than .btn for that reason; the reset stays as insurance for anything
   the shell renders inside the wrapper. */
.qr-scope .btn {
  text-transform: none;
  box-shadow: none;
}

/* The workbench is a JavaScript screen: the tabs switch without a reload, the
   code arrives by fetch, the camera and the clipboard are browser APIs. Anything
   that only works once the script has run carries .qr-jsonly, and the script's
   own `is-enhanced` class is what reveals it — so a visitor with JavaScript off,
   or one whose network dropped the file, sees the explanation in the
   <noscript>/.qr-fallback panel rather than a row of controls that do nothing.
   Written as :not(.is-enhanced) so each element keeps its natural display. */
.qr-scope:not(.is-enhanced) .qr-jsonly {
  display: none !important;
}
.qr-scope.is-enhanced .qr-fallback {
  display: none !important;
}
/* --------------------------------------------------------------------------
   Page head. 21px Montserrat in the body ink — the same metric as .bl-title on
   the Banglish screen and .yt-title on the downloader.

   Every declaration on .qr-title is !important, and it has to be:
   cs-foodbakery-plugin.css:2044 sets `h1, h1 a { font: 700 Normal 30px/36px
   "Montserrat" !important; letter-spacing: 1px !important; text-transform:
   capitalize !important; color: #000000 !important }`. `font` is shorthand, so
   all seven properties are restated below or this heading renders as the theme's
   h1 instead of this design's. Any phone override needs !important too.
   Paragraphs need the same treatment for colour alone: accounts/base.html writes
   `.main-section p { … color: #515151 !important }` into an inline <style> AFTER
   extra_css, so every <p> in this file restates its own colour.
   -------------------------------------------------------------------------- */
.qr-scope .qr-head {
  margin-bottom: 16px;
}

.qr-scope .qr-head__row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}

.qr-scope .qr-head__aside {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
}

.qr-scope .qr-eyebrow {
  margin: 0 0 5px;
  font-family: var(--qr-font-display);
  font-size: 11px;
  font-weight: 700;
  line-height: 16px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--qr-text-muted) !important;
}

.qr-scope .qr-title {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-family: var(--qr-font-display) !important;
  font-size: 21px !important;
  font-weight: 700 !important;
  line-height: 28px !important;
  letter-spacing: -0.2px !important;
  text-transform: none !important;
  color: var(--qr-text) !important;
}

.qr-scope .qr-title i {
  font-size: 19px;
  color: var(--qr-brand);
}
/* 128ch is a stop, not a measure: it binds on nothing narrower than a very wide
   monitor. A reading measure here (62–68ch, right for a lede inside a card) held
   the sentences to roughly half the content column and stacked them under a
   heading of the same width, which reads as a broken layout rather than as a
   paragraph. Same correction as .bl-lede and .vt-lede. */
.qr-scope .qr-lede {
  max-width: 128ch;
  margin: 7px 0 0;
  font-size: 13px;
  line-height: 21px;
  color: var(--qr-text-soft) !important;
}

/* --------------------------------------------------------------------------
   The polite announcer. Off-screen but not `display: none`, which screen readers
   skip — the same block as .bl-live and .yt-live. Every message the status strip
   shows is written here too, so a visitor who cannot see the strip is still told
   the code was built, or refused, or that the image held nothing.
   -------------------------------------------------------------------------- */
.qr-scope .qr-live {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
/* --------------------------------------------------------------------------
   Notices. One shape for four jobs: the "this needs JavaScript" panel, the "this
   server cannot read codes" panel, the readability warnings, and the transient
   result of a press. The transient one is an in-flow strip rather than the Django
   message + full page reload the old decoder used, and rather than the `alert()`
   the three generators used for every failure.
   -------------------------------------------------------------------------- */
.qr-scope .qr-alert {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin: 0 0 16px;
  padding: 11px 14px;
  border: 1px solid var(--qr-border-strong);
  border-radius: var(--qr-radius-sm);
  background: var(--qr-surface-sunken);
  font-size: 12px;
  line-height: 19px;
  color: var(--qr-text-soft) !important;
}
.qr-scope .qr-alert i {
  flex: none;
  margin-top: 2px;
  font-size: 13px;
  color: var(--qr-text-muted);
}

.qr-scope .qr-alert strong {
  display: block;
  margin-bottom: 2px;
  font-family: var(--qr-font-display);
  font-size: 12px;
  font-weight: 700;
  color: var(--qr-text) !important;
}

.qr-scope .qr-alert p {
  margin: 0;
  font-size: 12px;
  line-height: 19px;
  color: inherit !important;
}

.qr-scope .qr-alert p + p {
  margin-top: 5px;
}

.qr-scope .qr-alert code {
  padding: 1px 4px;
  border-radius: 3px;
  background: rgba(0, 0, 0, 0.05);
  font-family: var(--qr-font-mono);
  font-size: 11px;
  color: inherit;
}

.qr-scope .qr-alert.is-good {
  border-color: var(--qr-good-border);
  background: var(--qr-good-bg);
  color: var(--qr-good) !important;
}

.qr-scope .qr-alert.is-good i,
.qr-scope .qr-alert.is-good strong {
  color: var(--qr-good) !important;
}
.qr-scope .qr-alert.is-warn {
  border-color: var(--qr-warn-border);
  background: var(--qr-warn-bg);
  color: var(--qr-warn) !important;
}

.qr-scope .qr-alert.is-warn i,
.qr-scope .qr-alert.is-warn strong {
  color: var(--qr-warn) !important;
}

.qr-scope .qr-alert.is-bad {
  border-color: var(--qr-danger-border);
  background: var(--qr-danger-bg);
  color: var(--qr-danger) !important;
}

.qr-scope .qr-alert.is-bad i,
.qr-scope .qr-alert.is-bad strong {
  color: var(--qr-danger) !important;
}

/* The transient one, written by the script after every press. It sits above the
   two columns rather than beside the button that caused it, so a refusal from the
   rate limiter and a refusal from the form arrive in the same place. */
.qr-scope .qr-status {
  margin: 0 0 14px;
}

.qr-scope .qr-status[hidden] {
  display: none;
}

/* --------------------------------------------------------------------------
   The tab strip. Four buttons, `role="tablist"`, and the panels they own are
   plain `[hidden]` — which is why the panel spacing below is written as
   `:not([hidden]) + …`: a hidden element is still a sibling for `+`, so
   `.qr-panel + .qr-panel` would put a margin under nothing.

   The four screens this replaces each had their own <h1>, their own copy of the
   scaffold and their own back-link to the launcher. Switching cost a page load.
   -------------------------------------------------------------------------- */
.qr-scope .qr-tabs {
  display: flex;
  gap: 4px;
  margin: 0 0 18px;
  padding: 4px;
  border: 1px solid var(--qr-border);
  border-radius: var(--qr-radius-md);
  background: var(--qr-surface-muted);
  overflow-x: auto;
}
.qr-scope .qr-tab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: 1 1 0;
  justify-content: center;
  min-width: 108px;
  padding: 8px 13px;
  border: 1px solid transparent;
  border-radius: var(--qr-radius-sm);
  background: transparent;
  font-family: var(--qr-font-display);
  font-size: 12px;
  font-weight: 700;
  line-height: 18px;
  letter-spacing: 0.1px;
  text-transform: none;
  white-space: nowrap;
  color: var(--qr-text-soft);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.qr-scope .qr-tab i {
  font-size: 13px;
}

.qr-scope .qr-tab:hover {
  background: var(--qr-surface);
  color: var(--qr-text);
}

.qr-scope .qr-tab[aria-selected="true"] {
  border-color: var(--qr-border-strong);
  background: var(--qr-surface);
  color: var(--qr-brand);
}

.qr-scope .qr-tab:focus-visible {
  outline: 2px solid var(--qr-brand);
  outline-offset: 1px;
}

/* Without JavaScript the strip is a row of links to the four old URLs, which
   still resolve — each opens this same screen with that tab already chosen
   server-side. So the tabs work with the script off; they just cost a reload. */
.qr-scope a.qr-tab {
  text-decoration: none;
}
.qr-scope .qr-panel[hidden] {
  display: none;
}

/* --------------------------------------------------------------------------
   The workbench itself: the form on the left, the code on the right, and the
   code stays in view while the form is edited. CSS grid, not Bootstrap's
   row/col — the theme ships two Bootstrap majors and its gutters disagree.

   `minmax(0, …)` on both tracks, not `1fr`: a long decoded string in the
   monospace face has no space to wrap at and would otherwise push the column
   wider than its share and force the page to scroll sideways.
   -------------------------------------------------------------------------- */
.qr-scope .qr-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: 16px;
  align-items: start;
}

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

/* --------------------------------------------------------------------------
   Cards. The container for everything: a form, a preview, a result list. The
   heading inside one restates all seven properties `font` shorthand covers,
   for the reason in the .qr-title note above.
   -------------------------------------------------------------------------- */
.qr-scope .qr-card {
  border: 1px solid var(--qr-border);
  border-radius: var(--qr-radius-md);
  background: var(--qr-surface);
}

.qr-scope .qr-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  padding: 12px 16px;
  border-bottom: 1px solid var(--qr-border);
  background: var(--qr-surface-muted);
  border-radius: var(--qr-radius-md) var(--qr-radius-md) 0 0;
}
.qr-scope .qr-card__title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-family: var(--qr-font-display) !important;
  font-size: 13px !important;
  font-weight: 700 !important;
  line-height: 19px !important;
  letter-spacing: 0.1px !important;
  text-transform: none !important;
  color: var(--qr-text) !important;
}

.qr-scope .qr-card__title i {
  font-size: 13px;
  color: var(--qr-brand);
}

.qr-scope .qr-card__note {
  margin: 0;
  font-size: 11px;
  line-height: 17px;
  color: var(--qr-text-muted) !important;
}

.qr-scope .qr-card__body {
  padding: 16px;
}

.qr-scope .qr-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  padding: 12px 16px;
  border-top: 1px solid var(--qr-border);
  background: var(--qr-surface-muted);
  border-radius: 0 0 var(--qr-radius-md) var(--qr-radius-md);
}

.qr-scope .qr-card__foot p {
  margin: 0;
  font-size: 11px;
  line-height: 17px;
  color: var(--qr-text-muted) !important;
}
/* --------------------------------------------------------------------------
   Form fields. The class names here are not chosen freely: `qr-input`,
   `qr-textarea`, `qr-select`, `qr-check`, `qr-swatch` and `qr-file` are baked
   into the widget `attrs` of the four forms in conversion/forms.py, so renaming
   one of them here silently unstyles a control. The wrappers (.qr-field and
   below) are the template's.
   -------------------------------------------------------------------------- */
.qr-scope .qr-fields {
  display: grid;
  gap: 14px;
}

.qr-scope .qr-fields--pair {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

.qr-scope .qr-field {
  display: grid;
  gap: 5px;
  min-width: 0;
}

.qr-scope .qr-label {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-family: var(--qr-font-display);
  font-size: 11px;
  font-weight: 700;
  line-height: 17px;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--qr-text-soft);
}

.qr-scope .qr-label span {
  font-family: var(--qr-font-body);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--qr-text-muted);
}
.qr-scope .qr-input,
.qr-scope .qr-textarea,
.qr-scope .qr-select {
  width: 100%;
  min-width: 0;
  padding: 8px 11px;
  border: 1px solid var(--qr-border-strong);
  border-radius: var(--qr-radius-sm);
  background: var(--qr-surface);
  font-family: var(--qr-font-body);
  font-size: 13px;
  line-height: 20px;
  color: var(--qr-text);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.qr-scope .qr-input::placeholder,
.qr-scope .qr-textarea::placeholder {
  color: var(--qr-text-muted);
}

.qr-scope .qr-input:focus,
.qr-scope .qr-textarea:focus,
.qr-scope .qr-select:focus {
  outline: none;
  border-color: var(--qr-brand);
  box-shadow: 0 0 0 3px var(--qr-brand-bg);
}

.qr-scope .qr-textarea {
  min-height: 108px;
  resize: vertical;
}

/* The one control whose content is arbitrary machine text rather than prose: a
   URL with a query string, a WiFi password, a serial. Monospace so the person
   retyping it can tell l from 1. */
.qr-scope .qr-input--mono,
.qr-scope .qr-textarea--mono {
  font-family: var(--qr-font-mono);
  font-size: 12px;
}

.qr-scope .qr-select {
  padding-right: 30px;
  appearance: none;
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 9px 6px;
}
/* The arrow is an inline SVG data URI, not a background-image file and not a
   webfont glyph: it needs no extra request, it cannot 404 after a
   `collectstatic`, and it is the same chevron .bl-select uses. */
.qr-scope .qr-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6' viewBox='0 0 9 6'%3E%3Cpath d='M0.5 0.75 L4.5 4.75 L8.5 0.75' fill='none' stroke='%239097a1' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.qr-scope .qr-help {
  margin: 0;
  font-size: 11px;
  line-height: 17px;
  color: var(--qr-text-muted) !important;
}

/* What the endpoint's `fields` half writes under the control that caused it. The
   old generators had nowhere to put this: a bad SSID produced `{'success':
   false}` and the page showed an `alert()` with no field named in it. */
.qr-scope .qr-error {
  display: flex;
  align-items: flex-start;
  gap: 5px;
  margin: 0;
  font-size: 11px;
  line-height: 17px;
  color: var(--qr-danger) !important;
}

.qr-scope .qr-error[hidden] {
  display: none;
}

.qr-scope .qr-error i {
  margin-top: 2px;
  font-size: 11px;
}

.qr-scope .qr-field.is-bad .qr-input,
.qr-scope .qr-field.is-bad .qr-textarea,
.qr-scope .qr-field.is-bad .qr-select {
  border-color: var(--qr-danger);
}

/* Django renders the errors it found server-side into this list; the script
   replaces it with .qr-error text. Both are styled the same so a refusal looks
   identical whether the form was posted or fetched. */
.qr-scope .qr-field .errorlist {
  margin: 0;
  padding: 0;
  list-style: none;
}
.qr-scope .qr-field .errorlist li {
  font-size: 11px;
  line-height: 17px;
  color: var(--qr-danger);
}

/* --------------------------------------------------------------------------
   The hidden-network switch. A real checkbox, sized up and given a label big
   enough to press on a phone — `WIFI:…;H:true;;` is the only thing it changes,
   and the old WiFi screen had no way to say it at all.
   -------------------------------------------------------------------------- */
.qr-scope .qr-toggle {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 9px 11px;
  border: 1px solid var(--qr-border);
  border-radius: var(--qr-radius-sm);
  background: var(--qr-surface-muted);
  cursor: pointer;
}

.qr-scope .qr-check {
  flex: none;
  width: 15px;
  height: 15px;
  margin: 2px 0 0;
  accent-color: var(--qr-brand);
  cursor: pointer;
}

.qr-scope .qr-toggle__text {
  display: grid;
  gap: 1px;
  min-width: 0;
}

.qr-scope .qr-toggle__text b {
  font-family: var(--qr-font-display);
  font-size: 12px;
  font-weight: 700;
  line-height: 18px;
  color: var(--qr-text);
}

.qr-scope .qr-toggle__text span {
  font-size: 11px;
  line-height: 17px;
  color: var(--qr-text-muted);
}
/* --------------------------------------------------------------------------
   The style row: two colours, a size, an error-correction level and an optional
   centre logo. Shared by all three generator tabs, and the reason the three
   forms all declare the same five fields.

   The colours were `#000000`/`#ffffff` in one old view, absent from another and
   unvalidated in the third; `clean_fill_color` now refuses anything that is not
   a hex triple, and `readability_notes` warns when the contrast between the two
   is too low to scan. A native `<input type="color">` can only ever produce a
   valid hex, so the picker and the validator agree by construction.
   -------------------------------------------------------------------------- */
.qr-scope .qr-style {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  gap: 12px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px dashed var(--qr-border-strong);
}

.qr-scope .qr-colour {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 9px 5px 5px;
  border: 1px solid var(--qr-border-strong);
  border-radius: var(--qr-radius-sm);
  background: var(--qr-surface);
}

.qr-scope .qr-swatch {
  flex: none;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--qr-border-strong);
  border-radius: 3px;
  background: none;
  cursor: pointer;
}

/* Chrome and Safari draw a padded box inside the control; without these two the
   chosen colour shows as a small square floating in white. */
.qr-scope .qr-swatch::-webkit-color-swatch-wrapper {
  padding: 0;
}

.qr-scope .qr-swatch::-webkit-color-swatch {
  border: none;
  border-radius: 2px;
}
.qr-scope .qr-swatch::-moz-color-swatch {
  border: none;
  border-radius: 2px;
}

.qr-scope .qr-colour__text {
  display: grid;
  gap: 0;
  min-width: 0;
}

.qr-scope .qr-colour__text b {
  font-family: var(--qr-font-display);
  font-size: 10px;
  font-weight: 700;
  line-height: 15px;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--qr-text-soft);
}

/* The hex the script writes back after every pick. Shown because the colour a
   poster is printed in is a value someone will want to write down or reuse, and
   because it is the exact string the server validated. */
.qr-scope .qr-colour__hex {
  font-family: var(--qr-font-mono);
  font-size: 11px;
  line-height: 16px;
  color: var(--qr-text-muted);
}

/* --------------------------------------------------------------------------
   The logo picker. Two of the old generators pasted a logo at a third of the
   code's width, which covers more modules than any error-correction level can
   recover — the code stopped scanning. `paste_logo` caps it at
   MAX_LOGO_FRACTION and `readability_notes` says so on the page.
   -------------------------------------------------------------------------- */
/* The one control in the style row that needs a whole line to itself: it carries
   a Browse button, the chosen file's name and a sentence about the size cap,
   none of which fit the 132px column the row's other four controls sit in. */
.qr-scope .qr-style .qr-logo {
  grid-column: 1 / -1;
}

.qr-scope .qr-file {
  width: 100%;
  min-width: 0;
  padding: 7px 9px;
  border: 1px dashed var(--qr-border-strong);
  border-radius: var(--qr-radius-sm);
  background: var(--qr-surface-muted);
  font-family: var(--qr-font-body);
  font-size: 11px;
  line-height: 18px;
  color: var(--qr-text-soft);
  cursor: pointer;
}
.qr-scope .qr-file::file-selector-button {
  margin-right: 9px;
  padding: 4px 10px;
  border: 1px solid var(--qr-border-strong);
  border-radius: 3px;
  background: var(--qr-surface);
  font-family: var(--qr-font-display);
  font-size: 11px;
  font-weight: 700;
  color: var(--qr-text-soft);
  cursor: pointer;
}

.qr-scope .qr-file:focus-visible {
  outline: 2px solid var(--qr-brand);
  outline-offset: 1px;
}

/* --------------------------------------------------------------------------
   Buttons. `.qr-btn`, not `.btn`: mdb.min.css uppercases `.btn` and gives it a
   drop shadow, and Bootstrap 4.3.1 loading afterwards resets neither, so a
   themed `.btn` here would arrive shouting.
   -------------------------------------------------------------------------- */
.qr-scope .qr-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 9px 17px;
  border: 1px solid var(--qr-brand);
  border-radius: var(--qr-radius-sm);
  background: var(--qr-brand);
  font-family: var(--qr-font-display);
  font-size: 12px;
  font-weight: 700;
  line-height: 18px;
  letter-spacing: 0.2px;
  text-transform: none;
  text-decoration: none;
  white-space: nowrap;
  color: #ffffff;
  cursor: pointer;
  box-shadow: none;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.qr-scope .qr-btn i {
  font-size: 13px;
}

.qr-scope .qr-btn:hover,
.qr-scope .qr-btn:focus {
  border-color: var(--qr-brand-hover);
  background: var(--qr-brand-hover);
  color: #ffffff;
  text-decoration: none;
}

.qr-scope .qr-btn:focus-visible {
  outline: 2px solid var(--qr-brand);
  outline-offset: 2px;
}

.qr-scope .qr-btn.is-ghost {
  border-color: var(--qr-border-strong);
  background: var(--qr-surface);
  color: var(--qr-text-soft);
}

.qr-scope .qr-btn.is-ghost:hover,
.qr-scope .qr-btn.is-ghost:focus {
  border-color: var(--qr-brand);
  background: var(--qr-brand-bg);
  color: var(--qr-brand);
}

.qr-scope .qr-btn.is-small {
  padding: 6px 11px;
  font-size: 11px;
  line-height: 16px;
}

.qr-scope .qr-btn.is-small i {
  font-size: 12px;
}

/* Disabled covers three states: a build already in flight, a cooldown the script
   is counting down, and the two download buttons before there is a code to
   download. All three are the same answer to the visitor — not yet. */
.qr-scope .qr-btn:disabled,
.qr-scope .qr-btn[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
}
.qr-scope .qr-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 16px;
}

/* --------------------------------------------------------------------------
   The preview. Sticky on a wide screen so the code stays beside the form while
   the form is being edited — the whole reason this is one screen and not four.
   `top` clears the theme's fixed header; below 1100px it un-sticks, because a
   stuck panel in a single column just eats the viewport.
   -------------------------------------------------------------------------- */
.qr-scope .qr-side {
  position: sticky;
  top: 92px;
}

.qr-scope .qr-preview {
  display: grid;
  gap: 12px;
  justify-items: center;
  padding: 18px 16px;
}

/* The chequerboard is the point: a white-on-white code, or one whose background
   was left transparent, is invisible against the card and looks like a failed
   build. Against squares it reads as a code with a pale background. */
.qr-scope .qr-preview__frame {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 232px;
  padding: 14px;
  border: 1px solid var(--qr-border);
  border-radius: var(--qr-radius-sm);
  background-color: var(--qr-surface);
  background-image: linear-gradient(45deg, var(--qr-surface-sunken) 25%, transparent 25%),
    linear-gradient(-45deg, var(--qr-surface-sunken) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, var(--qr-surface-sunken) 75%),
    linear-gradient(-45deg, transparent 75%, var(--qr-surface-sunken) 75%);
  background-size: 14px 14px;
  background-position: 0 0, 0 7px, 7px -7px, -7px 0;
}
/* `image-rendering: pixelated` matters here. The PNG is drawn at the module size
   the picker asked for and then displayed at whatever width the column is;
   smooth scaling blurs the module edges, which is exactly the thing a phone
   camera needs to be crisp. Nearest-neighbour keeps the squares square. */
.qr-scope .qr-preview__img {
  display: block;
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: 320px;
  image-rendering: pixelated;
}

.qr-scope .qr-preview__img[hidden] {
  display: none;
}

.qr-scope .qr-empty {
  display: grid;
  gap: 6px;
  justify-items: center;
  padding: 22px 10px;
  text-align: center;
}

.qr-scope .qr-empty i {
  font-size: 26px;
  color: var(--qr-text-muted);
}

.qr-scope .qr-empty p {
  margin: 0;
  max-width: 36ch;
  font-size: 12px;
  line-height: 19px;
  color: var(--qr-text-muted) !important;
}

.qr-scope .qr-empty[hidden] {
  display: none;
}

/* The three dots that run while the fetch is out. Not a spinner image and not a
   library — three spans and one keyframe, so nothing is requested. */
.qr-scope .qr-wait {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--qr-text-muted);
}
.qr-scope .qr-wait[hidden] {
  display: none;
}

.qr-scope .qr-dots {
  display: inline-flex;
  gap: 3px;
}

.qr-scope .qr-dots span {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--qr-brand);
  animation: qr-bounce 1s ease-in-out infinite;
}

.qr-scope .qr-dots span:nth-child(2) {
  animation-delay: 0.15s;
}

.qr-scope .qr-dots span:nth-child(3) {
  animation-delay: 0.3s;
}

@keyframes qr-bounce {
  0%,
  70%,
  100% {
    opacity: 0.3;
    transform: translateY(0);
  }
  35% {
    opacity: 1;
    transform: translateY(-3px);
  }
}

/* --------------------------------------------------------------------------
   The facts under the code: version, module count, pixel size, payload bytes and
   error-correction level. All five come back from `build()` and none of them were
   shown before — the old screens handed over an <img> and nothing else, so
   "why won't this scan" had no answer on the page.
   -------------------------------------------------------------------------- */
.qr-scope .qr-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(74px, 1fr));
  gap: 1px;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 1px solid var(--qr-border);
  border-radius: var(--qr-radius-sm);
  background: var(--qr-border);
  overflow: hidden;
}
.qr-scope .qr-facts > div {
  display: grid;
  gap: 1px;
  padding: 8px 10px;
  background: var(--qr-surface-muted);
  text-align: center;
}

.qr-scope .qr-facts dt {
  font-family: var(--qr-font-display);
  font-size: 9px;
  font-weight: 700;
  line-height: 14px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--qr-text-muted);
}

.qr-scope .qr-facts dd {
  margin: 0;
  font-family: var(--qr-font-mono);
  font-size: 12px;
  line-height: 18px;
  color: var(--qr-text);
}

.qr-scope .qr-facts[hidden] {
  display: none;
}

/* The readability warnings. `readability_notes` returns a list, and each entry is
   a sentence about one specific risk — pale ink, a logo near the cap, a payload
   dense enough that a phone will struggle at poster distance. They are warnings,
   never refusals: the code still built, and only the person printing it knows how
   big it will be. */
.qr-scope .qr-notes {
  display: grid;
  gap: 6px;
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
}

.qr-scope .qr-notes:empty {
  display: none;
}
.qr-scope .qr-notes li {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  padding: 8px 11px;
  border: 1px solid var(--qr-warn-border);
  border-radius: var(--qr-radius-sm);
  background: var(--qr-warn-bg);
  font-size: 11px;
  line-height: 17px;
  color: var(--qr-warn);
}

.qr-scope .qr-notes li i {
  flex: none;
  margin-top: 2px;
  font-size: 11px;
}

/* --------------------------------------------------------------------------
   The two downloads. PNG for anything printed or pasted into a document, SVG for
   anything that will be resized — a sign, a banner, a business card sent to a
   printer. The old screens offered the PNG only, and offered it as a link to a
   file the view had written into MEDIA_ROOT: every code ever generated stayed on
   disk. Both of these are data URIs built in the browser from the JSON, so
   nothing is stored server-side at all.
   -------------------------------------------------------------------------- */
.qr-scope .qr-downloads {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
  width: 100%;
}

/* The payload readout under the preview: exactly what went into the code, in the
   same monospace box a decoded result is shown in — the two halves of this screen
   should present the same string the same way. The width is restated because
   .qr-preview centres its children at their content size, and a short payload
   ("hi") would otherwise be a thumbnail-sized box floating mid-column. */
.qr-scope .qr-preview .qr-result__text {
  width: 100%;
}

/* --------------------------------------------------------------------------
   The decode side. One target for three ways in — a chosen file, a dropped file
   and a pasted screenshot — because all three post to the same endpoint and the
   answer must not depend on how the picture arrived.
   -------------------------------------------------------------------------- */
.qr-scope .qr-drop {
  display: grid;
  gap: 9px;
  justify-items: center;
  padding: 26px 18px;
  border: 2px dashed var(--qr-border-strong);
  border-radius: var(--qr-radius-md);
  background: var(--qr-surface-muted);
  text-align: center;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.qr-scope .qr-drop.is-over {
  border-color: var(--qr-brand);
  background: var(--qr-brand-bg);
}

.qr-scope .qr-drop i {
  font-size: 24px;
  color: var(--qr-text-muted);
}

.qr-scope .qr-drop.is-over i {
  color: var(--qr-brand);
}

.qr-scope .qr-drop b {
  font-family: var(--qr-font-display);
  font-size: 13px;
  font-weight: 700;
  line-height: 19px;
  color: var(--qr-text);
}

.qr-scope .qr-drop p {
  margin: 0;
  max-width: 44ch;
  font-size: 11px;
  line-height: 17px;
  color: var(--qr-text-muted) !important;
}

/* The real <input type="file"> the drop zone and the Choose button both drive. It
   is off-screen rather than `display: none` so it stays keyboard-reachable and so
   the browser's own file dialog still opens from it. */
.qr-scope .qr-drop input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

.qr-scope .qr-drop__pick {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
}
/* --------------------------------------------------------------------------
   The camera. `getUserMedia` needs a secure context, so this whole block is
   revealed by the script only after it has confirmed the API exists — on plain
   HTTP, or in a browser without it, the visitor sees the file and paste routes
   and no dead Start button.

   The <video> is mirrored on neither axis: a QR code is not a face, and a
   mirrored preview makes aiming at one printed on a wall harder, not easier.
   -------------------------------------------------------------------------- */
.qr-scope .qr-camera {
  display: grid;
  gap: 10px;
  margin-top: 14px;
}

.qr-scope .qr-camera[hidden] {
  display: none;
}

.qr-scope .qr-camera__stage {
  position: relative;
  width: 100%;
  border: 1px solid var(--qr-border-strong);
  border-radius: var(--qr-radius-sm);
  background: #101114;
  overflow: hidden;
  aspect-ratio: 4 / 3;
}

.qr-scope .qr-camera__stage[hidden] {
  display: none;
}

.qr-scope .qr-camera__video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Where to hold the code. Drawn with two box-shadows rather than a border on a
   positioned overlay, so nothing in this file needs a z-index. */
.qr-scope .qr-camera__reticle {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 58%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border: 2px solid rgba(255, 255, 255, 0.85);
  border-radius: 6px;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.28);
  pointer-events: none;
}
.qr-scope .qr-camera__row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* The canvas the frame is drawn into before it is posted. Never shown — it is a
   scratch surface, not a preview. */
.qr-scope .qr-camera__canvas {
  display: none;
}

/* --------------------------------------------------------------------------
   Results. A list, not a single value: `decode_image` returns every symbol it
   found, because a photographed page can hold several and the old view read
   `decoded_objects[0]` and silently discarded the rest.
   -------------------------------------------------------------------------- */
.qr-scope .qr-results {
  display: grid;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.qr-scope .qr-results:empty {
  display: none;
}

.qr-scope .qr-result {
  display: grid;
  gap: 8px;
  padding: 12px 14px;
  border: 1px solid var(--qr-border);
  border-left: 3px solid var(--qr-brand);
  border-radius: var(--qr-radius-sm);
  background: var(--qr-surface);
}

.qr-scope .qr-result__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 9px;
  flex-wrap: wrap;
}
/* What `classify` decided this content is: a link, a WiFi network, an email, a
   phone number, plain text. Shown as a chip because it changes what the visitor
   can do with it — a link gets an Open button, a network gets its parts listed. */
.qr-scope .qr-result__kind {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border: 1px solid var(--qr-border-strong);
  border-radius: 999px;
  background: var(--qr-surface-sunken);
  font-family: var(--qr-font-display);
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--qr-text-soft);
}

.qr-scope .qr-result__kind i {
  font-size: 11px;
  color: var(--qr-brand);
}

/* `word-break: break-all` and not `break-word`: the content is frequently one
   unbroken token — a URL with a signed query, a base64 payload — and
   `break-word` leaves it overflowing its own box. */
.qr-scope .qr-result__text {
  margin: 0;
  padding: 9px 11px;
  border: 1px solid var(--qr-border);
  border-radius: var(--qr-radius-sm);
  background: var(--qr-surface-sunken);
  font-family: var(--qr-font-mono);
  font-size: 12px;
  line-height: 19px;
  word-break: break-all;
  white-space: pre-wrap;
  color: var(--qr-text) !important;
}

.qr-scope .qr-result__row {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
}
/* A WiFi code broken back into its parts by `parse_wifi`, which is the only
   useful thing to do with `WIFI:T:WPA;S:Net\;1;P:p\:w;H:true;;` — the raw string
   is exactly what a person cannot read. The password is shown, because the
   visitor is holding the poster it was printed on. */
.qr-scope .qr-wifi {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 3px 12px;
  margin: 0;
}

.qr-scope .qr-wifi dt {
  font-family: var(--qr-font-display);
  font-size: 10px;
  font-weight: 700;
  line-height: 18px;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--qr-text-muted);
}

.qr-scope .qr-wifi dd {
  margin: 0;
  font-family: var(--qr-font-mono);
  font-size: 12px;
  line-height: 18px;
  word-break: break-all;
  color: var(--qr-text);
}

/* --------------------------------------------------------------------------
   The clipboard fallback. `navigator.clipboard` needs a secure context and a
   permission; where it is missing the script drops the text into this and calls
   the old `execCommand("copy")` on it. Off-screen rather than hidden, because a
   `display: none` element cannot be selected and the copy silently does nothing —
   which is what the four old `copyText()` handlers did on every browser that
   refused them, with no message either way.
   -------------------------------------------------------------------------- */
.qr-scope .qr-offscreen {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  opacity: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
}
/* --------------------------------------------------------------------------
   The rules, stated on the page. `qrcodes.limits()` is rendered into this so the
   ceilings a visitor can hit are readable before they hit one, rather than
   arriving as a refusal out of nowhere. Every number in it comes from settings.
   -------------------------------------------------------------------------- */
.qr-scope .qr-limits {
  display: flex;
  gap: 6px 18px;
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
  list-style: none;
}

.qr-scope .qr-limits li {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  line-height: 17px;
  color: var(--qr-text-muted);
}

.qr-scope .qr-limits li i {
  font-size: 11px;
  color: var(--qr-text-muted);
}

.qr-scope .qr-limits b {
  font-family: var(--qr-font-mono);
  font-weight: 400;
  color: var(--qr-text-soft);
}

/* --------------------------------------------------------------------------
   Reduced motion. The dots are the only animation in the file; a visitor who has
   asked for less of it still needs to know a request is out, so they stop moving
   and stay visible rather than disappearing.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .qr-scope .qr-dots span {
    animation: none;
    opacity: 0.75;
  }

  .qr-scope .qr-btn,
  .qr-scope .qr-tab,
  .qr-scope .qr-drop,
  .qr-scope .qr-input,
  .qr-scope .qr-textarea,
  .qr-scope .qr-select {
    transition: none;
  }
}
/* --------------------------------------------------------------------------
   Narrow screens. One column below 1100px, and the preview stops being sticky —
   in a single column a stuck panel covers the form it is supposed to accompany.
   -------------------------------------------------------------------------- */
@media (max-width: 1100px) {
  .qr-scope .qr-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .qr-scope .qr-side {
    position: static;
    top: auto;
  }
}

@media (max-width: 768px) {
  .qr-scope {
    padding-bottom: 24px;
  }

  /* !important, and not because of the theme: the desktop .qr-title above is
     itself !important — it has to be, against cs-foodbakery-plugin.css's
     `h1 { font: 700 Normal 30px/36px … !important }` — and a plain declaration
     here would lose to it rather than to the theme. */
  .qr-scope .qr-title {
    font-size: 18px !important;
    line-height: 25px !important;
  }

  .qr-scope .qr-title i {
    font-size: 17px;
  }

  .qr-scope .qr-head__row {
    flex-direction: column;
    align-items: stretch;
  }

  .qr-scope .qr-fields--pair {
    grid-template-columns: minmax(0, 1fr);
  }

  .qr-scope .qr-tab {
    min-width: 0;
    padding: 8px 10px;
  }

  /* The tab labels go, the icons stay: four words do not fit across a phone and
     the strip would scroll sideways. `aria-label` on each button carries the
     name for anyone who cannot see the icon. */
  .qr-scope .qr-tab span {
    display: none;
  }
}
@media (max-width: 520px) {
  .qr-scope .qr-style {
    grid-template-columns: minmax(0, 1fr);
  }

  .qr-scope .qr-actions .qr-btn,
  .qr-scope .qr-downloads .qr-btn {
    flex: 1 1 100%;
  }

  .qr-scope .qr-facts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* --------------------------------------------------------------------------
   Print. Somebody will press Ctrl+P on this page rather than download the PNG,
   and what they want on the paper is the code — not the tab strip, not the form
   they filled in, not the buttons. The chequerboard goes too: printed, it is grey
   squares behind a code that has to be clean to scan.
   -------------------------------------------------------------------------- */
@media print {
  .qr-scope .qr-tabs,
  .qr-scope .qr-actions,
  .qr-scope .qr-downloads,
  .qr-scope .qr-status,
  .qr-scope .qr-fallback,
  .qr-scope .qr-camera,
  .qr-scope .qr-drop,
  .qr-scope .qr-limits,
  .qr-scope .qr-head__aside,
  .qr-scope .qr-card__foot {
    display: none !important;
  }

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

  .qr-scope .qr-card,
  .qr-scope .qr-preview__frame {
    border-color: #d9d9d9;
    background-image: none;
  }

  .qr-scope .qr-preview__img {
    max-height: none;
  }
}
