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.0"> 6 <title>The Feed â Open Event Syndication Protocol</title> 7 <meta name="description" content="The Feed is an open, headless event syndication protocol for performers, promoters, and venues. No platform. No lock-in. One submission, everywhere."> 8 <meta property="og:title" content="The Feed â Open Event Protocol"> 9 <meta property="og:description" content="Submit a show once. Syndicate everywhere. Open protocol for the live music industry."> 10 <meta property="og:type" content="website"> 11 <meta property="og:url" content="https://thefeed.site"> 12 <meta property="og:image" content="https://thefeed.site/feed-icon.svg"> 13 <link rel="icon" type="image/svg+xml" href="./feed-icon.svg"> 14 <link rel="apple-touch-icon" href="./feed-icon.svg"> 15 <link rel="preconnect" href="https://fonts.googleapis.com"> 16 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 17 <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"> 18 19 <style> 20 /* ââ Reset & base âââââââââââââââââââââââââââââââââââââââ */ 21 *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } 22 23 :root { 24 --accent: #e94560; 25 --accent-dim: rgba(233,69,96,0.12); 26 --cyan: #00b4d8; 27 --cyan-dim: rgba(0,180,216,0.12); 28 --bg: #0a0a0f; 29 --surface: #13131a; 30 --surface2: #1c1c26; 31 --border: rgba(255,255,255,0.07); 32 --text: #e8e8f0; 33 --text-mid: #9090a8; 34 --text-dim: #4a4a60; 35 --radius: 12px; 36 --font: 'Inter', system-ui, sans-serif; 37 --mono: 'IBM Plex Mono', 'Courier New', monospace; 38 --max: 1120px; 39 } 40 41 html { scroll-behavior: smooth; } 42 43 body { 44 font-family: var(--font); 45 background: var(--bg); 46 color: var(--text); 47 line-height: 1.6; 48 -webkit-font-smoothing: antialiased; 49 } 50 51 a { color: var(--accent); text-decoration: none; } 52 a:hover { text-decoration: underline; } 53 54 /* ââ Layout helpers âââââââââââââââââââââââââââââââââââââ */ 55 .wrap { max-width: var(--max); margin: 0 auto; padding: 0 1.5rem; } 56 .split { display: grid; gap: 3rem; align-items: start; } 57 @media (min-width: 860px) { 58 .split { grid-template-columns: 1fr 1fr; } 59 .split.flip > *:first-child { order: 2; } 60 .split.flip > *:last-child { order: 1; } 61 } 62 @media (min-width: 1000px) { 63 .split.wide { grid-template-columns: 1.15fr 1fr; } 64 } 65 66 /* ââ Nav ââââââââââââââââââââââââââââââââââââââââââââââââ */ 67 nav { 68 position: sticky; 69 top: 0; 70 z-index: 100; 71 background: rgba(10,10,15,0.88); 72 backdrop-filter: blur(16px); 73 border-bottom: 1px solid var(--border); 74 } 75 .nav-inner { 76 max-width: var(--max); 77 margin: 0 auto; 78 padding: 0 1.5rem; 79 height: 60px; 80 display: flex; 81 align-items: center; 82 justify-content: space-between; 83 gap: 1rem; 84 } 85 .nav-logo { 86 display: flex; 87 align-items: center; 88 gap: 10px; 89 font-weight: 700; 90 font-size: 1rem; 91 color: var(--text); 92 text-decoration: none; 93 } 94 .nav-logo-mark { 95 width: 28px; 96 height: 28px; 97 display: flex; 98 align-items: center; 99 justify-content: center; 100 flex-shrink: 0; 101 } 102 .nav-logo-mark svg { 103 width: 16px; 104 height: 28px; 105 } 106 .nav-links { 107 display: flex; 108 align-items: center; 109 gap: 0.25rem; 110 list-style: none; 111 } 112 .nav-links a { 113 font-size: 0.875rem; 114 color: var(--text-mid); 115 padding: 6px 12px; 116 border-radius: 6px; 117 transition: color 0.15s, background 0.15s; 118 } 119 .nav-links a:hover { color: var(--text); background: rgba(255,255,255,0.05); text-decoration: none; } 120 .nav-cta { 121 font-size: 0.8125rem; 122 font-weight: 600; 123 padding: 8px 16px; 124 background: var(--accent); 125 color: #fff !important; 126 border-radius: 7px; 127 transition: opacity 0.15s; 128 } 129 .nav-cta:hover { opacity: 0.85; text-decoration: none !important; } 130 @media (max-width: 620px) { 131 .nav-links { display: none; } 132 } 133 134 /* ââ Hero âââââââââââââââââââââââââââââââââââââââââââââââ */ 135 .hero { 136 padding: 6rem 0 5rem; 137 text-align: center; 138 position: relative; 139 overflow: hidden; 140 } 141 .hero::before { 142 content: ''; 143 position: absolute; 144 inset: 0; 145 background: 146 radial-gradient(ellipse 80% 50% at 50% -10%, rgba(233,69,96,0.15) 0%, transparent 70%), 147 radial-gradient(ellipse 60% 40% at 80% 100%, rgba(0,180,216,0.08) 0%, transparent 60%); 148 pointer-events: none; 149 } 150 .hero-eyebrow { 151 display: inline-flex; 152 align-items: center; 153 gap: 8px; 154 font-size: 0.75rem; 155 font-weight: 600; 156 letter-spacing: 0.1em; 157 text-transform: uppercase; 158 color: var(--accent); 159 background: var(--accent-dim); 160 border: 1px solid rgba(233,69,96,0.25); 161 padding: 5px 14px; 162 border-radius: 999px; 163 margin-bottom: 1.75rem; 164 } 165 .hero h1 { 166 font-size: clamp(2.4rem, 6vw, 4.25rem); 167 font-weight: 800; 168 line-height: 1.1; 169 letter-spacing: -0.03em; 170 margin-bottom: 1.25rem; 171 max-width: 820px; 172 margin-left: auto; 173 margin-right: auto; 174 } 175 .hero h1 em { 176 font-style: normal; 177 color: var(--accent); 178 } 179 .hero-sub { 180 font-size: clamp(1rem, 2.5vw, 1.2rem); 181 color: var(--text-mid); 182 max-width: 580px; 183 margin: 0 auto 2.5rem; 184 line-height: 1.65; 185 } 186 .hero-actions { 187 display: flex; 188 gap: 12px; 189 justify-content: center; 190 flex-wrap: wrap; 191 } 192 .btn-primary { 193 display: inline-flex; 194 align-items: center; 195 gap: 8px; 196 padding: 14px 28px; 197 background: var(--accent); 198 color: #fff; 199 font-weight: 700; 200 font-size: 1rem; 201 border-radius: 10px; 202 transition: opacity 0.15s, transform 0.1s; 203 text-decoration: none; 204 } 205 .btn-primary:hover { opacity: 0.85; text-decoration: none; transform: translateY(-1px); } 206 .btn-ghost { 207 display: inline-flex; 208 align-items: center; 209 gap: 8px; 210 padding: 14px 28px; 211 background: rgba(255,255,255,0.05); 212 border: 1px solid var(--border); 213 color: var(--text); 214 font-weight: 600; 215 font-size: 1rem; 216 border-radius: 10px; 217 transition: background 0.15s, border-color 0.15s, transform 0.1s; 218 text-decoration: none; 219 } 220 .btn-ghost:hover { background: rgba(255,255,255,0.09); border-color: rgba(255,255,255,0.15); text-decoration: none; transform: translateY(-1px); } 221
222 /* ââ Live marquee stripe ââââââââââââââââââââââââââââââââ */ 223 .marquee-stripe { 224 position: relative; 225 width: 100%; 226 background: var(--surface); 227 border-top: 1px solid var(--border); 228 border-bottom: 1px solid var(--border); 229 padding: 0; 230 overflow: hidden; 231 margin-top: 4rem; 232 } 233 /* left/right edge fade masks */ 234 .marquee-stripe::before, 235 .marquee-stripe::after { 236 content: ''; 237 position: absolute; 238 top: 0; bottom: 0; 239 width: 120px; 240 z-index: 2; 241 pointer-events: none; 242 } 243 .marquee-stripe::before { 244 left: 0; 245 background: linear-gradient(to right, var(--surface) 0%, transparent 100%); 246 } 247 .marquee-stripe::after { 248 right: 0; 249 background: linear-gradient(to left, var(--surface) 0%, transparent 100%); 250 } 251 .marquee-header { 252 position: absolute; 253 left: 0; top: 0; bottom: 0; 254 z-index: 3; 255 display: flex; 256 align-items: center; 257 padding: 0 1.25rem 0 1.5rem; 258 background: var(--surface); 259 border-right: 1px solid var(--border); 260 gap: 8px; 261 white-space: nowrap; 262 } 263 .marquee-header-dot { 264 width: 7px; height: 7px; 265 border-radius: 50%; 266 background: var(--accent); 267 animation: pulse-dot 1.6s ease-in-out infinite; 268 flex-shrink: 0; 269 } 270 @keyframes pulse-dot { 271 0%, 100% { opacity: 1; transform: scale(1); } 272 50% { opacity: 0.4; transform: scale(0.7); } 273 } 274 .marquee-header-label { 275 font-size: 0.68rem; 276 font-weight: 700; 277 letter-spacing: 0.12em; 278 text-transform: uppercase; 279 color: var(--accent); 280 } 281 .marquee-track-wrap { 282 overflow: hidden; 283 padding-left: 160px; /* room for header label */ 284 } 285 .marquee-track { 286 display: flex; 287 width: max-content; 288 animation: marquee-scroll 55s linear infinite; 289 will-change: transform; 290 } 291 .marquee-track:hover { animation-play-state: paused; } 292 @keyframes marquee-scroll { 293 from { transform: translateX(0); } 294 to { transform: translateX(-50%); } 295 } 296 .marquee-item { 297 display: inline-flex; 298 align-items: center; 299 gap: 10px; 300 padding: 0 2.5rem; 301 height: 52px; 302 cursor: default; 303 flex-shrink: 0; 304 transition: background 0.15s; 305 border-right: 1px solid var(--border); 306 } 307 .marquee-item:hover { 308 background: rgba(255,255,255,0.03); 309 } 310 .marquee-item a { 311 display: inline-flex; 312 align-items: center; 313 gap: 10px; 314 text-decoration: none; 315 color: inherit; 316 } 317 .marquee-item a:hover { text-decoration: none; } 318 .marquee-dot { 319 width: 5px; height: 5px; 320 border-radius: 50%; 321 background: var(--accent); 322 flex-shrink: 0; 323 opacity: 0.6; 324 } 325 .marquee-name { 326 font-size: 0.8rem; 327 font-weight: 600; 328 color: var(--text); 329 white-space: nowrap; 330 max-width: 220px; 331 overflow: hidden; 332 text-overflow: ellipsis; 333 } 334 .marquee-meta { 335 font-size: 0.72rem; 336 color: var(--text-mid); 337 white-space: nowrap; 338 font-family: var(--mono); 339 } 340 .marquee-sep { 341 color: var(--text-dim); 342 font-size: 0.7rem; 343 padding: 0 0.25rem; 344 } 345 .marquee-price { 346 font-size: 0.68rem; 347 font-weight: 700; 348 padding: 2px 7px; 349 border-radius: 4px; 350 background: var(--accent-dim); 351 color: var(--accent); 352 letter-spacing: 0.02em; 353 white-space: nowrap; 354 flex-shrink: 0; 355 } 356 .marquee-price.free { 357 background: rgba(0,180,216,0.1); 358 color: var(--cyan); 359 } 360 /* skeleton items shown while loading */ 361 .marquee-skel { 362 display: inline-flex; 363 align-items: center; 364 gap: 10px; 365 padding: 0 2.5rem; 366 height: 52px; 367 flex-shrink: 0; 368 border-right: 1px solid var(--border); 369 } 370 .skel-bar { 371 height: 10px; 372 border-radius: 5px; 373 background: rgba(255,255,255,0.06); 374 animation: skel-pulse 1.4s ease-in-out infinite; 375 }
376 @keyframes skel-pulse { 377 0%, 100% { opacity: 1; } 378 50% { opacity: 0.4; } 379 } 380 381 /* ââ Section chrome âââââââââââââââââââââââââââââââââââââ */ 382 section { padding: 5rem 0; } 383 section + section { border-top: 1px solid var(--border); } 384 .section-label { 385 display: inline-block; 386 font-size: 0.72rem; 387 font-weight: 700; 388 letter-spacing: 0.1em; 389 text-transform: uppercase; 390 color: var(--cyan); 391 margin-bottom: 0.875rem; 392 } 393 .section-label.red { color: var(--accent); } 394 h2.section-title { 395 font-size: clamp(1.75rem, 4vw, 2.6rem); 396 font-weight: 800; 397 letter-spacing: -0.025em; 398 line-height: 1.15; 399 margin-bottom: 1rem; 400 } 401 h2.section-title em { font-style: normal; color: var(--accent); } 402 .section-body { 403 font-size: 1.05rem; 404 color: var(--text-mid); 405 line-height: 1.7; 406 max-width: 520px; 407 } 408 .section-body p + p { margin-top: 0.875rem; } 409 410 /* ââ How it works âââââââââââââââââââââââââââââââââââââââ */ 411 .steps { 412 display: flex; 413 flex-direction: column; 414 gap: 0; 415 margin-top: 2.5rem; 416 } 417 .step { 418 display: flex; 419 gap: 1.25rem; 420 padding: 1.5rem 0; 421 border-bottom: 1px solid var(--border); 422 position: relative; 423 } 424 .step:last-child { border-bottom: none; } 425 .step-num { 426 width: 36px; 427 height: 36px; 428 flex-shrink: 0; 429 border-radius: 50%; 430 background: var(--surface2); 431 border: 1px solid var(--border); 432 display: flex; 433 align-items: center; 434 justify-content: center; 435 font-size: 0.8rem; 436 font-weight: 700; 437 color: var(--text-mid); 438 margin-top: 2px; 439 } 440 .step-content h4 { 441 font-size: 1rem; 442 font-weight: 700; 443 margin-bottom: 0.3rem; 444 color: var(--text); 445 } 446 .step-content p { 447 font-size: 0.9rem; 448 color: var(--text-mid); 449 line-height: 1.6; 450 } 451 452 /* ââ Live demo ââââââââââââââââââââââââââââââââââââââââââ */ 453 #try { 454 background: var(--surface); 455 } 456 .demo-wrap { 457 max-width: 480px; 458 } 459 .demo-note { 460 font-size: 0.75rem; 461 color: var(--text-dim); 462 margin-top: 0.875rem; 463 line-height: 1.5; 464 } 465 .demo-note a { color: var(--text-mid); } 466 467 /* ââ For publishers âââââââââââââââââââââââââââââââââââââ */ 468 .pub-cards { 469 display: grid; 470 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); 471 gap: 1rem; 472 margin-top: 2.5rem; 473 } 474 .pub-card { 475 background: var(--surface); 476 border: 1px solid var(--border); 477 border-radius: var(--radius); 478 padding: 1.5rem; 479 transition: border-color 0.15s, transform 0.15s; 480 } 481 .pub-card:hover { border-color: rgba(255,255,255,0.14); transform: translateY(-2px); } 482 .pub-card-icon { 483 width: 36px; 484 height: 36px; 485 margin-bottom: 0.875rem; 486 display: flex; 487 align-items: center; 488 justify-content: center; 489 background: var(--accent-dim); 490 border-radius: 9px; 491 color: var(--accent); 492 flex-shrink: 0; 493 } 494 .pub-card-icon svg { width: 18px; height: 18px; } 495 .pub-card h3 { 496 font-size: 1rem; 497 font-weight: 700; 498 margin-bottom: 0.4rem; 499 } 500 .pub-card p { 501 font-size: 0.875rem; 502 color: var(--text-mid); 503 line-height: 1.6; 504 } 505 506 /* ââ Code embed âââââââââââââââââââââââââââââââââââââââââ */ 507 .embed-box { 508 background: var(--surface2); 509 border: 1px solid var(--border); 510 border-radius: var(--radius); 511 overflow: hidden; 512 margin-top: 2rem; 513 } 514 .embed-tabs { 515 display: flex; 516 border-bottom: 1px solid var(--border); 517 overflow-x: auto; 518 } 519 .embed-tab { 520 padding: 10px 18px; 521 font-size: 0.78rem; 522 font-weight: 600; 523 color: var(--text-mid); 524 cursor: pointer; 525 border: none; 526 background: none; 527 font-family: var(--font); 528 white-space: nowrap; 529 border-bottom: 2px solid transparent; 530 margin-bottom: -1px; 531 transition: color 0.12s; 532 } 533 .embed-tab.active { color: var(--accent); border-bottom-color: var(--accent); } 534 .embed-pane { display: none; } 535 .embed-pane.active { display: block; } 536 pre { 537 padding: 1.25rem 1.5rem; 538 overflow-x: auto; 539 font-family: var(--mono); 540 font-size: 0.8125rem; 541 line-height: 1.7; 542 color: #cdd9e5; 543 } 544 .kw { color: #ff7b72; } 545 .str { color: #a5d6ff; } 546 .cmt { color: #484f58; } 547 .attr{ color: #79c0ff; } 548 .val { color: #a5d6ff; } 549 .tag { color: #7ee787; } 550 .copy-btn { 551 display: block; 552 width: calc(100% - 3rem); 553 margin: 0 1.5rem 1.25rem; 554 padding: 9px; 555 background: rgba(255,255,255,0.04); 556 border: 1px solid var(--border); 557 border-radius: 7px; 558 color: var(--text-mid); 559 font-size: 0.78rem; 560 font-weight: 600; 561 cursor: pointer; 562 font-family: var(--font); 563 transition: background 0.12s, color 0.12s; 564 } 565 .copy-btn:hover { background: rgba(255,255,255,0.08); color: var(--text); } 566 567 /* ââ Embed preview layout âââââââââââââââââââââââââââââââ */ 568 .embed-preview-wrap { 569 display: grid; 570 grid-template-columns: 1fr 1fr; 571 gap: 0; 572 min-height: 480px; 573 } 574 @media (max-width: 780px) { 575 .embed-preview-wrap { grid-template-columns: 1fr; } 576 .embed-preview-side { border-left: none !important; border-top: 1px solid var(--border); } 577 } 578 .embed-code-side { 579 display: flex; 580 flex-direction: column; 581 } 582 .embed-configurator { 583 display: flex; 584 flex-wrap: wrap; 585 gap: 8px; 586 padding: 1rem 1.5rem 0.75rem; 587 border-bottom: 1px solid var(--border); 588 } 589 .embed-configurator label { 590 display: flex; 591 flex-direction: column; 592 gap: 4px; 593 font-size: 0.7rem; 594 font-weight: 600; 595 color: var(--text-mid); 596 text-transform: uppercase; 597 letter-spacing: 0.06em; 598 } 599 .embed-configurator select { 600 background: var(--surface); 601 border: 1px solid var(--border); 602 border-radius: 6px; 603 color: var(--text); 604 font-size: 0.8rem; 605 font-family: var(--font); 606 padding: 5px 8px; 607 cursor: pointer; 608 min-width: 110px; 609 } 610 .embed-configurator select:focus { outline: 1px solid var(--accent); } 611 .embed-code-side pre { flex: 1; } 612 .embed-preview-side { 613 border-left: 1px solid var(--border); 614 display: flex; 615 flex-direction: column; 616 } 617 .embed-preview-label { 618 font-size: 0.7rem; 619 font-weight: 600; 620 color: var(--text-dim); 621 text-transform: uppercase; 622 letter-spacing: 0.08em; 623 padding: 0.6rem 1rem; 624 border-bottom: 1px solid var(--border); 625 display: flex; 626 align-items: center; 627 justify-content: space-between; 628 } 629 .embed-preview-label a { 630 font-size: 0.7rem; 631 color: var(--accent); 632 font-weight: 500; 633 } 634 .embed-preview-iframe { 635 flex: 1; 636 width: 100%; 637 border: none; 638 min-height: 420px; 639 background: var(--bg); 640 } 641 642 /* ââ Skins showcase âââââââââââââââââââââââââââââââââââââ */ 643 .skins-grid { 644 display: grid; 645 grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); 646 gap: 1rem; 647 margin-top: 2rem; 648 } 649 .skin-chip { 650 background: var(--surface); 651 border: 1px solid var(--border); 652 border-radius: var(--radius); 653 padding: 1.25rem; 654 cursor: pointer; 655 transition: border-color 0.15s, transform 0.15s; 656 text-align: left; 657 } 658 .skin-chip:hover { transform: translateY(-2px); } 659 .skin-chip.active { border-color: var(--accent); } 660 .skin-swatch { 661 width: 100%; 662 height: 6px; 663 border-radius: 3px; 664 margin-bottom: 0.875rem; 665 } 666 .skin-chip h4 { font-size: 0.9rem; font-weight: 700; margin-bottom: 0.2rem; } 667 .skin-chip p { font-size: 0.78rem; color: var(--text-mid); line-height: 1.5; } 668 669 /* ââ Open source / protocol âââââââââââââââââââââââââââââ */ 670 .protocol-grid { 671 display: grid; 672 grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); 673 gap: 1rem; 674 margin-top: 2.5rem; 675 } 676 .protocol-card { 677 background: var(--surface); 678 border: 1px solid var(--border); 679 border-radius: var(--radius); 680 padding: 1.5rem; 681 } 682 .protocol-card h3 { 683 font-size: 0.9375rem; 684 font-weight: 700; 685 margin-bottom: 0.5rem; 686 display: flex; 687 align-items: center; 688 gap: 8px; 689 } 690 .protocol-card p { 691 font-size: 0.875rem; 692 color: var(--text-mid); 693 line-height: 1.6; 694 } 695 .tag-pill { 696 font-size: 0.65rem; 697 font-weight: 700; 698 padding: 2px 8px; 699 border-radius: 4px; 700 letter-spacing: 0.04em; 701 } 702 .tag-pill.green { background: rgba(34,197,94,0.15); color: #22c55e; } 703 .tag-pill.blue { background: rgba(0,180,216,0.15); color: var(--cyan); } 704 .tag-pill.orange { background: rgba(245,158,11,0.15); color: #f59e0b; } 705 706 /* ââ CTA strip ââââââââââââââââââââââââââââââââââââââââââ */ 707 .cta-strip { 708 background: linear-gradient(135deg, rgba(233,69,96,0.12) 0%, rgba(0,180,216,0.08) 100%); 709 border: 1px solid rgba(233,69,96,0.2); 710 border-radius: 16px; 711 padding: 3.5rem 2.5rem; 712 text-align: center; 713 margin: 5rem 0; 714 } 715 .cta-strip h2 { 716 font-size: clamp(1.6rem, 3.5vw, 2.4rem); 717 font-weight: 800; 718 letter-spacing: -0.025em; 719 margin-bottom: 0.875rem; 720 } 721 .cta-strip p { 722 font-size: 1.05rem; 723 color: var(--text-mid); 724 max-width: 460px; 725 margin: 0 auto 2rem; 726 line-height: 1.65; 727 } 728 .cta-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; } 729 730 /* ââ Footer âââââââââââââââââââââââââââââââââââââââââââââ */ 731 footer { 732 border-top: 1px solid var(--border); 733 padding: 2.5rem 0; 734 } 735 .footer-inner { 736 max-width: var(--max); 737 margin: 0 auto; 738 padding: 0 1.5rem; 739 display: flex; 740 align-items: center; 741 justify-content: space-between; 742 gap: 1rem; 743 flex-wrap: wrap; 744 } 745 .footer-logo { 746 font-weight: 700; 747 font-size: 0.875rem; 748 color: var(--text-mid); 749 display: flex; 750 align-items: center; 751 gap: 8px; 752 } 753 .footer-logo svg { 754 width: 10px; 755 height: 17px; 756 flex-shrink: 0; 757 } 758 .footer-links { 759 display: flex; 760 gap: 1.5rem; 761 list-style: none; 762 } 763 .footer-links a { 764 font-size: 0.8125rem; 765 color: var(--text-dim); 766 transition: color 0.15s; 767 } 768 .footer-links a:hover { color: var(--text-mid); text-decoration: none; } 769 .footer-credit { 770 font-size: 0.75rem; 771 color: var(--text-dim); 772 } 773 </style> 774</head> 775<body> 776 777<!-- ââââââââââââââââââââââââââââ NAV ââââââââââââââââââââââââââââ --> 778<nav> 779 <div class="nav-inner"> 780 <a href="#" class="nav-logo">
781 <span class="nav-logo-mark"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 302.72 522.95" aria-hidden="true"><g fill="#e94560"><rect y="356.36" width="133.17" height="22.1"/><rect y="460.3" width="133.17" height="22.1"/><rect y="408.33" width="133.17" height="22.1"/><polygon points="0 200.45 0 201.38 0 222.56 244.79 222.56 244.79 201.38 133.17 201.38 133.17 200.45 0 200.45"/><rect y="304.39" width="244.79" height="22.1"/><polygon points="302.72 14.68 302.72 0 133.17 0 0 0 0 14.68 302.72 14.68"/><rect y="252.42" width="244.79" height="22.1"/><rect y="44.54" width="302.72" height="22.1"/><rect y="512.27" width="133.17" height="10.67"/><rect y="148.48" width="133.17" height="22.1"/><rect y="96.51" width="302.72" height="22.1"/></g></svg></span> 782 The Feed 783 </a> 784 <ul class="nav-links"> 785 <li><a href="#how">How it works</a></li> 786 <li><a href="#try">Try it</a></li> 787 <li><a href="#embed">Embed</a></li> 788 <li><a href="#protocol">Protocol</a></li> 789 </ul> 790 <a href="#try" class="nav-links nav-cta">Try it free â</a> 791 </div> 792</nav> 793 794<!-- ââââââââââââââââââââââââââââ HERO ââââââââââââââââââââââââââââ --> 795<section class="hero"> 796 <div class="wrap"> 797 <div class="hero-eyebrow"> 798 <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4.9 19.1C1 15.2 1 8.8 4.9 4.9"/><path d="M7.8 16.2c-2.3-2.3-2.3-6.1 0-8.5"/><circle cx="12" cy="12" r="2"/><path d="M16.2 7.8c2.3 2.3 2.3 6.1 0 8.5"/><path d="M19.1 4.9C23 8.8 23 15.1 19.1 19"/></svg> 799 Open Event Protocol 800 </div> 801 <h1>Submit a show once.<br><em>Syndicate everywhere.</em></h1> 802 <p class="hero-sub"> 803 The Feed is a headless, open-source event protocol that connects performers, promoters, and venues â 804 no platform, no lock-in, no forms. 805 </p> 806 <div class="hero-actions"> 807 <a href="#try" class="btn-primary"> 808 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg> 809 Try it now 810 </a> 811 <a href="#embed" class="btn-ghost"> 812 Embed on your site â 813 </a> 814 </div> 815 </div> 816</section> 817 818<!-- ââââââââââââââââââââââââ LIVE MARQUEE ââââââââââââââââââââââââ --> 819<div class="marquee-stripe" id="live-marquee" aria-label="Live events ticker" role="marquee"> 820 <div class="marquee-header"> 821 <span class="marquee-header-dot" aria-hidden="true"></span> 822 <span class="marquee-header-label">Live</span> 823 </div> 824 <div class="marquee-track-wrap"> 825 <div class="marquee-track" id="marquee-track"> 826 <!-- skeletons while loading --> 827 <div class="marquee-skel"><div class="skel-bar" style="width:160px"></div><div class="skel-bar" style="width:80px"></div></div> 828 <div class="marquee-skel"><div class="skel-bar" style="width:130px"></div><div class="skel-bar" style="width:70px"></div></div> 829 <div class="marquee-skel"><div class="skel-bar" style="width:180px"></div><div class="skel-bar" style="width:90px"></div></div> 830 <div class="marquee-skel"><div class="skel-bar" style="width:140px"></div><div class="skel-bar" style="width:75px"></div></div> 831 <div class="marquee-skel"><div class="skel-bar" style="width:170px"></div><div class="skel-bar" style="width:85px"></div></div> 832 <div class="marquee-skel"><div class="skel-bar" style="width:150px"></div><div class="skel-bar" style="width:80px"></div></div> 833 </div> 834 </div> 835</div> 836 837<!-- ââââââââââââââââââââââââ HOW IT WORKS ââââââââââââââââââââââââ --> 838<section id="how"> 839 <div class="wrap"> 840 <div class="split wide"> 841 <div> 842 <span class="section-label">How it works</span> 843 <h2 class="section-title">No forms.<br>No login.<br><em>Just paste.</em></h2> 844 <div class="section-body"> 845 <p>Paste a URL, a poster description, a venue email, or a social media post. The Feed's AI extraction engine â "The Eventizer" â pulls out the performer, date, venue, and ticket info automatically.</p> 846 <p>Every submission gets a deterministic SHA-256 ID so the same show is never counted twice, no matter how many people submit it.</p> 847 </div> 848 <div class="steps"> 849 <div class="step"> 850 <div class="step-num">1</div> 851 <div class="step-content"> 852 <h4>Paste a URL or describe the show</h4> 853 <p>Link to Bandsintown, Eventbrite, Facebook, or just type "The Trews at the Commodore, Friday 8pm, $35."</p> 854 </div> 855 </div> 856 <div class="step"> 857 <div class="step-num">2</div> 858 <div class="step-content"> 859 <h4>AI extracts the structured event</h4> 860 <p>The Eventizer runs brand-safety checks, resolves relative dates, normalizes performer and venue names to schema.org/Event.</p> 861 </div> 862 </div> 863 <div class="step"> 864 <div class="step-num">3</div> 865 <div class="step-content"> 866 <h4>Committed to the open ledger</h4> 867 <p>Verified sources commit directly. Public submissions go to editorial review via a GitHub Pull Request â the whole history is public and auditable.</p> 868 </div> 869 </div> 870 <div class="step"> 871 <div class="step-num">4</div> 872 <div class="step-content"> 873 <h4>Consumed anywhere, in any format</h4> 874 <p>Publishers and sites pull the live feed as JSON-LD, RSS, .ics, or embed the <code><the-feed-event></code> web component directly.</p> 875 </div> 876 </div> 877 </div> 878 </div> 879 <div> 880 <div class="embed-box" style="margin-top:0;"> 881 <div style="padding:1.25rem 1.5rem; border-bottom:1px solid var(--border);">
882 <span style="font-size:0.72rem;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;color:var(--text-dim);">Live event in the ledger</span> 883 </div> 884 <pre style="font-size:0.76rem;">{ 885 <span class="attr">"@context"</span>: <span class="val">"https://schema.org"</span>, 886 <span class="attr">"@type"</span>: <span class="val">"Event"</span>, 887 <span class="attr">"@id"</span>: <span class="val">"evt_3f7a2b..."</span>, 888 <span class="attr">"name"</span>: <span class="val">"The Trews â Live"</span>, 889 <span class="attr">"startDate"</span>: <span class="val">"2026-04-17T20:00:00"</span>, 890 <span class="attr">"location"</span>: { 891 <span class="attr">"@type"</span>: <span class="val">"Place"</span>, 892 <span class="attr">"name"</span>: <span class="val">"The Fillmore"</span> 893 }, 894 <span class="attr">"performer"</span>: [{ <span class="attr">"name"</span>: <span class="val">"The Trews"</span> }], 895 <span class="attr">"offers"</span>: [{ 896 <span class="attr">"price"</span>: <span class="val">35</span>, 897 <span class="attr">"priceCurrency"</span>: <span class="val">"USD"</span> 898 }], 899 <span class="attr">"_feed"</span>: { 900 <span class="attr">"trustScore"</span>: <span class="val">95</span>, 901 <span class="attr">"source"</span>: <span class="val">"verified_venue"</span>, 902 <span class="attr">"brandSafe"</span>: <span class="val">true</span> 903 } 904}</pre> 905 </div> 906 </div> 907 </div> 908 </div> 909</section> 910 911<!-- âââââââââââââââââââââââââ LIVE DEMO ââââââââââââââââââââââââââ --> 912<section id="try"> 913 <div class="wrap"> 914 <div class="split flip wide"> 915 <div class="demo-wrap"> 916 <the-feed-ingest 917 api="https://the-feed-ingest.stephan-99b.workers.dev" 918 skin="default" 919 mode="dark" 920 ></the-feed-ingest> 921 <p class="demo-note"> 922 Bring your own <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener">OpenAI API key</a> (sk-â¦). 923 Your key is only used to call OpenAI directly â it is never stored on our servers. 924 Check the box to save it in your browser for next time. 925 </p> 926 </div> 927 <div> 928 <span class="section-label red">Live demo</span> 929 <h2 class="section-title">Try The Feed<br>right now.</h2> 930 <div class="section-body"> 931 <p>Paste a URL to any event listing â Bandsintown, Eventbrite, a venue website, a Facebook event â and watch it get extracted into a structured schema.org event in seconds.</p> 932 <p>Or just describe a show in plain English. "Soccer Mommy at The Fillmore, Saturday April 18, doors 8pm, $30 advance."</p> 933 <p>Public submissions go to editorial review. Provision a source token via the <a href="#embed">admin API</a> for direct commits.</p> 934 </div> 935 <div class="skins-grid" style="margin-top:2rem;"> 936 <div class="skin-chip active" data-skin="default" onclick="setSkin(this)"> 937 <div class="skin-swatch" style="background:linear-gradient(90deg,#e94560,#e9456077);"></div> 938 <h4>Default</h4> 939 <p>Clean & neutral â works anywhere.</p> 940 </div> 941 <div class="skin-chip" data-skin="broadcast" onclick="setSkin(this)"> 942 <div class="skin-swatch" style="background:linear-gradient(90deg,#00b4d8,#00b4d877);"></div> 943 <h4>Broadcast</h4> 944 <p>Dark, monospace, broadcast energy.</p> 945 </div> 946 <div class="skin-chip" data-skin="poster" onclick="setSkin(this)"> 947 <div class="skin-swatch" style="background:linear-gradient(90deg,#ff3c00,#ff3c0077);"></div> 948 <h4>Poster</h4> 949 <p>Bold, high-contrast, music venue.</p> 950 </div> 951 </div> 952 </div> 953 </div> 954 </div> 955</section> 956 957<!-- ââââââââââââââââââââââââââ EMBED ââââââââââââââââââââââââââââââ --> 958<section id="embed"> 959 <div class="wrap"> 960 <span class="section-label">For promoters, venues & performers</span> 961 <h2 class="section-title" style="max-width:580px;">Put The Feed on<br>your site in <em>two lines.</em></h2> 962 963 <div class="pub-cards"> 964 <div class="pub-card">
965 <span class="pub-card-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 11l19-9-9 19-2-8-8-2z"/></svg></span> 966 <h3>Promoters & Labels</h3> 967 <p>Provision a source token via the admin API and your submissions commit directly â no review queue, full trust score. Register your OpenAI key server-side so it's never exposed to end users.</p> 968 </div> 969 <div class="pub-card"> 970 <span class="pub-card-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg></span> 971 <h3>Venues</h3> 972 <p>Embed the submission widget on your site. Anyone can submit shows directly from your event page â submissions route through your token and your brand.</p> 973 </div> 974 <div class="pub-card"> 975 <span class="pub-card-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="8" r="4"/><path d="M9 15l-3 6m6-6v6m3-6l3 6"/></svg></span> 976 <h3>Performers & Managers</h3> 977 <p>Submit your own shows directly. A verified token ties your submissions to your name â no middleman, no waiting for a venue to post it for you.</p> 978 </div> 979 <div class="pub-card"> 980 <span class="pub-card-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg></span> 981 <h3>Calendar Publishers</h3> 982 <p>Embed <code><the-feed-calendar></code> on any page for a fully skinnable event listing. Users toggle between mosaic, list, week, and month views â no backend required.</p> 983 </div> 984 <div class="pub-card"> 985 <span class="pub-card-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 010 20M12 2a15.3 15.3 0 000 20"/></svg></span> 986 <h3>WordPress</h3> 987 <p>The Feed ships with a native WordPress plugin â shortcode builder, NLP quick-parse metabox, and automatic syndication on publish.</p> 988 </div> 989 <div class="pub-card"> 990 <span class="pub-card-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg></span> 991 <h3>No-code builders</h3> 992 <p>Weebly, Squarespace, Wix, Showit and similar builders block custom scripts but allow <code><iframe></code> embeds. Use the hosted embed URL â no JavaScript permissions needed.</p> 993 </div> 994 </div> 995 996 <div class="embed-box"> 997 <div class="embed-tabs"> 998 <button class="embed-tab active" onclick="showTab('basic', this)">Submit widget</button> 999 <button class="embed-tab" onclick="showTab('calendar', this)">Calendar</button> 1000 <button class="embed-tab" onclick="showTab('nocode', this)">No-code / iframe</button> 1001 <button class="embed-tab" onclick="showTab('verified', this)">Verified source</button> 1002 <button class="embed-tab" onclick="showTab('org', this)">Org shared key</button> 1003 <button class="embed-tab" onclick="showTab('consume', this)">Raw feed</button> 1004 </div> 1005 1006 <div id="tab-basic" class="embed-pane active"> 1007 <div class="embed-preview-wrap"> 1008 <div class="embed-code-side"> 1009 <div class="embed-configurator"> 1010 <label>Skin 1011 <select id="ingest-skin" onchange="updateIngestPreview()"> 1012 <option value="default">default</option> 1013 <option value="broadcast" selected>broadcast</option> 1014 <option value="poster">poster</option> 1015 </select> 1016 </label> 1017 <label>Mode 1018 <select id="ingest-mode" onchange="updateIngestPreview()"> 1019 <option value="auto" selected>auto</option> 1020 <option value="dark">dark</option> 1021 <option value="light">light</option> 1022 </select> 1023 </label> 1024 </div> 1025 <pre id="ingest-code"><span class="cmt"><!-- 1. Load the component --></span> 1026<span class="tag"><script</span> <span class="attr">type</span>=<span class="str">"module"</span> 1027 <span class="attr">src</span>=<span class="str">"https://cdn.jsdelivr.net/gh/Stephanada/the-feed@main/ui/the-feed-ingest.js"</span> 1028<span class="tag">></script></span> 1029 1030<span class="cmt"><!-- 2. Drop the element --></span> 1031<span class="tag"><the-feed-ingest</span> 1032 <span class="attr">skin</span>=<span class="str">"broadcast"</span> 1033<span class="tag">></the-feed-ingest></span> 1034 1035<span class="cmt"><
1035!-- Users enter their own OpenAI key in the widget. --></span></pre> 1036 <button class="copy-btn" onclick="copyCode('tab-basic')">Copy snippet</button> 1037 </div> 1038 <div class="embed-preview-side"> 1039 <div class="embed-preview-label"> 1040 <span>Live preview</span> 1041 <a id="ingest-preview-link" href="#" target="_blank" rel="noopener">Open â</a> 1042 </div> 1043 <iframe id="ingest-preview-iframe" class="embed-preview-iframe" 1044 src="./embed.html?mode=ingest&skin=broadcast&colormode=auto" 1045 title="Submit widget preview" 1046 loading="lazy"></iframe> 1047 </div> 1048 </div> 1049 </div> 1050 1051 <div id="tab-calendar" class="embed-pane"> 1052 <div class="embed-preview-wrap"> 1053 <div class="embed-code-side"> 1054 <div class="embed-configurator"> 1055 <label>Skin 1056 <select id="cal-skin" onchange="updateCalPreview()"> 1057 <option value="default" selected>default</option> 1058 <option value="broadcast">broadcast</option> 1059 <option value="poster">poster</option> 1060 </select> 1061 </label> 1062 <label>View 1063 <select id="cal-view" onchange="updateCalPreview()"> 1064 <option value="mosaic" selected>mosaic</option> 1065 <option value="list">list</option> 1066 <option value="week">week</option> 1067 <option value="month">month</option> 1068 </select> 1069 </label> 1070 <label>Mode 1071 <select id="cal-mode" onchange="updateCalPreview()"> 1072 <option value="auto" selected>auto</option> 1073 <option value="dark">dark</option> 1074 <option value="light">light</option> 1075 </select> 1076 </label> 1077 <label>Views shown 1078 <select id="cal-views" onchange="updateCalPreview()"> 1079 <option value="" selected>all 4</option> 1080 <option value="list,week,month">list+week+month</option> 1081 <option value="week,month">week+month</option> 1082 <option value="mosaic,list">mosaic+list</option> 1083 </select> 1084 </label> 1085 </div> 1086 <pre id="cal-code"><span class="tag"><script</span> <span class="attr">type</span>=<span class="str">"module"</span> 1087 <span class="attr">src</span>=<span class="str">"https://cdn.jsdelivr.net/gh/Stephanada/the-feed@main/ui/the-feed-calendar.js"</span> 1088<span class="tag">></script></span> 1089 1090<span class="tag"><the-feed-calendar</span> 1091 <span class="attr">skin</span>=<span class="str">"default"</span> 1092 <span class="attr">view</span>=<span class="str">"mosaic"</span> 1093<span class="tag">></the-feed-calendar></span></pre> 1094 <button class="copy-btn" onclick="copyCode('tab-calendar')">Copy snippet</button> 1095 </div> 1096 <div class="embed-preview-side"> 1097 <div class="embed-preview-label"> 1098 <span>Live preview</span> 1099 <a id="cal-preview-link" href="#" target="_blank" rel="noopener">Open â</a> 1100 </div> 1101 <iframe id="cal-preview-iframe" class="embed-preview-iframe" 1102 src="./embed.html?mode=calendar&skin=default&view=mosaic&colormode=auto" 1103 title="Calendar preview" 1104 loading="lazy"></iframe> 1105 </div> 1106 </div> 1107 </div> 1108 1109 <div id="tab-nocode" class="embed-pane"> 1110 <pre><span class="cmt"><!-- âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1111 No-code / iframe embed â works on Weebly, Squarespace, Wix, 1112 Showit, or any builder that accepts HTML/iframe blocks but 1113 blocks <script type="module">. 1114 1115 The hosted embed.html page loads the component for you. 1116 Paste this into an "HTML block" or "Embed code" widget. 1117ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ --></span> 1118 1119<span class="cmt"><!-- Submit widget (ingest) --></span> 1120<span class="tag"><iframe</span> 1121 <span class="attr">src</span>=<span class="str">"https://the-feed-ui.pages.dev/embed.html?mode=ingest&skin=broadcast"</span> 1122 <span class="attr">width</span>=<span class="str">"100%"</span> 1123 <span class="attr">height</span>=<span class="str">"420"</span> 1124 <span class="attr">style</span>=<span class="str">"border:none;border-radius:12px;"</span> 1125 <span class="attr">loading</span>=<span class="str">"lazy"</span> 1126 <span class="attr">title</span>=<span class="str">
1126"Submit an event"</span> 1127<span class="tag">></iframe></span> 1128 1129<span class="cmt"><!-- Event calendar (mosaic default, all 4 views) --></span> 1130<span class="tag"><iframe</span> 1131 <span class="attr">src</span>=<span class="str">"https://the-feed-ui.pages.dev/embed.html?mode=calendar&skin=default&view=mosaic"</span> 1132 <span class="attr">width</span>=<span class="str">"100%"</span> 1133 <span class="attr">height</span>=<span class="str">"600"</span> 1134 <span class="attr">style</span>=<span class="str">"border:none;border-radius:12px;"</span> 1135 <span class="attr">loading</span>=<span class="str">"lazy"</span> 1136 <span class="attr">title</span>=<span class="str">"Event calendar"</span> 1137<span class="tag">></iframe></span> 1138 1139<span class="cmt"><!-- Token-scoped calendar â only your events, dark skin --></span> 1140<span class="tag"><iframe</span> 1141 <span class="attr">src</span>=<span class="str">"https://the-feed-ui.pages.dev/embed.html?mode=calendar&token=your-token&skin=poster&view=list&colormode=dark"</span> 1142 <span class="attr">width</span>=<span class="str">"100%"</span> 1143 <span class="attr">height</span>=<span class="str">"600"</span> 1144 <span class="attr">style</span>=<span class="str">"border:none;border-radius:12px;"</span> 1145 <span class="attr">loading</span>=<span class="str">"lazy"</span> 1146 <span class="attr">title</span>=<span class="str">"My events"</span> 1147<span class="tag">></iframe></span> 1148 1149<span class="cmt"><!-- URL params reference: 1150 mode = ingest | calendar (default: ingest) 1151 skin = default | broadcast | poster | https://... (default: default) 1152 token = your-source-token (optional) 1153 group = group-slug (optional) 1154 venue = "Venue Name" (calendar only) 1155 performer = "Artist Name" (calendar only) 1156 genre = rock | jazz | ⦠(optional) 1157 view = mosaic | list | week | month (calendar, default: mosaic) 1158 views = comma list, e.g. list,week,month (limits toggle buttons) 1159 colormode = light | dark | auto (default: auto) 1160 limit = 50 (default: 50) 1161--></span></pre> 1162 <button class="copy-btn" onclick="copyCode('tab-nocode')">Copy snippet</button> 1163 </div> 1164 1165 <div id="tab-verified" class="embed-pane"> 1166 <pre><span class="cmt"><!-- Verified source: your token grants direct-commit trust. 1167 Users don't see a key field â you register your OpenAI key 1168 server-side when you provision the token. --></span> 1169 1170<span class="tag"><the-feed-ingest</span> 1171 <span class="attr">token</span>=<span class="str">"your-source-token"</span> 1172 <span class="attr">skin</span>=<span class="str">"broadcast"</span> 1173<span class="tag">></the-feed-ingest></span> 1174 1175<span class="cmt"><!-- Provision your token + key once: 1176curl -X POST https://the-feed-ingest.stephan-99b.workers.dev/admin/tokens \ 1177 -H "Authorization: Bearer ADMIN_SECRET" \ 1178 -H "Content-Type: application/json" \ 1179 -d '{ 1180 "token": "your-source-token", 1181 "name": "Blue Note", 1182 "sourceAuthority": "verified_venue", 1183 "openaiKey": "sk-..." 1184 }' --></span></pre> 1185 <button class="copy-btn" onclick="copyCode('tab-verified')">Copy snippet</button> 1186 </div> 1187 1188 <div id="tab-org" class="embed-pane"> 1189 <pre><span class="cmt"><!-- All pages in your org share one token + one key. 1190 Every page embeds the same token â no key visible to users, 1191 no per-page configuration needed. --></span> 1192 1193<span class="cmt"><!-- Page 1: venue homepage --></span> 1194<span class="tag"><the-feed-ingest</span> <span class="attr">token</span>=<span class="str">"org-token"</span><span class="tag">></the-feed-ingest></span> 1195 1196<span class="cmt"><!-- Page 2: events submission form --></span> 1197<span class="tag"><the-feed-ingest</span> <span class="attr">token</span>=<span class="str">"org-token"</span> <span class="attr">skin</span>=<span class="str">"broadcast"</span><span class="tag">></the-feed-ingest></span> 1198 1199<span class="cmt"><!-- Page 3: partner venue --></span> 1200<span class="tag"><the-feed-ingest</span> <span class="attr">token</span>=<span class="str">"org-token"</span> <span class="attr">skin</span>=<span class="str">"poster"</span><span class="tag">></the-feed-ingest></span> 1201 1202<span class="cmt"><!-- All three share your org's key stored in KV. 1203 Revoke the token any time from the admin API. --></span></pre> 1204 <button class="copy-btn" onclick="copyCode('tab-org')">Copy snippet</button> 1205 </div> 1206 1207 <div id="tab-consume" class="embed-pane"> 1208 <pre><span class="cmt">// Fetch the live event feed (JSON-LD array)</span> 1209<span class="kw">const</span> res = <span class="kw">await</span> fetch(<span class="str">'https://the-feed-api.stephan-99b.workers.dev/api/events'</span>); 1210<span class="kw">const</span> events = <span class="kw">await</span> res.json(); 1211 1212<span class="cmt">// Filter by city (optional)</span> 1213<span class="kw">const</span> local = events.filter(e => 1214 e.location?.name === <span class="str">
1214'The Fillmore'</span> 1215); 1216 1217<span class="cmt"><!-- Or use the event display component --></span> 1218<span class="tag"><script</span> <span class="attr">type</span>=<span class="str">"module"</span> 1219 <span class="attr">src</span>=<span class="str">"https://cdn.jsdelivr.net/gh/Stephanada/the-feed@main/ui/the-feed-event.js"</span> 1220<span class="tag">></script></span> 1221 1222<span class="tag"><the-feed-event</span> 1223 <span class="attr">event-id</span>=<span class="str">"evt_3f7a2b..."</span> 1224 <span class="attr">api</span>=<span class="str">"https://the-feed-api.stephan-99b.workers.dev"</span> 1225<span class="tag">></the-feed-event></span> 1226 1227<span class="cmt"><!-- RSS / iCal --> 1228GET /api/feed.rss 1229GET /api/feed.ics 1230GET /api/feed.json?venue=The+Fillmore&limit=20</span></pre> 1231 <button class="copy-btn" onclick="copyCode('tab-consume')">Copy snippet</button> 1232 </div> 1233 </div> 1234 </div> 1235</section> 1236 1237<!-- âââââââââââââââââââââââ OPEN PROTOCOL ââââââââââââââââââââââââ --> 1238<section id="protocol"> 1239 <div class="wrap"> 1240 <span class="section-label">Open protocol</span> 1241 <h2 class="section-title" style="max-width:560px;">Built on open standards.<br><em>Owned by no one.</em></h2> 1242 <div class="section-body"> 1243 <p>The Feed is not a platform. It's a protocol. The ledger is a public GitHub repository. The data format is schema.org/Event â the same standard Google, Ticketmaster, and every major events platform uses. No proprietary lock-in.</p> 1244 </div> 1245 <div class="protocol-grid"> 1246 <div class="protocol-card"> 1247 <h3>GitOps Ledger <span class="tag-pill green">Open</span></h3> 1248 <p>Events live as flat JSON files in a public GitHub repo. History is the audit log. Pull Requests are the moderation queue. Anyone can fork.</p> 1249 </div> 1250 <div class="protocol-card"> 1251 <h3>schema.org/Event <span class="tag-pill blue">Standard</span></h3> 1252 <p>Every event is valid schema.org JSON-LD. Ingest it into any system that speaks structured data â no adapters, no translation layer.</p> 1253 </div> 1254 <div class="protocol-card"> 1255 <h3>BYOK NLP <span class="tag-pill blue">Privacy</span></h3> 1256 <p>The platform never stores your OpenAI key. You bring your own â it flows directly from your browser to OpenAI and never touches our servers.</p> 1257 </div> 1258 <div class="protocol-card"> 1259 <h3>Cloudflare Edge <span class="tag-pill orange">Fast</span></h3> 1260 <p>All reads served at sub-50ms from 300+ edge locations. No origin server for reads. The ledger on GitHub is the canonical source.</p> 1261 </div> 1262 <div class="protocol-card"> 1263 <h3>Web Components <span class="tag-pill blue">Portable</span></h3> 1264 <p>Shadow DOM. Zero dependencies. Works in WordPress, React, plain HTML â one <code><script></code> tag, any CMS.</p> 1265 </div> 1266 <div class="protocol-card"> 1267 <h3>SHA-256 Idempotency <span class="tag-pill green">Reliable</span></h3> 1268 <p>The same show submitted twice â even from different sources with different wording â gets the same deterministic ID. No duplicates.</p> 1269 </div> 1270 </div> 1271 </div> 1272</section> 1273 1274<!-- âââââââââââââââââââââââââââ CTA ââââââââââââââââââââââââââââââ --> 1275<div class="wrap"> 1276 <div class="cta-strip"> 1277 <h2>Self-serve. No gatekeepers.</h2> 1278 <p>Provision your own source token via the admin API, embed the widget, and start submitting â no account, no approval, no waiting.</p> 1279 <div class="cta-actions"> 1280 <a href="#embed" class="btn-primary"> 1281 See the embed guide â 1282 </a> 1283 </div> 1284 </div> 1285</div> 1286 1287<!-- ââââââââââââââââââââââââ FOOTER ââââââââââââââââââââââââââââââ --> 1288<footer> 1289 <div class="footer-inner"> 1290 <span class="footer-logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 302.72 522.95" aria-hidden="true"><g fill="#e94560"><rect y="356.36" width="133.17" height="22.1"/><rect y="460.3" width="133.17" height="22.1"/><rect y="408.33" width="133.17" height="22.1"/><polygon points="0 200.45 0 201.38 0 222.56 244.79 222.56 244.79 201.38 133.17 201.38 133.17 200.45 0 200.45"/><rect y="304.39" width="244.79" height="22.1"/><polygon points="302.72 14.68 302.72 0 133.17 0 0 0 0 14.68 302.72 14.68"/><rect y="252.42" width="244.79" height="22.1"/><rect y="44.54" width="302.72" height="22.1"/><rect y="512.27" width="133.17" height="10.67"/><rect y="148.48" width="133.17" height="22.1"/><rect y="96.51" width="302.72" height="22.1"/></g></svg> The Feed â Open Event Protocol</span> 1291 <ul class="footer-links"> 1292 <li><a href="#how">How it works</a></li> 1293 <li><a href="#embed">Embed</a></li> 1294 <li><a href="#protocol">Protocol</a></li> 1295 </ul> 1296 <span class="footer-credit">thefeed.site · Open Event Protocol · MIT License</span> 1297 </div> 1298</footer> 1299 1300<!-- ââââââââââââââââââââââââââ JS ââââââââââââââââââââââââââââââ -->
1301<script type="module" src="./the-feed-ingest.js"></script>
vendor: 1 bytes, line 1301
1301
1302<script type="module" src="./the-feed-calendar.js"></script>
vendor: 1 bytes, line 1302
1302
1303<script> 1304 // ââ Skin picker 1305 function setSkin(chip) { 1306 document.querySelectorAll('.skin-chip').forEach(c => c.classList.remove('active')); 1307 chip.classList.add('active'); 1308 const el = document.querySelector('the-feed-ingest'); 1309 if (el) el.setAttribute('skin', chip.dataset.skin); 1310 } 1311 1312 // ââ Ingest live preview 1313 function updateIngestPreview() { 1314 const skin = document.getElementById('ingest-skin').value; 1315 const mode = document.getElementById('ingest-mode').value; 1316 const src = `./embed.html?mode=ingest&skin=${skin}&colormode=${mode}`; 1317 document.getElementById('ingest-preview-iframe').src = src; 1318 document.getElementById('ingest-preview-link').href = src; 1319 // Update code snippet 1320 document.getElementById('ingest-code').innerHTML = 1321 `<span class="cmt"><!-- 1. Load the component --></span>\n` + 1322 `<span class="tag"><script</span> <span class="attr">type</span>=<span class="str">"module"</span>\n` + 1323 ` <span class="attr">src</span>=<span class="str">"https://cdn.jsdelivr.net/gh/Stephanada/the-feed@main/ui/the-feed-ingest.js"</span>\n` + 1324 `<span class="tag">></script></span>\n\n` + 1325 `<span class="cmt"><!-- 2. Drop the element --></span>\n` + 1326 `<span class="tag"><the-feed-ingest</span>\n` + 1327 ` <span class="attr">skin</span>=<span class="str">"${skin}"</span>` + 1328 (mode !== 'auto' ? `\n <span class="attr">mode</span>=<span class="str">"${mode}"</span>` : '') + 1329 `\n<span class="tag">></the-feed-ingest></span>`; 1330 } 1331 1332 // ââ Calendar live preview 1333 function updateCalPreview() { 1334 const skin = document.getElementById('cal-skin').value; 1335 const view = document.getElementById('cal-view').value; 1336 const mode = document.getElementById('cal-mode').value; 1337 const views = document.getElementById('cal-views').value; 1338 let src = `./embed.html?mode=calendar&skin=${skin}&view=${view}&colormode=${mode}`; 1339 if (views) src += `&views=${encodeURIComponent(views)}`; 1340 document.getElementById('cal-preview-iframe').src = src; 1341 document.getElementById('cal-preview-link').href = src; 1342 // Update code snippet 1343 let code = 1344 `<span class="tag"><script</span> <span class="attr">type</span>=<span class="str">"module"</span>\n` + 1345 ` <span class="attr">src</span>=<span class="str">"https://cdn.jsdelivr.net/gh/Stephanada/the-feed@main/ui/the-feed-calendar.js"</span>\n` + 1346 `<span class="tag">></script></span>\n\n` + 1347 `<span class="tag"><the-feed-calendar</span>\n` + 1348 ` <span class="attr">skin</span>=<span class="str">"${skin}"</span>\n` + 1349 ` <span class="attr">view</span>=<span class="str">"${view}"</span>`;
1350 if (views) code += `\n <span class="attr">views</span>=<span class="str">"${views}"</span>`; 1351 if (mode !== 'auto') code += `\n <span class="attr">mode</span>=<span class="str">"${mode}"</span>`; 1352 code += `\n<span class="tag">></the-feed-calendar></span>`; 1353 document.getElementById('cal-code').innerHTML = code; 1354 } 1355 1356 // ââ Code tabs 1357 function showTab(id, btn) { 1358 // Panes 1359 document.querySelectorAll('.embed-pane').forEach(p => p.classList.remove('active')); 1360 document.getElementById('tab-' + id).classList.add('active'); 1361 // Tabs 1362 btn.closest('.embed-box').querySelectorAll('.embed-tab').forEach(t => t.classList.remove('active')); 1363 btn.classList.add('active'); 1364 } 1365 1366 // ââ Copy code 1367 function copyCode(paneId) { 1368 const pane = document.getElementById(paneId); 1369 const text = pane.querySelector('pre').innerText; 1370 navigator.clipboard.writeText(text).then(() => { 1371 const btn = pane.querySelector('.copy-btn'); 1372 const orig = btn.textContent; 1373 btn.textContent = 'â Copied!'; 1374 setTimeout(() => btn.textContent = orig, 2000); 1375 }); 1376 } 1377 1378 // ââ Live marquee âââââââââââââââââââââââââââââââââââââââââ 1379 (async function initMarquee() { 1380 const track = document.getElementById('marquee-track'); 1381 if (!track) return; 1382 1383 const API = 'https://the-feed-api.stephan-99b.workers.dev/api/events?limit=50&past=false'; 1384 1385 let events = []; 1386 try { 1387 const res = await fetch(API); 1388 const data = await res.json(); 1389 events = (data.itemListElement || []).map(el => el.item); 1390 } catch (_) {} 1391 1392 if (!events.length) return; // keep skeletons on error 1393 1394 // Format date: "Wed Mar 26" 1395 function fmtDate(iso) { 1396 if (!iso) return ''; 1397 const d = new Date(iso); 1398 return d.toLocaleDateString('en-CA', { weekday: 'short', month: 'short', day: 'numeric', timeZone: 'America/Vancouver' }); 1399 } 1400 1401 // Format price 1402 function fmtPrice(evt) { 1403 const o = evt.offers; 1404 if (!o) return null; 1405 const p = parseFloat((Array.isArray(o) ? o[0] : o).price ?? '0'); 1406 return p === 0 ? 'Free' : `$${p % 1 === 0 ? p : p.toFixed(2)}`; 1407 } 1408 1409 // Build one marquee item 1410 function buildItem(evt) { 1411 const name = evt.name || 'Event'; 1412 const venue = evt.location?.name || ''; 1413 const city = evt.location?.address?.addressLocality || ''; 1414 const where = [venue, city].filter(Boolean).join(' · '); 1415 const date = fmtDate(evt.startDate); 1416 const price = fmtPrice(evt); 1417 const isFree = price === 'Free'; 1418 const url = evt.url || '#'; 1419 1420 const div = document.createElement('div'); 1421 div.className = 'marquee-item'; 1422 1423 const inner = url !== '#' 1424 ? `<a href="${url}" target="_blank" rel="noopener">` 1425 : `<span>`;
1426 const close = url !== '#' ? `</a>` : `</span>`; 1427 1428 div.innerHTML = ` 1429 ${inner} 1430 <span class="marquee-dot" aria-hidden="true"></span> 1431 <span class="marquee-name" title="${name}">${name}</span> 1432 ${where ? `<span class="marquee-meta">${where}</span>` : ''} 1433 ${date ? `<span class="marquee-sep" aria-hidden="true">·</span><span class="marquee-meta">${date}</span>` : ''} 1434 ${price ? `<span class="marquee-price${isFree ? ' free' : ''}">${price}</span>` : ''} 1435 ${close}`; 1436 return div; 1437 } 1438 1439 // Clear skeletons, build items à 2 for seamless loop 1440 track.innerHTML = ''; 1441 const frag1 = document.createDocumentFragment(); 1442 const frag2 = document.createDocumentFragment(); 1443 events.forEach(evt => { 1444 frag1.appendChild(buildItem(evt)); 1445 frag2.appendChild(buildItem(evt)); 1446 }); 1447 track.appendChild(frag1); 1448 track.appendChild(frag2); 1449 1450 // Adjust animation duration based on item count (roughly 5s per item) 1451 const dur = Math.max(30, events.length * 5); 1452 track.style.animationDuration = dur + 's'; 1453 })(); 1454</script>
1454 1455 1456</body> 1457</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.