@layer components {
  .qr-scanner {
    inline-size: min(90vw, 30rem);
    max-inline-size: 100%;
    outline: 3px solid transparent; /* coloured by the unusable-code flash */
  }

  /* Flashes the dialog border red when a scanned code can't be used. Uses
     outline (not border/box-shadow) to avoid layout shift and clashing with the
     panel border and .shadow; it follows the dialog's border-radius. */
  .qr-scanner--invalid {
    animation: qr-scanner-flash 0.6s ease;
  }

  @keyframes qr-scanner-flash {
    0%, 100% { outline-color: transparent; }
    25%, 75% { outline-color: var(--color-negative); }
  }

  @media (prefers-reduced-motion: reduce) {
    .qr-scanner--invalid {
      animation: none;
      outline-color: var(--color-negative);
    }
  }

  .qr-scanner__header {
    align-items: center;
    display: flex;
    gap: var(--inline-space);
    justify-content: space-between;
    margin-block-end: var(--block-space);
  }

  .qr-scanner__title {
    font-size: var(--text-large);
    margin: 0;
  }

  .qr-scanner__video {
    aspect-ratio: 1;
    background-color: var(--color-black);
    border-radius: 0.5em;
    inline-size: 100%;
    object-fit: cover;
  }

  .qr-scanner__hint {
    color: var(--color-ink-light);
    font-size: var(--text-small);
    margin-block-start: var(--block-space-half);
    text-align: center;
  }

  .qr-scanner__error {
    color: var(--color-negative);
    font-size: var(--text-small);
    font-weight: 600;
    margin-block-start: var(--block-space-half);
    text-align: center;
    transition: opacity 400ms ease; /* keep in sync with FADE_MS in qr_scanner_controller.js */
  }

  .qr-scanner__error--out {
    opacity: 0;
  }

  @media (prefers-reduced-motion: reduce) {
    .qr-scanner__error {
      transition: none;
    }
  }
}
