static site frontend for mapped.at mapped.at
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add about tab

+120 -3
+2 -2
router.js
··· 8 8 export function getRoute() { 9 9 const params = new URLSearchParams(location.search); 10 10 return { 11 - view: params.get('view') ?? 'trails', 11 + view: params.get('view') ?? 'about', 12 12 filter: params.get('filter') ?? null, 13 - id: params.get('id') ?? null, 13 + id: params.get('id') ?? null, 14 14 }; 15 15 } 16 16
+43 -1
script.js
··· 470 470 471 471 customElements.define('atproto-login', AtprotoLogin); 472 472 473 + // ── <about-view> ───────────────────────────────────────────────── 474 + class AboutView extends HTMLElement { 475 + connectedCallback() { 476 + const shadow = this.attachShadow({ mode: 'open' }); 477 + shadow.innerHTML = /*html*/` 478 + <link rel="stylesheet" href="styles.css"> 479 + <div class="about-container"> 480 + <div class="about-hero"> 481 + <div class="about-hero-label">Welcome to</div> 482 + <div class="about-hero-title">mapped.at</div> 483 + <div class="about-hero-tagline">Trails and activities, built on the open AT protocol</div> 484 + </div> 485 + <div class="card"> 486 + <div class="card-body"> 487 + <p class="caption">Browse community trails, log your activities, and connect with other adventurers — all on a decentralised, open network.</p> 488 + <div class="about-ctas"> 489 + <button class="about-cta-primary">Browse Trails →</button> 490 + <button class="about-cta-secondary">View Activity</button> 491 + </div> 492 + </div> 493 + </div> 494 + </div> 495 + `; 496 + 497 + shadow.querySelector('.about-cta-primary').addEventListener('click', () => { 498 + navigate({ view: 'trails' }); 499 + }); 500 + shadow.querySelector('.about-cta-secondary').addEventListener('click', () => { 501 + navigate({ view: 'feed' }); 502 + }); 503 + } 504 + } 505 + 506 + customElements.define('about-view', AboutView); 507 + 473 508 // ── <app-root> ──────────────────────────────────────────────────── 474 509 // Reads the current route and renders the appropriate page component. 475 510 // Subscribes to route changes and re-renders on each transition. ··· 509 544 const tabs = document.createElement('tab-switcher'); 510 545 this.appendChild(tabs); 511 546 512 - if (route.view === 'feed') { 547 + if (route.view === 'about') { 548 + this.appendChild(document.createElement('about-view')); 549 + } else if (route.view === 'feed') { 513 550 const feed = document.createElement('activity-feed'); 514 551 if (route.filter) feed.setAttribute('data-filter', route.filter); 515 552 this.appendChild(feed); ··· 703 740 <link rel="stylesheet" href="styles.css"> 704 741 <div class="tabs-container"> 705 742 <div role="tablist" class="tabs"> 743 + <button role="tab" class="tab" data-view="about">About</button> 706 744 <button role="tab" class="tab" data-view="trails">Trails</button> 707 745 <button role="tab" class="tab" data-view="feed">All Activity</button> 708 746 </div> ··· 733 771 btn.setAttribute('aria-selected', String(isActive)); 734 772 btn.classList.toggle('active', isActive); 735 773 }); 774 + 775 + // Hide filter row on about view 776 + const filterRow = this.shadowRoot.querySelector('.filter-row'); 777 + filterRow.style.display = route.view === 'about' ? 'none' : ''; 736 778 737 779 // Rebuild dropdown options for current view 738 780 const select = this.shadowRoot.querySelector('select');
+75
styles.css
··· 235 235 color: var(--color-text-primary); 236 236 } 237 237 238 + /* ── About view ─────────────────────────────────────────────────── */ 239 + .about-container { 240 + max-width: var(--feed-max-width); 241 + margin: 0 auto; 242 + padding: 16px 16px 0; 243 + display: flex; 244 + flex-direction: column; 245 + gap: 12px; 246 + } 247 + 248 + .about-hero { 249 + background: linear-gradient(135deg, var(--color-accent-green), var(--color-accent-teal)); 250 + border-radius: var(--radius-card); 251 + padding: 24px 20px; 252 + color: #fff; 253 + } 254 + 255 + .about-hero-label { 256 + font-size: 11px; 257 + font-weight: 600; 258 + text-transform: uppercase; 259 + letter-spacing: 0.5px; 260 + opacity: 0.75; 261 + margin-bottom: 6px; 262 + } 263 + 264 + .about-hero-title { 265 + font-size: 28px; 266 + font-weight: 800; 267 + margin-bottom: 6px; 268 + letter-spacing: -0.5px; 269 + } 270 + 271 + .about-hero-tagline { 272 + font-size: 13px; 273 + opacity: 0.85; 274 + line-height: 1.5; 275 + } 276 + 277 + .about-ctas { 278 + display: flex; 279 + gap: 8px; 280 + margin-top: 16px; 281 + } 282 + 283 + .about-cta-primary { 284 + background: var(--color-accent-green); 285 + color: #fff; 286 + border: none; 287 + border-radius: 6px; 288 + padding: 8px 16px; 289 + font-size: 13px; 290 + font-weight: 600; 291 + cursor: pointer; 292 + } 293 + 294 + .about-cta-secondary { 295 + background: var(--color-bg); 296 + color: var(--color-text-primary); 297 + border: 1px solid var(--color-border); 298 + border-radius: 6px; 299 + padding: 8px 16px; 300 + font-size: 13px; 301 + font-weight: 500; 302 + cursor: pointer; 303 + } 304 + 305 + .about-cta-primary:hover { 306 + opacity: 0.9; 307 + } 308 + 309 + .about-cta-secondary:hover { 310 + background: var(--color-card); 311 + } 312 + 238 313 /* ── Card shell ─────────────────────────────────────────────────── */ 239 314 .card { 240 315 background: var(--color-card);