diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000000000000000000000000000000000000..8392d159f2e71825027fd6af5863d218b8b5f9cb
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use flake
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..764c6e5891095933f6385c999636bc2bf4bd5115
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.direnv
+/dist
+node_modules
+.parcel-cache
\ No newline at end of file
diff --git a/app.js b/app.js
index 84cf90c4c54d723ddf421d16941ad1e304da5bf0..881bcf43d6bc6604b251960dc5125e2d2fd36c94 100644
--- a/app.js
+++ b/app.js
@@ -5,12 +5,28 @@ var canvasElement = document.getElementById("canvas");
 var canvas = canvasElement.getContext("2d");
 var loadingMessage = document.getElementById("loadingMessage");
 var outputContainer = document.getElementById("output");
-var outputMessage = document.getElementById("outputMessage");
 var outputData = document.getElementById("outputData");
 
 const latencyDiv = document.getElementById("latency");
 const latencyScan = document.getElementById("scan");
 
+let datas = {};
+
+function renderData() {
+  let html = "<table><tr><th>Data</th><th>Count</th>";
+  const keys = Object.keys(datas);
+  for (let i = 0; i < keys.length; i++) {
+    const key = keys[i];
+    const val = datas[key];
+
+    html += `<tr><td>${key}</td><td>${val}</td></tr>`;
+  }
+
+  html += "</table>";
+
+  outputData.innerHTML = html;
+}
+
 function drawLine(begin, end, color) {
   canvas.beginPath();
   canvas.moveTo(begin.x, begin.y);
@@ -56,7 +72,7 @@ function tick() {
 
     latencyDiv.innerText = `Frame latency: ${delta}ms`;
 
-    if (code) {
+    if (code?.data.length > 0) {
       latencyScan.innerText = `Scan latency: ${delta}ms`;
 
       drawLine(
@@ -79,12 +95,11 @@ function tick() {
         code.location.topLeftCorner,
         "#FF3B58",
       );
-      outputMessage.hidden = true;
-      outputData.parentElement.hidden = false;
-      outputData.innerText = code.data;
-    } else {
-      outputMessage.hidden = false;
-      outputData.parentElement.hidden = true;
+
+      datas[code.data] ||= 0;
+      datas[code.data] += 1;
+
+      renderData();
     }
   }
   requestAnimationFrame(tick);
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000000000000000000000000000000000000..e8892eb4557e56ce39709d1ed62b59bafc397bec
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,62 @@
+{
+  "nodes": {
+    "flake-utils": {
+      "inputs": {
+        "systems": "systems"
+      },
+      "locked": {
+        "lastModified": 1731533236,
+        "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "ref": "main",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1741513245,
+        "narHash": "sha256-7rTAMNTY1xoBwz0h7ZMtEcd8LELk9R5TzBPoHuhNSCk=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "e3e32b642a31e6714ec1b712de8c91a3352ce7e1",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "flake-utils": "flake-utils",
+        "nixpkgs": "nixpkgs"
+      }
+    },
+    "systems": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000000000000000000000000000000000000..342869c7965e91e1153247c20547d95bb1ac4fa1
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,25 @@
+{
+  inputs = {
+    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+    flake-utils.url = "github:numtide/flake-utils?ref=main";
+  };
+
+  outputs =
+    inputs:
+    inputs.flake-utils.lib.eachDefaultSystem (
+      system:
+      let
+        pkgs = inputs.nixpkgs.legacyPackages.${system};
+      in
+        {
+          devShells.default = pkgs.mkShell {
+            packages = (
+              with pkgs;
+              [
+                yarn
+              ]
+            );
+          };
+        }
+    );
+}
diff --git a/index.html b/index.html
index b0e517814911e8f8deb11120d1ca8240ac3d6ee9..cdbe00db4749e00c085a5acbb2061ce0ecf9b51f 100644
--- a/index.html
+++ b/index.html
@@ -6,10 +6,9 @@
     <div id="loadingMessage">🎥 Unable to access video stream (please make sure you have a webcam enabled)</div>
     <canvas id="canvas" hidden></canvas>
     <div id="output" hidden>
-      <div id="outputMessage">No QR code detected.</div>
-      <div hidden><b>Data:</b> <span id="outputData"></span></div>
       <div id="latency"></div>
       <div id="scan"></div>
+      <div><span id="outputData"></span></div>
     </div>
   </body>
   <head>