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; } }