onlyn00bs-badge

OnlyN00bs: a DEF CON 34 friend-finder badge. ESP32 firmware, Web Bluetooth setup app, printable case
git clone https://git.virtualshack.io/onlyn00bs-badge.git
Log | Files | Refs | README | LICENSE

index.html (7301B)


      1 <!DOCTYPE html>
      2 <html lang="en">
      3 <head>
      4 <meta charset="utf-8">
      5 <meta name="viewport" content="width=device-width, initial-scale=1">
      6 <title>onlyn00bs badge · photos</title>
      7 <meta name="description" content="OnlyN00bs badges in the wild at DEF CON 34.">
      8 <meta name="color-scheme" content="dark light">
      9 <meta property="og:title" content="onlyn00bs badge · photos">
     10 <meta property="og:description" content="OnlyN00bs badges in the wild at DEF CON 34.">
     11 <meta property="og:type" content="website">
     12 <meta property="og:site_name" content="virtualshack">
     13 <meta property="og:url" content="https://badge.virtualshack.io/gallery/">
     14 <meta property="og:image" content="https://badge.virtualshack.io/assets/img/og-badge.jpg">
     15 <meta property="og:image:width" content="1200">
     16 <meta property="og:image:height" content="630">
     17 <meta name="twitter:card" content="summary_large_image">
     18 <meta name="twitter:image" content="https://badge.virtualshack.io/assets/img/og-badge.jpg">
     19 <!-- ?v= must match CACHE in ../sw.js — same rule as index.html -->
     20 <link rel="stylesheet" href="../assets/badge.css?v=39">
     21 <link rel="icon" type="image/svg+xml" href="../assets/icon.svg">
     22 <link rel="manifest" href="../manifest.webmanifest">
     23 <meta name="theme-color" content="#212121">
     24 </head>
     25 <body>
     26 <main class="wrapper" id="top">
     27 
     28   <h1>// photos</h1>
     29   <p class="tagline">onlyn00bs badges in the wild · def con 34</p>
     30   <p class="small muted"><a href="https://virtualshack.io/">virtualshack.io</a> /
     31     <a href="../">badge</a> / photos</p>
     32 
     33   <!-- The grid between these markers is generated from the gallery manifest. -->
     34   <!-- gallery:begin -->
     35   <div class="gallery">
     36     <figure>
     37       <a href="../assets/gallery/dc34-01.jpg" data-w="1600" data-h="1200"><img src="../assets/gallery/thumbs/dc34-01.jpg" alt="A green OnlyN00bs badge on the finder screen beside the official DEF CON 34 badge and a challenge coin, with a purple foot charm on the case"
     38            width="720" height="540" loading="lazy" decoding="async"></a>
     39     </figure>
     40     <figure>
     41       <a href="../assets/gallery/dc34-02.jpg" data-w="1234" data-h="929"><img src="../assets/gallery/thumbs/dc34-02.jpg" alt="A hand holding a purple OnlyN00bs badge on the finder screen, with a green badge on a lanyard behind it"
     42            width="720" height="542" loading="lazy" decoding="async"></a>
     43     </figure>
     44     <figure>
     45       <a href="../assets/gallery/dc34-03.jpg" data-w="929" data-h="1238"><img src="../assets/gallery/thumbs/dc34-03.jpg" alt="A lavender badge with foot and gummy-bear charms, its finder screen listing five nearby badges"
     46            width="540" height="720" loading="lazy" decoding="async"></a>
     47     </figure>
     48     <figure>
     49       <a href="../assets/gallery/dc34-04.jpg" data-w="1234" data-h="929"><img src="../assets/gallery/thumbs/dc34-04.jpg" alt="A purple and a blue OnlyN00bs badge side by side on a table, both on the home screen, decorated with charms"
     50            width="720" height="542" loading="lazy" decoding="async"></a>
     51     </figure>
     52     <figure>
     53       <a href="../assets/gallery/dc34-05.jpg" data-w="929" data-h="1238"><img src="../assets/gallery/thumbs/dc34-05.jpg" alt="A pastel badge on the home screen in front of a table covered in more badges and OnlyN00bs lanyards"
     54            width="540" height="720" loading="lazy" decoding="async"></a>
     55     </figure>
     56     <figure>
     57       <a href="../assets/gallery/dc34-06.jpg" data-w="929" data-h="1238"><img src="../assets/gallery/thumbs/dc34-06.jpg" alt="A small lavender badge on a table, its screen showing the avatar&#x27;s shower-time reminder"
     58            width="540" height="720" loading="lazy" decoding="async"></a>
     59     </figure>
     60     <figure>
     61       <a href="../assets/gallery/dc34-08.jpg" data-w="1234" data-h="929"><img src="../assets/gallery/thumbs/dc34-08.jpg" alt="Two hands holding a purple badge on the finder screen, with green badges behind it"
     62            width="720" height="542" loading="lazy" decoding="async"></a>
     63     </figure>
     64     <figure>
     65       <a href="../assets/gallery/dc34-09.jpg" data-w="1600" data-h="1200"><img src="../assets/gallery/thumbs/dc34-09.jpg" alt="A mint-green badge on the home screen with pink foot, red dinosaur and cat charms, on its lanyard"
     66            width="720" height="540" loading="lazy" decoding="async"></a>
     67     </figure>
     68   </div>
     69   <!-- gallery:end -->
     70 
     71   <p class="small muted">Photos CC BY-SA 4.0 · virtualtack.
     72     <a href="../#source">The badge is open source too.</a></p>
     73 
     74   <p style="text-align:center"><a class="totop" href="../">← back to the badge</a></p>
     75 
     76 </main>
     77 
     78 <dialog class="lightbox" id="lightbox" aria-label="photo viewer">
     79   <figure><img alt=""><figcaption></figcaption></figure>
     80   <button class="close" type="button" aria-label="close">✕</button>
     81   <button class="prev" type="button" aria-label="previous photo">←</button>
     82   <button class="next" type="button" aria-label="next photo">→</button>
     83   <span class="count"></span>
     84 </dialog>
     85 
     86 <script>
     87   // Lightbox. Progressive enhancement: every tile is a real link to the full image, so with
     88   // no JS (or no <dialog>) a click just opens the photo. Modifier-clicks are left alone so
     89   // "open in new tab" still works.
     90   (() => {
     91     const links = [...document.querySelectorAll(".gallery a")];
     92     const dlg = document.getElementById("lightbox");
     93     if (!links.length || !dlg.showModal) return;
     94     const img = dlg.querySelector("img");
     95     const cap = dlg.querySelector("figcaption");
     96     const count = dlg.querySelector(".count");
     97     let i = 0;
     98 
     99     const show = (n) => {
    100       i = (n + links.length) % links.length;
    101       const a = links[i];
    102       const fc = a.closest("figure").querySelector("figcaption");
    103       img.width = a.dataset.w;                 // reserve the shape before the bytes arrive
    104       img.height = a.dataset.h;
    105       img.src = a.href;
    106       img.alt = a.querySelector("img").alt;
    107       cap.textContent = fc ? fc.textContent : "";
    108       count.textContent = `${i + 1} / ${links.length}`;
    109       new Image().src = links[(i + 1) % links.length].href;   // warm the next one
    110     };
    111 
    112     links.forEach((a, n) => a.addEventListener("click", (e) => {
    113       if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button) return;
    114       e.preventDefault();
    115       show(n);
    116       dlg.showModal();
    117     }));
    118     dlg.querySelector(".prev").addEventListener("click", () => show(i - 1));
    119     dlg.querySelector(".next").addEventListener("click", () => show(i + 1));
    120     dlg.querySelector(".close").addEventListener("click", () => dlg.close());
    121     dlg.addEventListener("click", (e) => { if (e.target === dlg) dlg.close(); });  // empty space
    122     dlg.addEventListener("keydown", (e) => {
    123       if (e.key === "ArrowLeft") show(i - 1);
    124       if (e.key === "ArrowRight") show(i + 1);
    125     });
    126     let x0 = null;
    127     dlg.addEventListener("touchstart", (e) => { x0 = e.touches[0].clientX; }, { passive: true });
    128     dlg.addEventListener("touchend", (e) => {
    129       if (x0 === null) return;
    130       const dx = e.changedTouches[0].clientX - x0;
    131       x0 = null;
    132       if (Math.abs(dx) > 50) show(i + (dx < 0 ? 1 : -1));
    133     });
    134     dlg.addEventListener("close", () => img.removeAttribute("src"));
    135   })();
    136 
    137   if ("serviceWorker" in navigator) {
    138     addEventListener("load", () => navigator.serviceWorker.register("../sw.js").catch(() => {}));
    139   }
    140 </script>
    141 </body>
    142 </html>