PageSourceSearch

https://the-feed-ui.pages.dev/

html the-feed-ui.pages.dev collected 2026-10-03 05:57:47 UTC 61,492 bytes, 1,457 lines download raw bytes

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>&lt;the-feed-event&gt;</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 &amp; 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 &amp; 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 &amp; 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 &amp; 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>&lt;the-feed-calendar&gt;</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>&lt;iframe&gt;</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">&lt;!-- 1. Load the component --&gt;</span>
1026<span class="tag">&lt;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">&gt;&lt;/script&gt;</span>
1029
1030<span class="cmt">&lt;!-- 2. Drop the element --&gt;</span>
1031<span class="tag">&lt;the-feed-ingest</span>
1032  <span class="attr">skin</span>=<span class="str">"broadcast"</span>
1033<span class="tag">&gt;&lt;/the-feed-ingest&gt;</span>
1034
1035<span class="cmt">&lt;
1035!-- Users enter their own OpenAI key in the widget. --&gt;</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">&lt;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">&gt;&lt;/script&gt;</span>
1089
1090<span class="tag">&lt;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">&gt;&lt;/the-feed-calendar&gt;</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">&lt;!-- ─────────────────────────────────────────────────────────────
1111  No-code / iframe embed — works on Weebly, Squarespace, Wix,
1112  Showit, or any builder that accepts HTML/iframe blocks but
1113  blocks &lt;script type="module"&gt;.
1114
1115  The hosted embed.html page loads the component for you.
1116  Paste this into an "HTML block" or "Embed code" widget.
1117────────────────────────────────────────────────────────────── --&gt;</span>
1118
1119<span class="cmt">&lt;!-- Submit widget (ingest) --&gt;</span>
1120<span class="tag">&lt;iframe</span>
1121  <span class="attr">src</span>=<span class="str">"https://the-feed-ui.pages.dev/embed.html?mode=ingest&amp;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">&gt;&lt;/iframe&gt;</span>
1128
1129<span class="cmt">&lt;!-- Event calendar (mosaic default, all 4 views) --&gt;</span>
1130<span class="tag">&lt;iframe</span>
1131  <span class="attr">src</span>=<span class="str">"https://the-feed-ui.pages.dev/embed.html?mode=calendar&amp;skin=default&amp;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">&gt;&lt;/iframe&gt;</span>
1138
1139<span class="cmt">&lt;!-- Token-scoped calendar — only your events, dark skin --&gt;</span>
1140<span class="tag">&lt;iframe</span>
1141  <span class="attr">src</span>=<span class="str">"https://the-feed-ui.pages.dev/embed.html?mode=calendar&amp;token=your-token&amp;skin=poster&amp;view=list&amp;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">&gt;&lt;/iframe&gt;</span>
1148
1149<span class="cmt">&lt;!-- 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--&gt;</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">&lt;!-- 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. --&gt;</span>
1169
1170<span class="tag">&lt;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">&gt;&lt;/the-feed-ingest&gt;</span>
1174
1175<span class="cmt">&lt;!-- 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  }' --&gt;</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">&lt;!-- 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. --&gt;</span>
1192
1193<span class="cmt">&lt;!-- Page 1: venue homepage --&gt;</span>
1194<span class="tag">&lt;the-feed-ingest</span> <span class="attr">token</span>=<span class="str">"org-token"</span><span class="tag">&gt;&lt;/the-feed-ingest&gt;</span>
1195
1196<span class="cmt">&lt;!-- Page 2: events submission form --&gt;</span>
1197<span class="tag">&lt;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">&gt;&lt;/the-feed-ingest&gt;</span>
1198
1199<span class="cmt">&lt;!-- Page 3: partner venue --&gt;</span>
1200<span class="tag">&lt;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">&gt;&lt;/the-feed-ingest&gt;</span>
1201
1202<span class="cmt">&lt;!-- All three share your org's key stored in KV.
1203     Revoke the token any time from the admin API. --&gt;</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">&lt;!-- Or use the event display component --&gt;</span>
1218<span class="tag">&lt;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">&gt;&lt;/script&gt;</span>
1221
1222<span class="tag">&lt;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">&gt;&lt;/the-feed-event&gt;</span>
1226
1227<span class="cmt">&lt;!-- RSS / iCal --&gt;
1228GET /api/feed.rss
1229GET /api/feed.ics
1230GET /api/feed.json?venue=The+Fillmore&amp;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>&lt;script&gt;</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">&lt;!-- 1. Load the component --&gt;</span>\n` +
1322      `<span class="tag">&lt;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">&gt;&lt;/script&gt;</span>\n\n` +
1325      `<span class="cmt">&lt;!-- 2. Drop the element --&gt;</span>\n` +
1326      `<span class="tag">&lt;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">&gt;&lt;/the-feed-ingest&gt;</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">&lt;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">&gt;&lt;/script&gt;</span>\n\n` +
1347      `<span class="tag">&lt;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">&gt;&lt;/the-feed-calendar&gt;</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.