top secret

clean things up a bit

+102 -528
+5 -12
src/components/Footer.astro
··· 2 2 --- 3 3 4 4 <footer class="footer"> 5 - <div class="container footer__inner"> 6 - <p class="footer__copy"> 7 - <!-- Hubble is created and operated by <a href="https://microcosm.blue" target="_blank">microcosm</a>, with support from <a href="https://bsky.app" target="_blank">Bluesky</a>. --> 5 + <div class="container footer-inner"> 6 + <p class="footer-copy"> 7 + (footer) 8 8 </p> 9 9 </div> 10 10 </footer> ··· 15 15 margin-top: auto; 16 16 } 17 17 18 - .footer__inner { 18 + .footer-inner { 19 19 display: flex; 20 20 align-items: center; 21 21 justify-content: space-between; ··· 23 23 gap: var(--space-4); 24 24 } 25 25 26 - .footer__copy { 26 + .footer-copy { 27 27 font-size: 0.875rem; 28 28 color: var(--color-muted); 29 29 margin-bottom: 0; 30 30 max-width: none; 31 31 } 32 32 33 - .footer__links { 34 - display: flex; 35 - gap: var(--space-4); 36 - list-style: none; 37 - } 38 - 39 33 .footer a { 40 34 font-size: 0.875rem; 41 35 color: var(--color-muted); 42 36 text-decoration: none; 43 37 transition: color var(--transition); 44 38 } 45 - 46 39 .footer a:hover { 47 40 color: var(--color-accent2); 48 41 }
+15 -15
src/components/Nav.astro
··· 9 9 --- 10 10 11 11 <header class="nav"> 12 - <div class="container nav__inner"> 13 - <a class="nav__brand" href="/" aria-label="Hubble home"> 12 + <div class="container nav-inner"> 13 + <a class="nav-brand" href="/" aria-label="Hubble home"> 14 14 <img 15 15 src="/molten-ring.png" 16 16 height="64" 17 17 width="64" 18 - alt="(logo) the molten ring galaxy cluster, captured by hubble" 18 + alt="(logo) the molten ring galaxy cluster, captured by the hubble space telescope" 19 19 /> 20 - <span class="nav__brand-name">Hubble</span> 20 + <span class="nav-brand-name">Hubble</span> 21 21 </a> 22 22 23 23 <nav aria-label="Primary navigation"> 24 - <ul class="nav__links" role="list"> 24 + <ul class="nav-links" role="list"> 25 25 {navLinks.map(({ href, label, pop }) => { 26 26 const isActive = pathname === href || (href !== '/' && pathname.startsWith(href)); 27 27 return ( 28 28 <li> 29 29 <a 30 30 href={href} 31 - class:list={['nav__link', { 'nav__link--active': isActive }]} 31 + class:list={['nav-link', { 'nav-link--active': isActive }]} 32 32 aria-current={isActive ? 'page' : undefined} 33 33 target={pop ? '_blank' : undefined} 34 34 > ··· 43 43 </header> 44 44 45 45 <style> 46 - .nav__inner { 46 + .nav-inner { 47 47 display: flex; 48 48 align-items: center; 49 49 justify-content: space-between; 50 50 height: 4rem; 51 51 } 52 52 53 - .nav__brand { 53 + .nav-brand { 54 54 display: inline-flex; 55 55 align-items: center; 56 56 margin: 0; ··· 62 62 align-items: center; 63 63 } 64 64 65 - .nav__brand:hover { 65 + .nav-brand:hover { 66 66 color: var(--color-accent2); 67 67 } 68 68 69 - .nav__brand-name { 69 + .nav-brand-name { 70 70 font-family: var(--font-heading); 71 71 font-weight: 300; 72 72 font-size: 1.25rem; 73 73 } 74 74 75 - .nav__links { 75 + .nav-links { 76 76 display: flex; 77 77 align-items: center; 78 78 gap: 0.2rem; 79 79 list-style: none; 80 80 } 81 81 82 - .nav__link { 82 + .nav-link { 83 83 display: block; 84 84 padding: 0.2rem 0.75rem; 85 85 border-radius: var(--radius-md); ··· 90 90 transition: color var(--transition), background-color var(--transition); 91 91 } 92 92 93 - .nav__link:hover { 93 + .nav-link:hover { 94 94 color: var(--color-text); 95 95 background: var(--color-border); 96 96 } 97 97 98 - .nav__link--active { 98 + .nav-link--active { 99 99 color: var(--color-accent2); 100 100 background: color-mix(in srgb, var(--color-accent2) 12%, transparent); 101 101 } 102 102 103 - .nav__link--active:hover { 103 + .nav-link--active:hover { 104 104 color: var(--color-accent2); 105 105 background: color-mix(in srgb, var(--color-accent2) 18%, transparent); 106 106 }
+15 -27
src/components/StarField.astro
··· 1 1 --- 2 - const STAR_COUNT = 150; 3 - 4 - interface Star { 5 - top: number; 6 - left: number; 7 - opacity: number; 8 - duration: number; 9 - delay: number; 10 - size: number; 2 + function style_range(min: number, max: number, f: number = 2): number { 3 + return (Math.random() * (max - min) + min).toFixed(f); 11 4 } 12 5 13 - function rnd(min: number, max: number): number { 14 - return Math.random() * (max - min) + min; 15 - } 16 - 17 - const stars: Star[] = Array.from({ length: STAR_COUNT }, () => ({ 18 - top: rnd(0, 100), 19 - left: rnd(0, 100), 20 - opacity: rnd(0.2, 0.8), 21 - duration: rnd(2, 6), 22 - delay: rnd(0, 5), 23 - size: rnd(1, 3), 24 - })); 6 + const stars: string[] = Array.from({ length: 150 }, () => { 7 + const size = style_range(1, 3, 1); 8 + let style = ""; 9 + style += `width:${size}px;`; 10 + style += `height:${size}px;`; 11 + style += `top:${style_range(0, 100)}%;`; 12 + style += `left:${style_range(0, 100)}%;`; 13 + style += `--star-opacity:${style_range(0.25, 0.81)};`; 14 + style += `--star-duration:${style_range(1.6, 7)}s;`; 15 + style += `--star-delay:${style_range(0, 4)}s;`; 16 + return style; 17 + }); 25 18 --- 26 19 27 20 <div class="starfield" aria-hidden="true"> 28 - {stars.map((s) => ( 29 - <span 30 - class="star" 31 - style={`top:${s.top.toFixed(2)}%;left:${s.left.toFixed(2)}%;--star-opacity:${s.opacity.toFixed(2)};--star-duration:${s.duration.toFixed(2)}s;--star-delay:${s.delay.toFixed(2)}s;width:${s.size.toFixed(1)}px;height:${s.size.toFixed(1)}px;`} 32 - /> 33 - ))} 21 + {stars.map((s) => <span class="star" style={s} />)} 34 22 </div>
+5 -6
src/layouts/Base.astro
··· 8 8 title?: string; 9 9 description?: string; 10 10 } 11 - 12 11 const { 13 12 title: providedTitle, 14 - description = 'Hubble — explore the universe of knowledge.', 13 + description = 'Hubble synchronizes an authenticated copy of data repositories in the atmosphere, so you can recover your content even if your PDS loses it.', 15 14 } = Astro.props; 16 15 17 - const title = providedTitle ? `${providedTitle} | Hubble` : 'Hubble'; 18 - const canonicalURL = new URL(Astro.url.pathname, Astro.site ?? 'https://hubble.example.com'); 16 + const title = providedTitle ? `${providedTitle} | Hubble` : 'Hubble: full-network atmosphere mirror'; 17 + const canonicalURL = new URL(Astro.url.pathname, Astro.site ?? 'https://hubble.microcosm.blue'); 19 18 --- 20 19 21 20 <!doctype html> ··· 23 22 <head> 24 23 <meta charset="utf-8" /> 25 24 <meta name="viewport" content="width=device-width, initial-scale=1" /> 25 + <title>{title}</title> 26 26 <meta name="description" content={description} /> 27 - <link rel="canonical" href={canonicalURL} /> 28 27 <link rel="icon" href="/favicon.svg" type="image/svg+xml" /> 29 - <title>{title}</title> 28 + <link rel="canonical" href={canonicalURL} /> 30 29 </head> 31 30 <body> 32 31 <StarField />
+27 -379
src/pages/index.astro
··· 1 1 --- 2 2 import Base from '../layouts/Base.astro'; 3 - 4 - const features = [ 5 - { 6 - title: 'A neat thing!', 7 - description: 'A short thing about it. idk if i like these, but here they are and they can stay for the moment.', 8 - }, 9 - { 10 - title: 'Something cool', 11 - description: 'With more words to write. maybe i should make ai write some slop for these.', 12 - }, 13 - { 14 - title: 'Open-Source', 15 - description: 'Is this how stuff like that will be presented? a lil box just to say "MIT/Apache" or whatever?', 16 - }, 17 - { 18 - title: 'Blah blah blah', 19 - description: 'blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah.', 20 - }, 21 - { 22 - title: 'Hopefully i remember', 23 - description: 'to change the words in this box befor sharing anything publicly', 24 - }, 25 - { 26 - title: 'Okay we made it to six!', 27 - description: 'Six boxes. So the grid layout can be tested. Is it toooo bland and generic?', 28 - }, 29 - ]; 30 - 31 - const faqs = [ 32 - { 33 - question: 'What is Hubble?', 34 - answer: 'alskdjflkaj flaj sflkj aslkdjf lakj', 35 - }, 36 - { 37 - question: 'Who is Hubble for?', 38 - answer: 'alskdjflkaj flaj sflkj aslkdjf lakj', 39 - }, 40 - { 41 - question: 'Is Hubble free to use?', 42 - answer: 'alskdjflkaj flaj sflkj aslkdjf lakj', 43 - }, 44 - { 45 - question: 'How is content created on Hubble?', 46 - answer: 'alskdjflkaj flaj sflkj aslkdjf lakj', 47 - }, 48 - { 49 - question: 'Can I contribute to Hubble?', 50 - answer: 'alskdjflkaj flaj sflkj aslkdjf lakj', 51 - }, 52 - { 53 - question: 'Does Hubble track me or show ads?', 54 - answer: 'alskdjflkaj flaj sflkj aslkdjf lakj', 55 - }, 56 - { 57 - question: 'How do I report an error or suggest an improvement?', 58 - answer: 'alskdjflkaj flaj sflkj aslkdjf lakj', 59 - }, 60 - { 61 - question: 'What technology does Hubble use?', 62 - answer: 'alskdjflkaj flaj sflkj aslkdjf lakj', 63 - }, 64 - ]; 65 3 --- 66 4 67 5 <Base 68 6 description="Hubble: full-network atmosphere mirror (in development!)" 69 7 > 70 - <!-- Hero --> 71 - <section class="hero section--lg"> 72 - <div class="container hero__inner"> 73 - <h1 class="hero__title"> 74 - Hubble<br> 75 - <span class="hero__titlemore">full-network atmosphere mirror</span> 76 - </h1> 77 - <p class="hero__subtitle"> 78 - In development! 79 - </p> 80 - 81 - <!-- decorative background blobby shit --> 82 - <div class="hero__orb" aria-hidden="true"> 83 - <div class="orb orb--1"></div> 84 - <div class="orb orb--2"></div> 85 - </div> 86 - </div> 8 + <!-- helloooo --> 9 + <section class="hello section-lg"> 10 + <h1 class="hello-title"> 11 + Hubble<br> 12 + <span class="hello-titlemore">full-network atmosphere mirror</span> 13 + </h1> 14 + <p class="hello-subtitle"> 15 + In development! 16 + </p> 87 17 </section> 88 18 89 - <section class="section docs-content"> 90 - <div class="container docs-prose"> 91 - 92 - <div class="docs-section" id="what-is-hubble"> 19 + <!-- add more info-sections for hr-separated bits --> 20 + <section class="section info-content"> 21 + <div class="container info-prose"> 22 + <div class="info-section"> 93 23 <h2>Infrastructure for network resilience</h2> 94 - <p> 95 - Public data in atproto lives on Personal Data Servers (PDS), which can sometimes crash, have networking issues, or be unavailable for other reasons. 96 - </p> 97 - <p> 98 - Hubble synchronizes an authenticated copy of every data repository in the atmosphere, so you can recover your content even if your PDS loses it. 99 - </p> 100 - <p> 101 - We need to make some choices around language still. Like, is "archive" a good word to have on the website? 102 - I think it decently conveys the intended meaning, but I'm worried it might suggest that Hubble keeps <em>historical</em> archives of past repository states, which is not what we're doing here!! 103 - </p> 104 - <p> 105 - "Mirror" feels more immediate, but for some reason I don't like it that much. Maybe it's ok and I just need the right phrasing around it. "Sync" and "synchronizing" feel a little better but will need some care to avoid being confusing with "sync" in the sense of being tap-like or otherwise a firehose library. 24 + <p>Public data in atproto lives on Personal Data Servers (PDS), which can sometimes crash, have networking issues, or be unavailable for other reasons.</p> 25 + <p>Hubble synchronizes an authenticated copy of data repositories in the atmosphere, so you can recover your content even if your PDS loses it. 26 + <strong>Authenticated Transfer</strong> (the AT in AT Protocol) ensures accurate replication on Hubble, and prevents Hubble itself from tampering.</p> 27 + <p>Hubble is a new project from 28 + <a href="https://microcosm.blue" style="color: var(--color-accent-microcosm)" target="_blank">microcosm</a> and 29 + <a style="color: var(--color-accent-bsky)">Bluesky</a>, deploying by end of June. 106 30 </p> 107 31 </div> 108 - 109 - <div class="docs-section" id="what-is-hubble"> 110 - <h2>Heading off concerns</h2> 111 - <p> 112 - Are there some obvious concerns that people are going to have about this, that should be communicated early on this page (like right here)?. 113 - </p> 114 - <p> 115 - Probably some assurance about repo state being respected (deactivated, takendown, etc.) and also moderation (the service can moderate) and maybe some expectation-setting about access (designed personal access to your own repo). 116 - </p> 117 - </div> 118 - 119 - <div class="docs-section" id="what-is-hubble"> 120 - <h2>Purpose</h2> 121 - <p> 122 - Probably goes even earlier here, but, why does this exist and what can you do with it? 123 - </p> 124 - </div> 125 - </div> 126 - </section> 127 - 128 - <!-- About --> 129 - <section class="section about"> 130 - <div class="container"> 131 - <div class="boxes__header"> 132 - <span class="eyebrow">Blah blah</span> 133 - <h2>Section title thing</h2> 134 - <p class="boxes__lead"> 135 - Some contextual info before the 2-grid boxes... 136 - </p> 137 - </div> 138 - 139 - <div class="about__two-col"> 140 - <div class="card feature-card"> 141 - <h3>Public full-network archive service</h3> 142 - <p> 143 - Microcosm will run Hubble atmosphere-wide, synchronizing all records in the network. 144 - </p> 145 - </div> 146 - <div class="card feature-card"> 147 - <h3>Self-host</h3> 148 - <p> 149 - Hubble is open-source! While synchronizing the entire atmosphere is resource-intensive, you can 150 - </p> 151 - </div> 152 - </div> 153 - </div> 154 - </section> 155 - 156 - <!-- Features --> 157 - <section class="section features"> 158 - <div class="container"> 159 - <div class="boxes__header"> 160 - <span class="eyebrow">So much content</span> 161 - <h2>Another part with another title</h2> 162 - </div> 163 - 164 - <ul class="features__grid" role="list"> 165 - {features.map(({ title, description }) => ( 166 - <li class="card feature-card"> 167 - <h3>{title}</h3> 168 - <p>{description}</p> 169 - </li> 170 - ))} 171 - </ul> 172 - </div> 173 - </section> 174 - 175 - <section class="section faq-section"> 176 - <div class="container faq-container"> 177 - <div class="boxes__header"> 178 - <h2>whishful thinking ("faq")</h2> 179 - </div> 180 - <dl class="faq-list"> 181 - {faqs.map(({ question, answer }) => ( 182 - <details class="faq-item"> 183 - <summary class="faq-question"> 184 - <span>{question}</span> 185 - <span class="faq-chevron" aria-hidden="true"> 186 - <svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" width="16" height="16"> 187 - <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> 188 - </svg> 189 - </span> 190 - </summary> 191 - <div class="faq-answer"> 192 - <p>{answer}</p> 193 - </div> 194 - </details> 195 - ))} 196 - </dl> 197 32 </div> 198 33 </section> 199 34 </Base> 200 35 201 36 <style> 202 - /* hero thing */ 203 - .hero { 37 + .hello { 204 38 text-align: center; 205 - position: relative; 206 - overflow: hidden; 207 39 } 208 - 209 - .hero__inner { 210 - position: relative; 211 - z-index: 1; 212 - } 213 - 214 - .hero__title { 215 - font-family: var(--font-heading); 216 - font-weight: 900; 40 + .hello-title { 217 41 margin-bottom: var(--space-3); 218 42 } 219 - 220 - .hero__titlemore { 43 + .hello-titlemore { 221 44 color: var(--color-accent3); 222 45 font-weight: 300; 223 46 } 224 - 225 - .hero__subtitle { 47 + .hello-subtitle { 226 48 font-size: clamp(1rem, 2vw, 1.25rem); 227 49 color: var(--color-muted); 228 50 max-width: 55ch; 229 51 margin-inline: auto; 230 - margin-bottom: var(--space-8); 231 - } 232 - 233 - .hero__orb { 234 - position: absolute; 235 - inset: 0; 236 - pointer-events: none; 237 - z-index: 0; 238 - } 239 - 240 - .orb { 241 - position: absolute; 242 - border-radius: 50%; 243 - opacity: 0.12; 244 - } 245 - 246 - .orb--1 { 247 - width: 400px; 248 - height: 400px; 249 - background: radial-gradient(circle, var(--color-accent4), transparent 70%); 250 - top: -120px; 251 - right: -80px; 252 - } 253 - 254 - .orb--2 { 255 - width: 300px; 256 - height: 300px; 257 - background: radial-gradient(circle, var(--color-accent2), transparent 70%); 258 - bottom: -80px; 259 - left: -60px; 260 52 } 261 53 262 - /* about */ 263 - .boxes__header { 264 - max-width: 56ch; 265 - margin-bottom: var(--space-8); 266 - } 267 - 268 - .boxes__header h2 { 269 - font-family: var(--font-heading); 270 - font-weight: 400; 271 - } 272 - 273 - .boxes__lead { 274 - font-size: 1.1rem; 275 - color: var(--color-muted); 276 - margin-top: var(--space-4); 277 - } 278 - 279 - .about__two-col { 280 - display: grid; 281 - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); 282 - gap: var(--space-6); 283 - } 284 - 285 - /* Prose content */ 286 - .docs-prose { 54 + .info-prose { 287 55 max-width: 720px; 288 56 } 289 - 290 - .docs-section { 57 + .info-section { 291 58 margin-bottom: var(--space-12); 292 59 padding-bottom: var(--space-12); 293 60 border-bottom: 1px solid var(--color-border); 294 61 } 295 - 296 - .docs-section:last-child { 62 + .info-section:last-child { 297 63 border-bottom: none; 298 64 } 299 - 300 - .docs-section h2 { 301 - font-family: var(--font-heading); 302 - font-weight: 400; 303 - margin-bottom: var(--space-4); 304 - } 305 - 306 - .docs-section p { 65 + .info-section p { 307 66 max-width: none; 308 - } 309 - 310 - 311 - .section__header { 312 - margin-bottom: var(--space-8); 313 - } 314 - 315 - .features__grid { 316 - display: grid; 317 - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); 318 - gap: var(--space-6); 319 - list-style: none; 320 - } 321 - 322 - .feature-card__icon { 323 - font-size: 1.75rem; 324 - margin-bottom: var(--space-4); 325 - } 326 - 327 - .feature-card h3 { 328 - font-family: var(--font-heading); 329 - font-weight: 200; 330 - margin-bottom: var(--space-2); 331 - } 332 - 333 - .feature-card p { 334 - color: var(--color-muted); 335 - font-size: 0.95rem; 336 - } 337 - 338 - /* faq */ 339 - .faq-container { 340 - max-width: 760px; 341 - } 342 - 343 - .faq-list { 344 - display: flex; 345 - flex-direction: column; 346 - gap: var(--space-3); 347 - margin-bottom: var(--space-8); 348 - } 349 - 350 - .faq-item { 351 - background: var(--color-surface); 352 - border: 1px solid var(--color-border); 353 - border-radius: var(--radius-md); 354 - transition: border-color var(--transition); 355 - overflow: hidden; 356 - } 357 - 358 - .faq-item[open] { 359 - border-color: var(--color-accent2); 360 - } 361 - 362 - .faq-question { 363 - display: flex; 364 - align-items: center; 365 - justify-content: space-between; 366 - gap: var(--space-4); 367 - padding: var(--space-4) var(--space-6); 368 - cursor: pointer; 369 - list-style: none; 370 - font-family: var(--font-heading); 371 - font-weight: 400; 372 - font-size: 1rem; 373 - color: var(--color-text); 374 - transition: color var(--transition); 375 - user-select: none; 376 - } 377 - 378 - /* Remove default triangle in Safari/Firefox */ 379 - .faq-question::-webkit-details-marker { display: none; } 380 - .faq-question::marker { display: none; } 381 - 382 - .faq-question:hover { 383 - color: var(--color-accent2); 384 - } 385 - 386 - .faq-chevron { 387 - flex-shrink: 0; 388 - color: var(--color-muted); 389 - transition: transform var(--transition), color var(--transition); 390 - } 391 - 392 - .faq-item[open] .faq-chevron { 393 - transform: rotate(180deg); 394 - color: var(--color-accent2); 395 - } 396 - 397 - .faq-answer { 398 - padding: 0 var(--space-6) var(--space-6); 399 - color: var(--color-muted); 400 - line-height: 1.7; 401 - border-top: 1px solid var(--color-border); 402 - } 403 - 404 - .faq-answer p { 405 - margin-top: var(--space-4); 406 - max-width: none; 407 - } 408 - 409 - 410 - /* Shared */ 411 - .eyebrow { 412 - display: block; 413 - font-weight: 400; 414 - font-size: 0.8rem; 415 - letter-spacing: 0.1em; 416 - text-transform: uppercase; 417 - color: var(--color-accent2); 418 - margin-bottom: var(--space-3); 419 67 } 420 68 </style>
+35 -89
src/styles/global.css
··· 1 - /* ============================================================ 2 - Fonts 3 - ============================================================ */ 4 1 @import '@fontsource/londrina-solid/300.css'; 5 2 @import '@fontsource/londrina-solid/400.css'; 6 3 @import '@fontsource/londrina-solid/900.css'; 7 4 8 - /* ============================================================ 9 - Design tokens — Light mode (default) 10 - ============================================================ */ 5 + /* default/light mode */ 11 6 :root { 12 - /* Colors */ 13 7 --color-bg: #F5F2EB; 14 8 --color-surface: #FFFFFF; 15 9 --color-border: #D9D4C7; ··· 17 11 --color-muted: #5C6080; 18 12 --color-accent1: #E07A5F; 19 13 --color-accent2: #3AAFA9; 20 - --color-accent3: #F2CC60; 14 + --color-accent3: #E2BC50; 21 15 --color-accent4: #9B8EC4; 22 - 23 - --color-star: hsla(94, 10%, 60%, 0.3); 16 + --color-star: hsla(192, 81%, 44%, 0.667); 17 + --color-accent-microcosm: #ce9df1; 18 + --color-accent-bsky: rgb(0, 106, 255); 24 19 25 - /* Typography */ 26 20 --font-heading: 'Londrina Solid', system-ui, -apple-system, 'Segoe UI', sans-serif; 27 21 --font-body: system-ui, -apple-system, 'Segoe UI', sans-serif; 28 22 --font-mono: ui-monospace, 'Cascadia Code', Menlo, Consolas, monospace; 29 23 30 - /* Spacing scale */ 31 24 --space-1: 0.25rem; 32 25 --space-2: 0.5rem; 33 26 --space-3: 0.75rem; ··· 38 31 --space-16: 4rem; 39 32 --space-24: 6rem; 40 33 41 - /* Radius */ 42 34 --radius-sm: 0.25rem; 43 35 --radius-md: 0.5rem; 44 36 --radius-lg: 1rem; 45 37 --radius-full: 9999px; 46 38 47 - /* Transitions */ 48 39 --transition: 200ms ease; 49 40 } 50 41 51 - /* ============================================================ 52 - Dark mode — system preference 53 - ============================================================ */ 42 + /* dark mode overrides */ 54 43 @media (prefers-color-scheme: dark) { 55 44 :root { 56 45 --color-bg: #0D0F1C; ··· 62 51 --color-accent2: #4DBFB9; 63 52 --color-accent3: #F5D570; 64 53 --color-accent4: #B0A3D4; 54 + --color-star: hsla(289, 80%, 72%, 0.4); 65 55 } 66 56 } 67 57 68 - /* Dark mode — manual override via data attribute */ 69 - [data-theme="dark"] { 70 - --color-bg: #0D0F1C; 71 - --color-surface: #161929; 72 - --color-border: #252840; 73 - --color-text: #EAE6D9; 74 - --color-muted: #8B8FA8; 75 - --color-accent1: #E88B73; 76 - --color-accent2: #4DBFB9; 77 - --color-accent3: #F5D570; 78 - --color-accent4: #B0A3D4; 79 - } 80 - 81 - [data-theme="light"] { 82 - --color-bg: #F5F2EB; 83 - --color-surface: #FFFFFF; 84 - --color-border: #D9D4C7; 85 - --color-text: #1C1F3A; 86 - --color-muted: #5C6080; 87 - --color-accent1: #E07A5F; 88 - --color-accent2: #3AAFA9; 89 - --color-accent3: #F2CC60; 90 - --color-accent4: #9B8EC4; 91 - } 92 - 93 - /* ============================================================ 94 - Reset 95 - ============================================================ */ 58 + /* reset/basics */ 96 59 *, *::before, *::after { 97 60 box-sizing: border-box; 98 61 margin: 0; 99 62 padding: 0; 100 63 } 101 - 102 64 html { 103 65 scroll-behavior: smooth; 104 66 -webkit-text-size-adjust: 100%; 105 67 } 106 - 107 68 body { 108 69 font-family: var(--font-body); 109 70 background-color: var(--color-bg); 110 71 color: var(--color-text); 111 72 line-height: 1.65; 112 73 min-height: 100vh; 113 - transition: background-color var(--transition), color var(--transition); 114 74 } 115 - 116 75 img, svg, video { 117 76 display: block; 118 77 max-width: 100%; 119 78 } 120 - 121 79 button, input, select, textarea { 122 80 font: inherit; 123 81 } 124 82 125 - /* ============================================================ 126 - Typography scale 127 - ============================================================ */ 83 + /* type */ 128 84 h1, h2, h3, h4, h5, h6 { 129 - line-height: 1.2; 130 85 color: var(--color-text); 86 + line-height: 1.2; 87 + font-family: var(--font-heading); 88 + font-weight: 400; 89 + margin-bottom: 0.5em; 131 90 } 132 - 133 - h1 { font-size: clamp(2rem, 5vw, 3.5rem); font-weight: 800; } 134 - h1.hero__title { font-size: clamp(3rem, 5vw, 4.5rem); font-weight: 900; } 91 + h1 { 92 + font-size: clamp(3rem, 5vw, 4.5rem); 93 + font-weight: 900; 94 + font-size: clamp(2rem, 5vw, 3.5rem); font-weight: 800; 95 + } 135 96 h2 { font-size: clamp(1.5rem, 3vw, 2.25rem); } 136 97 h3 { font-size: clamp(1.15rem, 2vw, 1.5rem); } 137 98 h4 { font-size: 1.125rem; } 138 99 139 100 p { 140 101 max-width: 65ch; 141 - margin-bottom: var(--space-4); 102 + margin-bottom: 1em; 142 103 } 143 - 144 - p:last-child { 145 - margin-bottom: 0; 146 - } 104 + p:first-child { margin-top: 0 } 105 + p:last-child { margin-bottom: 0 } 147 106 148 107 a { 149 108 color: var(--color-accent2); 150 109 text-decoration: underline; 151 110 text-decoration-thickness: 1px; 152 111 text-underline-offset: 2px; 153 - transition: color var(--transition); 112 + transition: color var(--transition), text-decoration-thickness var(--transition); 154 113 } 155 - 156 114 a:hover { 157 115 color: var(--color-accent1); 116 + text-decoration-thickness: 2px; 158 117 } 159 118 160 119 code { ··· 184 143 font-size: inherit; 185 144 } 186 145 187 - /* ============================================================ 188 - Layout utilities 189 - ============================================================ */ 146 + /* layout */ 190 147 .container { 191 148 width: 100%; 192 149 max-width: 1100px; ··· 198 155 padding-block: var(--space-12); 199 156 } 200 157 201 - .section--lg { 158 + .section-lg { 202 159 padding-block: var(--space-24); 203 160 } 204 161 205 - /* ============================================================ 206 - Star field canvas 207 - ============================================================ */ 162 + /* bg stars */ 208 163 .starfield { 209 164 position: fixed; 210 165 inset: 0; ··· 217 172 position: absolute; 218 173 width: 2px; 219 174 height: 2px; 220 - border-radius: var(--radius-full); 175 + border-radius: 2px; 221 176 background: var(--color-star); 222 177 animation: twinkle var(--star-duration, 3s) ease-in-out infinite; 223 178 animation-delay: var(--star-delay, 0s); ··· 225 180 226 181 @keyframes twinkle { 227 182 0%, 100% { opacity: var(--star-opacity, 0.6); transform: scale(1); } 228 - 50% { opacity: 0.1; transform: scale(0.7); } 183 + 50% { opacity: 0.1; transform: scale(0.6); } 229 184 } 230 185 231 - /* ============================================================ 232 - Main content above star field 233 - ============================================================ */ 186 + /* content */ 234 187 .site-wrapper { 235 188 position: relative; 236 189 z-index: 1; ··· 238 191 flex-direction: column; 239 192 min-height: 100vh; 240 193 } 241 - 242 194 .site-main { 243 195 flex: 1; 244 196 } 245 197 246 - /* ============================================================ 247 - Buttons 248 - ============================================================ */ 198 + /* everyone's favourite: buttons */ 249 199 .btn { 250 200 display: inline-flex; 251 201 align-items: center; 252 202 gap: var(--space-2); 253 203 padding: var(--space-3) var(--space-6); 254 204 border-radius: var(--radius-full); 255 - font-family: var(--font-heading); 256 205 font-weight: 700; 257 206 font-size: 1rem; 258 207 text-decoration: none; ··· 260 209 cursor: pointer; 261 210 transition: background-color var(--transition), color var(--transition), border-color var(--transition), transform var(--transition); 262 211 } 263 - 264 212 .btn:hover { 265 213 transform: translateY(-1px); 266 214 } 267 215 268 - .btn--primary { 216 + .btn.primary { 269 217 background: var(--color-accent1); 270 218 color: #fff; 271 219 border-color: var(--color-accent1); 272 220 } 273 221 274 - .btn--primary:hover { 222 + .btn.primary:hover { 275 223 background: transparent; 276 224 color: var(--color-accent1); 277 225 } 278 226 279 - .btn--outline { 227 + .btn.outline { 280 228 background: transparent; 281 229 color: var(--color-accent2); 282 230 border-color: var(--color-accent2); 283 231 } 284 232 285 - .btn--outline:hover { 233 + .btn.outline:hover { 286 234 background: var(--color-accent2); 287 235 color: #fff; 288 236 } 289 237 290 - /* ============================================================ 291 - Cards 292 - ============================================================ */ 238 + /* cards, why not */ 293 239 .card { 294 240 background: var(--color-surface); 295 241 border: 1px solid var(--color-border);