Sorry for the delay; it’s a 3-day weekend here in the U.S., so doing family stuff. Happy holiday to everyone who celebrates it.
This is the QR code scanner I coded. The GIF isn’t very good because the buttons look blurry. It’s just to give you a general idea. It’s right in the code.

I don’t use the start, stop, and reset buttons because I do all that in a workflow. You can set it up the way you want. I also use a pop-up for my scanner.
You can change anything in your database using JS to Bubble. The code is built for that.
This does a lot more than the plugins I checked out:
It uses laser guidance to find the QR code.
It has a haptic thump when it finds a code. Android only, Apple doesn’t support it.
It uses a camera flash when it finds the code and chirps. Works on any type of phone.
Asks for camera access when the start scanning button is clicked. If they have denied it earlier, it automatically opens instructions based on the type of phone they’re using on how to reset it. The instructions for why my phone isn’t working are also dynamic based on the phone (Apple, Android).
The camera automatically opens to the rear camera.
To save on battery, the camera automatically stops when a code is scanned.
Has a fluid frame rate.
Probably missing a lot of things, trying to hurry up and post this.
Anyway, here is the code you can drop in an HTML element on your page, pop-up, or whatever you use. Change the colors, etc., also for your use case:
Start Camera
Stop
Refresh
<div class="tc-footer-links">
<button type="button" class="tc-info-link" onclick="showPrivacyInfo()">
<span class="tc-info-icon">i</span> Why do we need camera access?
</button>
<button type="button" class="tc-help-btn" onclick="showHelp()">
Trouble scanning? Tap for fix
</button>
</div>
#tc-wrap, #tc-wrap \* { box-sizing: border-box !important; -webkit-tap-highlight-color: transparent; }
#tc-wrap { width: 100%; max-width: 360px; margin: 0 auto; padding: 10px 10px 40px 10px; display: flex; flex-direction: column; align-items: center; gap: 15px; font-family: -apple-system, system-ui, sans-serif; position: relative; }
/\* Success Flash \*/
#tc-success-flash { position: absolute; inset: 0; background: white; z-index: 10; opacity: 0; pointer-events: none; transition: opacity 0.1s ease-out; }
.flash-active { opacity: 1 !important; }
/\* Modals \*/
.tc-modal { position: absolute; inset: 0; background: rgba(0,0,0,0.7); z-index: 100; display: none; align-items: center; justify-content: center; padding: 20px; border-radius: 32px; backdrop-filter: blur(8px); }
.tc-modal-content { background: white; width: 100%; border-radius: 24px; padding: 24px; text-align: left; box-shadow: 0 25px 50px rgba(0,0,0,0.5); }
.tc-modal-content h3 { margin: 0 0 15px 0; color: #111; font-size: 19px; text-align: center; }
.tc-modal-content p { color: #444; font-size: 14px; line-height: 1.6; margin-bottom: 15px; }
/\* Scanner Frame \*/
#tc-scan-wrap { position: relative; width: 100%; aspect-ratio: 1/1; background: #000; border-radius: 32px; border: 4px solid #222; overflow: hidden; transition: 0.4s ease; }
.tc-running #tc-scan-wrap { border-color: #ff6a00; box-shadow: 0 0 20px rgba(255,106,0,0.2); }
#tc-qr-reader { position: absolute; inset: 0; z-index: 1; }
#tc-qr-reader video { width: 100% !important; height: 100% !important; object-fit: cover !important; }
.tc-idle { position: absolute; inset: 0; z-index: 2; display: flex; align-items: center; justify-content: center; background: #111; }
.tc-idle-inner { background: rgba(255,255,255,0.1); padding: 10px 20px; border-radius: 100px; color: #fff; font-weight: 500; display: flex; align-items: center; gap: 8px; border: 1px solid rgba(255,255,255,0.1); backdrop-filter: blur(10px); font-size: 14px; }
.tc-dot { width: 8px; height: 8px; background: #ff6a00; border-radius: 50%; animation: tc-pulse 2s infinite; }
@keyframes tc-pulse { 0% { transform: scale(1); opacity: 1; } 70% { transform: scale(1.3); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }
.tc-overlay { position: absolute; inset: 30px; z-index: 3; pointer-events: none; opacity: 0; transition: 0.4s ease; }
.tc-running .tc-overlay { opacity: 1; }
.tc-corner { position: absolute; width: 30px; height: 30px; border: 3px solid #ff6a00; border-radius: 8px; }
.tl { top: 0; left: 0; border-right: 0; border-bottom: 0; }
.tr { top: 0; right: 0; border-left: 0; border-bottom: 0; }
.bl { bottom: 0; left: 0; border-right: 0; border-top: 0; }
.br { bottom: 0; right: 0; border-left: 0; border-top: 0; }
.tc-scanline { position: absolute; width: 100%; height: 2px; background: linear-gradient(90deg, transparent, #ff6a00, transparent); top: 0; animation: tc-scan 2.5s ease-in-out infinite; }
@keyframes tc-scan { 0% { top: 5%; opacity: 0; } 50% { opacity: 1; } 100% { top: 95%; opacity: 0; } }
/\* Controls \*/
.tc-controls { width: 100%; display: flex; flex-direction: column; gap: 10px; }
.tc-btn-group { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.tc-btn { border: none; border-radius: 18px; padding: 16px; font-weight: 600; font-size: 15px; cursor: pointer; transition: 0.2s; }
.tc-primary { background: #ff6a00; color: white; box-shadow: 0 4px 12px rgba(255, 106, 0, 0.2); }
.tc-stop { background: #222; color: #fff; }
.tc-secondary { background: #f4f4f4; color: #666; border: 1px solid #ddd; }
.tc-btn:disabled { opacity: 0.2; filter: grayscale(1); }
/\* ✅ Consolidated Centered Link Styling \*/
.tc-footer-links { display: flex; flex-direction: column; align-items: center; gap: 12px; margin-top: 10px; }
.tc-info-link { background: none; border: none; color: #777; font-size: 13px; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; padding: 5px; }
.tc-info-icon { width: 16px; height: 16px; background: #bbb; color: white; border-radius: 50%; font-size: 11px; font-weight: bold; line-height: 16px; display: inline-block; }
.tc-help-btn { background: none; border: none; color: #ff6a00; font-size: 12px; font-weight: 700; cursor: pointer; text-decoration: underline; padding: 5px; opacity: 0.9; }
.tc-help-btn:hover { opacity: 1; }