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

badge.css (10639B)


      1 /* badge.virtualshack.io — shared shell
      2  *
      3  * Palette rule: parent-site chrome
      4  * from virtualshack.io (hugo-coder dark: #212121 / #dadada / #42a5f5) with the
      5  * badge's mint #3ddc97 kept as the sub-brand accent. The `//` heading motif
      6  * mirrors the device OLED (// home, // storage, // options) — that through-line
      7  * from screen → page → docs is the brand, keep it.
      8  *
      9  * Parent site is colorscheme-auto, so we honour prefers-color-scheme too.
     10  */
     11 
     12 :root {
     13   --bg: #212121;          /* virtualshack.io dark bg */
     14   --fg: #dadada;
     15   --link: #22d3ee;        /* virtualshack accents (STYLE.md): cyan links, */
     16   --hover: #ff79c6;       /* pink hover/focus, */
     17   --focus: #9b8cff;       /* blue-purple focus ring */
     18   --accent: #3ddc97;      /* badge sub-brand mint */
     19   --panel: #2b2b2b;
     20   --rule: #424242;
     21   --code-bg: #4f4f4f;
     22   --muted: #8b949e;
     23   --warn: #ffb454;
     24   --err: #ff5370;
     25 }
     26 
     27 @media (prefers-color-scheme: light) {
     28   :root {
     29     --bg: #fafafa;
     30     --fg: #212121;
     31     --link: #0e7490;
     32     --hover: #c2185b;
     33     --focus: #5b4fe0;
     34     --accent: #0a7f50;    /* mint for white: 4.8:1 (#0f9d63 was 3.3:1, failed AA) */
     35     --panel: #efefef;
     36     --rule: #d5d5d5;
     37     --code-bg: #e4e4e4;
     38     --muted: #5c6570;
     39     --warn: #a05e00;
     40     --err: #c62828;
     41   }
     42 }
     43 
     44 * { box-sizing: border-box; }
     45 
     46 body {
     47   margin: 0;
     48   background: var(--bg);
     49   color: var(--fg);
     50   font: 15px/1.65 ui-monospace, Menlo, Consolas, monospace;
     51   -webkit-text-size-adjust: 100%;
     52 }
     53 
     54 .wrapper { max-width: 780px; margin: 0 auto; padding: 32px 20px 64px; }
     55 
     56 /* Links are never underlined (STYLE.md): hover/focus is the colour change, keyboard focus
     57    also gets the ring. */
     58 a { color: var(--link); text-decoration: none; transition: color .15s ease; }
     59 a:hover, a:focus { color: var(--hover); text-decoration: none; }
     60 a:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 2px; }
     61 
     62 h1, h2, h3 { line-height: 1.3; font-weight: 700; }
     63 h1 { font-size: 26px; margin: 0 0 6px; color: var(--accent); letter-spacing: .5px; }
     64 h2 { font-size: 18px; margin: 40px 0 12px; color: var(--accent); scroll-margin-top: 16px; }
     65 h3 { font-size: 15px; margin: 24px 0 8px; color: var(--fg); }
     66 
     67 p, li { margin: 0 0 12px; }
     68 ul, ol { padding-left: 22px; }
     69 
     70 code {
     71   background: var(--code-bg);
     72   color: var(--fg);
     73   padding: 1px 5px;
     74   border-radius: 3px;
     75   font-size: .92em;
     76 }
     77 
     78 pre {
     79   background: var(--panel);
     80   border: 1px solid var(--rule);
     81   border-radius: 8px;
     82   padding: 14px 16px;
     83   overflow-x: auto;          /* diagrams scroll themselves; the page never does */
     84   font-size: 13px;
     85   line-height: 1.5;
     86 }
     87 pre code { background: none; padding: 0; }
     88 
     89 .tagline { color: var(--muted); margin: 0 0 4px; }
     90 .muted { color: var(--muted); }
     91 .small { font-size: 13px; }
     92 
     93 hr { border: 0; border-top: 1px solid var(--rule); margin: 36px 0; }
     94 
     95 /* callouts — one shape, three severities */
     96 .note {
     97   border-left: 3px solid var(--link);
     98   background: var(--panel);
     99   padding: 12px 16px;
    100   border-radius: 0 8px 8px 0;
    101   margin: 16px 0;
    102 }
    103 .note.warn { border-left-color: var(--warn); }
    104 .note.stop { border-left-color: var(--err); }
    105 .note :last-child { margin-bottom: 0; }
    106 .note b { color: var(--fg); }
    107 /* .log is a density modifier, not a fourth severity — the changelog is a list of many
    108    short items, and the default 12px gap between them makes the callout enormous. */
    109 .note.log ul { margin: 6px 0 0; }
    110 .note.log li { margin: 0 0 6px; }
    111 
    112 /* the big call-to-action to the provisioning app */
    113 .cta {
    114   display: inline-block;
    115   margin: 8px 0 4px;
    116   padding: 12px 20px;
    117   background: #3ddc97;        /* bright mint in BOTH themes: the dark label is 10.8:1 on it,
    118                                   but only 3.8:1 on the light-mode TEXT mint (#0a7f50) */
    119   color: #08110b;
    120   font-weight: 700;
    121   border-radius: 8px;
    122 }
    123 .cta:hover, .cta:focus { color: #08110b; text-decoration: none; opacity: .9; }  /* keep the dark label: a:hover would turn it pink */
    124 
    125 /* Non-link CTA while the setup app isn't hosted yet: same shape, but muted and inert. */
    126 .cta.disabled {
    127   cursor: default;
    128   background: var(--panel);
    129   color: var(--muted);
    130   border: 1px solid var(--rule);
    131 }
    132 .cta .soon { font-weight: 400; font-size: 13px; opacity: .85; }
    133 
    134 /* Back-to-top link above the footer. */
    135 .totop {
    136   display: inline-block;
    137   margin: 32px 0 8px;
    138   font-size: 13px;
    139   color: var(--muted);
    140 }
    141 .totop:hover, .totop:focus { color: var(--accent); text-decoration: none; }
    142 
    143 /* table of contents */
    144 .toc { border: 1px solid var(--rule); border-radius: 8px; padding: 14px 16px 6px; margin: 24px 0; }
    145 .toc ul { list-style: none; padding: 0; margin: 0; columns: 2; column-gap: 24px; }
    146 .toc li { margin: 0 0 6px; }
    147 @media (max-width: 520px) { .toc ul { columns: 1; } }
    148 
    149 /* ── photos ──────────────────────────────────────────────────────────────────
    150    Usage:
    151      <figure>
    152        <img src="assets/photo-badge.jpg" alt="a badge, screen lit, showing HOME"
    153             width="1600" height="1200" loading="lazy" decoding="async">
    154        <figcaption>the whole point</figcaption>
    155      </figure>
    156    Always set width/height (the real pixel dimensions) — the browser reserves the
    157    space before the file loads, so nothing jumps as photos stream in on con wifi.
    158    loading="lazy" on everything below the fold; NOT on the first/hero image, which
    159    should load immediately. A faint border keeps a dark photo from bleeding into
    160    the dark page background. */
    161 img { max-width: 100%; height: auto; display: block; }
    162 
    163 figure {
    164   margin: 24px 0;
    165 }
    166 figure img {
    167   width: 100%;
    168   border: 1px solid var(--rule);
    169   border-radius: 8px;
    170   background: var(--panel);      /* shows while the image is still decoding */
    171 }
    172 figcaption {
    173   margin-top: 8px;
    174   font-size: 13px;
    175   color: var(--muted);
    176   text-align: center;
    177 }
    178 
    179 /* Clickable photo: small inline, full resolution in a new tab.
    180    The <img> points at a ~900px thumbnail (assets/img/thumbs/), the <a> at the original —
    181    so the page loads ~0.5MB instead of ~1.7MB, which matters on con wifi, and the detail
    182    is still one click away. */
    183 a.photo {
    184   display: block;
    185   max-width: 420px;
    186   margin: 20px auto;
    187   text-decoration: none;
    188 }
    189 a.photo img {
    190   width: 100%;
    191   height: auto;
    192   border: 1px solid var(--rule);
    193   border-radius: 8px;
    194   background: var(--panel);           /* holds the space while decoding */
    195   transition: border-color .15s ease, opacity .15s ease;
    196 }
    197 a.photo:hover img,
    198 a.photo:focus-visible img { border-color: var(--accent); opacity: .9; }
    199 a.photo:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
    200 
    201 /* Persistent hint — without it nobody discovers the images are clickable. */
    202 a.photo::after {
    203   content: "click to enlarge ⤢";
    204   display: block;
    205   margin-top: 6px;
    206   font-size: 12px;
    207   color: var(--muted);
    208   text-align: center;
    209 }
    210 a.photo:hover::after,
    211 a.photo:focus-visible::after { color: var(--accent); }
    212 
    213 /* Portrait shots pair up nicely; .shots handles the collapse on narrow screens. */
    214 .shots a.photo { max-width: none; margin: 0; }
    215 
    216 /* Two or more photos side by side; collapses to one column on a phone. */
    217 .shots {
    218   display: grid;
    219   grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    220   gap: 16px;
    221   margin: 24px 0;
    222 }
    223 .shots figure { margin: 0; }
    224 
    225 dl { margin: 0; }
    226 dt { color: var(--accent); margin-top: 16px; }
    227 dd { margin: 4px 0 0; }
    228 
    229 footer {
    230   margin-top: 56px;
    231   padding-top: 16px;
    232   border-top: 1px solid var(--rule);
    233   color: var(--muted);
    234   font-size: 13px;
    235 }
    236 
    237 /* offline/browser banners injected by JS */
    238 #banner { display: none; }
    239 #banner.show { display: block; }
    240 
    241 /* ── gallery (gallery/index.html) ────────────────────────────────────────────
    242    Tiles are uniform 4:3 crops of the thumbnail (object-fit), so a mix of portrait and
    243    landscape shots still lines up; the full, uncropped photo is one click away. Grid and
    244    <img> markup are generated from the gallery manifest. */
    245 .gallery {
    246   display: grid;
    247   grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    248   gap: 12px;
    249   margin: 24px 0;
    250 }
    251 @media (max-width: 520px) { .gallery { grid-template-columns: 1fr 1fr; gap: 8px; } }
    252 .gallery figure { margin: 0; }
    253 .gallery a { display: block; border-radius: 8px; }
    254 .gallery img {
    255   width: 100%;
    256   aspect-ratio: 4 / 3;
    257   object-fit: cover;
    258   border: 1px solid var(--rule);
    259   border-radius: 8px;
    260   background: var(--panel);
    261   transition: border-color .15s ease, opacity .15s ease;
    262 }
    263 .gallery a:hover img, .gallery a:focus-visible img { border-color: var(--accent); opacity: .9; }
    264 .gallery a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
    265 .gallery figcaption { margin-top: 6px; font-size: 12px; color: var(--muted); }
    266 
    267 /* Lightbox: a full-viewport <dialog>. Always dark — it's a photo viewer, not page chrome.
    268    The figure ignores pointer events so a click on empty space reaches the dialog (= close). */
    269 dialog.lightbox {
    270   width: 100vw; height: 100vh; max-width: none; max-height: none;
    271   margin: 0; padding: 56px 16px 24px; border: 0;
    272   background: rgba(10, 10, 10, .97);
    273   color: #dadada;
    274 }
    275 dialog.lightbox::backdrop { background: rgba(0, 0, 0, .6); }
    276 dialog.lightbox figure {
    277   margin: 0; height: 100%;
    278   display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
    279   pointer-events: none;
    280 }
    281 dialog.lightbox img {
    282   width: auto; height: auto; max-width: 100%; max-height: calc(100vh - 130px);
    283   border-radius: 6px; pointer-events: auto;
    284 }
    285 dialog.lightbox figcaption { font-size: 13px; color: #bdbdbd; text-align: center; }
    286 .lightbox button {
    287   position: fixed;
    288   min-width: 44px; min-height: 44px;               /* thumb-sized */
    289   font: 20px/1 ui-monospace, Menlo, Consolas, monospace;
    290   color: #dadada; background: rgba(43, 43, 43, .85);
    291   border: 1px solid #424242; border-radius: 8px; cursor: pointer;
    292 }
    293 .lightbox button:hover, .lightbox button:focus-visible { border-color: #ff79c6; color: #ff79c6; outline: none; }
    294 .lightbox .close { top: 8px; right: 12px; }
    295 .lightbox .prev { left: 12px; top: 50%; transform: translateY(-50%); }
    296 .lightbox .next { right: 12px; top: 50%; transform: translateY(-50%); }
    297 .lightbox .count { position: fixed; top: 20px; left: 16px; font-size: 13px; color: #8b949e; }
    298 
    299 /* The badge page's // photos section: the gallery's first row (three tiles). On a phone the
    300    grid is two columns, so the third would sit alone on a second row: show two instead. */
    301 @media (max-width: 520px) { .gallery-preview figure:nth-child(n+3) { display: none; } }