···969969 {fieldsError && <span class={s.errorText}>{fieldsError}</span>}
970970 {schemaUnresolved && (
971971 <div class={s.alertWarning}>
972972- This lexicon's schema could not be resolved. You can still set up your automation —
973973- field paths for conditions will need to be entered manually.
972972+ This lexicon's schema could not be resolved. You can still set up your automation,
973973+ but field paths for conditions will need to be entered manually.
974974 </div>
975975 )}
976976 </div>
···13451345 <div class={s.catGroupHeader}>
13461346 <span class={s.catDot} data-cat={cat.id} />
13471347 {cat.label}
13481348- <span class={s.catGroupHeaderDesc}>— {cat.description}</span>
13481348+ <span class={s.catGroupHeaderDesc}>- {cat.description}</span>
13491349 </div>
13501350 <div class={s.catTileRow}>
13511351 {cat.actions.map((a) => {
+1-1
app/islands/DeliveryLog.tsx
···191191 class={s.toggleBtn}
192192 onClick={toggleDryRun}
193193 disabled={loading}
194194- title="Test mode — logs what would happen without actually running actions"
194194+ title="Test mode - logs what would happen without actually running actions"
195195 >
196196 <FlaskConical size={14} /> {isDryRun ? "Disable Dry Run" : "Enable Dry Run"}
197197 </button>
···4848}
49495050const defaultDescription =
5151- "Automate the AT Protocol and Bluesky — set up webhooks, create records, and filter Jetstream events by lexicon.";
5151+ "Automation platform for Bluesky and the AT Protocol. Filter Jetstream events by lexicon, deliver webhooks, and create records on your PDS.";
5252const defaultOgImage = `${config.publicUrl}/og-image.png`;
53535454export default jsxRenderer(({ children, title, description, ogImage }, c) => {
···7979 </Container>
8080 </AppShell>,
8181 {
8282- title: "Sign in — Airglow",
8282+ title: "Sign in | Airglow",
8383 description:
8484 "Sign in to Airglow with your AT Protocol identity to create automations and webhooks.",
8585 },
···11import { createRoute } from "honox/factory";
22import { desc, count } from "drizzle-orm";
33import { raw } from "hono/html";
44-import { Webhook, FilePlus2, Filter, Activity } from "../icons.js";
44+import { Code, Filter, Globe, Repeat } from "../icons.js";
55import { getSessionUser } from "@/auth/middleware.js";
66import { db } from "@/db/index.js";
77import { automations } from "@/db/schema.js";
···1212import { Table } from "../components/Table/index.js";
1313import { NsidCode } from "../components/NsidCode/index.js";
1414import ThemeToggle from "../islands/ThemeToggle.js";
1515+import FlowAction from "../islands/FlowAction.js";
1516import * as s from "../styles/pages/landing.css.js";
16171718const jsonLd = raw(
···2122 name: "Airglow",
2223 url: "https://airglow.run",
2324 description:
2424- "Automate the AT Protocol and Bluesky — webhooks, record creation, and Jetstream event filtering.",
2525+ "Automations for the AT Protocol. Subscribe to Jetstream events, match a lexicon, and send a webhook, create a record, or post to Bluesky.",
2526 applicationCategory: "DeveloperApplication",
2627 operatingSystem: "Web",
2728 })}</script>`,
···4041 return c.render(
4142 <AppShell header={<Header user={user} actions={<ThemeToggle />} />}>
4243 {jsonLd}
4343- <Container>
4444- <section class={s.hero}>
4545- <h1 class={s.heroTitle}>Webhooks & Automations for the AT Protocol</h1>
4646- <p class={s.heroSubtitle}>
4747- Automate Bluesky and the AT Protocol network. Listen to Jetstream events, filter by
4848- lexicon, deliver webhooks, create records on your PDS, and track every run.
4949- </p>
5050- {user ? (
5151- <Button href="/dashboard">Go to Dashboard</Button>
5252- ) : (
5353- <Button href="/auth/login">Get Started</Button>
5454- )}
5555- </section>
4444+4545+ <section class={s.hero}>
4646+ <div class={s.bgGrid} aria-hidden="true" />
4747+ <Container>
4848+ <div class={s.heroInner}>
4949+ <h1 class={s.heroTitle}>
5050+ Automations for the
5151+ <br />
5252+ <span class={s.heroTitleAccent}>AT Protocol.</span>
5353+ </h1>
5454+ <p class={s.heroSubtitle}>
5555+ Airglow listens to AT Protocol events for you. Pick a lexicon, set a few
5656+ conditions, and send a webhook, create a record on your PDS, or post to Bluesky.
5757+ </p>
5858+ <div class={s.heroActions}>
5959+ {user ? (
6060+ <Button href="/dashboard">Go to dashboard</Button>
6161+ ) : (
6262+ <Button href="/auth/login">Get started</Button>
6363+ )}
6464+ <Button href="https://tangled.org/exosphere.site/airglow" variant="secondary">
6565+ <Code size={16} /> View source
6666+ </Button>
6767+ </div>
6868+ </div>
6969+ </Container>
7070+ </section>
7171+7272+ <section class={s.flowSection}>
7373+ <Container>
7474+ <div class={s.sectionHead}>
7575+ <div class={s.sectionEyebrow}>How it works</div>
7676+ <h2 class={s.sectionTitle}>Three steps, any AT Protocol event.</h2>
7777+ <p class={s.sectionSub}>
7878+ Airglow subscribes to Jetstream, matches your filter, and runs the action you picked.
7979+ </p>
8080+ </div>
8181+8282+ <div class={s.flowDiagram}>
8383+ <div class={s.flowCard}>
8484+ <div class={s.flowCardHead}>
8585+ <span class={s.flowKind}>Source</span>
8686+ </div>
8787+ <div class={s.flowCardBody}>
8888+ <div class={s.flowCardTitle}>
8989+ <Globe size={18} /> Jetstream
9090+ </div>
9191+ <div class={s.flowCardSub}>AT Protocol events</div>
9292+ <div class={s.flowCardLine}>
9393+ <span class={s.led} />
9494+ streaming events
9595+ </div>
9696+ </div>
9797+ </div>
9898+9999+ <FlowArrow />
561005757- <section>
5858- <h2 class={s.stepsTitle}>Features</h2>
5959- <div class={s.features}>
6060- <div class={s.featureCard}>
6161- <div class={s.featureIcon}>
6262- <Webhook size={28} />
101101+ <div class={s.flowCard}>
102102+ <div class={s.flowCardHead}>
103103+ <span class={s.flowKind}>Filter</span>
104104+ </div>
105105+ <div class={s.flowCardBody}>
106106+ <div class={s.flowCardTitle}>
107107+ <Filter size={18} /> Match a lexicon
108108+ </div>
109109+ <div class={s.flowCardSub}>with field conditions</div>
110110+ <div class={s.flowCode}>
111111+ <div>
112112+ <span class={s.codeOp}>eq</span>{" "}
113113+ <span class={s.codeS}>site.standard.document</span>
114114+ </div>
115115+ <div>
116116+ <span class={s.codeK}>author</span> <span class={s.codeOp}>eq</span>{" "}
117117+ <span class={s.codeS}>airglow.run</span>
118118+ </div>
119119+ </div>
63120 </div>
6464- <h3 class={s.featureTitle}>Webhook Delivery</h3>
6565- <p class={s.featureDesc}>
6666- Receive HTTP POST callbacks instantly when matching events occur on the AT Protocol
6767- network via Jetstream.
6868- </p>
69121 </div>
7070- <div class={s.featureCard}>
122122+123123+ <FlowArrow />
124124+125125+ <FlowAction />
126126+ </div>
127127+ </Container>
128128+ </section>
129129+130130+ <section class={s.features}>
131131+ <Container>
132132+ <div class={s.sectionHead}>
133133+ <div class={s.sectionEyebrow}>Built to run</div>
134134+ <h2 class={s.sectionTitle}>Three guarantees, no surprises.</h2>
135135+ </div>
136136+ <div class={s.featuresGrid}>
137137+ <div class={s.feature}>
71138 <div class={s.featureIcon}>
7272- <FilePlus2 size={28} />
139139+ <Filter size={18} />
73140 </div>
7474- <h3 class={s.featureTitle}>Record Creation</h3>
141141+ <h3 class={s.featureTitle}>Precise conditions</h3>
75142 <p class={s.featureDesc}>
7676- Automatically create records on your PDS when events match. Use templates with
7777- placeholders to build records from event data.
143143+ Match on any record field with operators like <code>eq</code>, <code>contains</code>
144144+ , and <code>starts</code>.
78145 </p>
79146 </div>
8080- <div class={s.featureCard}>
147147+ <div class={s.feature}>
81148 <div class={s.featureIcon}>
8282- <Filter size={28} />
149149+ <Repeat size={18} />
83150 </div>
8484- <h3 class={s.featureTitle}>Smart Filtering</h3>
151151+ <h3 class={s.featureTitle}>Reliable delivery</h3>
85152 <p class={s.featureDesc}>
8686- Listen to specific record types by NSID. Add field-level conditions with operators
8787- like equals, starts with, or contains.
153153+ Every run is logged with its status and errors. Failed automations retry
154154+ automatically.
88155 </p>
89156 </div>
9090- <div class={s.featureCard}>
157157+ <div class={s.feature}>
91158 <div class={s.featureIcon}>
9292- <Activity size={28} />
159159+ <Code size={18} />
93160 </div>
9494- <h3 class={s.featureTitle}>Delivery Tracking</h3>
161161+ <h3 class={s.featureTitle}>Open source</h3>
95162 <p class={s.featureDesc}>
9696- Full delivery log with status codes, retry attempts, and error details. Know exactly
9797- what happened with every event.
163163+ MIT-licensed. Run it yourself or use the hosted version.{" "}
164164+ <a href="https://tangled.org/exosphere.site/airglow">See the code →</a>
98165 </p>
99166 </div>
100167 </div>
101101- </section>
168168+ </Container>
169169+ </section>
102170103103- {topLexicons.length > 0 && (
104104- <section class={s.topLexicons}>
105105- <h2 class={s.stepsTitle}>Popular Lexicons</h2>
106106- <Table>
107107- <thead>
108108- <tr>
109109- <th>NSID</th>
110110- <th>Automations</th>
111111- </tr>
112112- </thead>
113113- <tbody>
114114- {topLexicons.map((row) => (
115115- <tr key={row.lexicon}>
116116- <td>
117117- <a href={`/lexicons/${row.lexicon}`}>
118118- <NsidCode>{row.lexicon}</NsidCode>
119119- </a>
120120- </td>
121121- <td>{row.count}</td>
171171+ {topLexicons.length > 0 && (
172172+ <section class={s.lexiconsWrap} id="lexicons">
173173+ <Container>
174174+ <div class={s.lexiconsGrid}>
175175+ <div>
176176+ <div class={s.sectionEyebrow}>The ecosystem</div>
177177+ <h2 class={s.lexiconsIntroTitle}>Popular lexicons</h2>
178178+ <p class={s.lexiconsIntroText}>
179179+ Airglow works with any lexicon: Bluesky, community, or your own. Here are a few
180180+ that people automate against.
181181+ </p>
182182+ </div>
183183+ <Table>
184184+ <thead>
185185+ <tr>
186186+ <th>NSID</th>
187187+ <th class={s.countCell}>Automations</th>
122188 </tr>
123123- ))}
124124- </tbody>
125125- </Table>
126126- </section>
127127- )}
128128-129129- <section class={s.steps}>
130130- <h2 class={s.stepsTitle}>How It Works</h2>
131131- <ol class={s.stepsList}>
132132- <li class={s.step}>
133133- <div class={s.stepNumber}>1</div>
134134- <h3 class={s.stepTitle}>Sign in</h3>
135135- <p class={s.stepDesc}>Authenticate with your AT Protocol identity via OAuth.</p>
136136- </li>
137137- <li class={s.step}>
138138- <div class={s.stepNumber}>2</div>
139139- <h3 class={s.stepTitle}>Automate</h3>
140140- <p class={s.stepDesc}>
141141- Choose a lexicon, set conditions, and add actions like webhooks or record creation.
142142- </p>
143143- </li>
144144- <li class={s.step}>
145145- <div class={s.stepNumber}>3</div>
146146- <h3 class={s.stepTitle}>Receive</h3>
147147- <p class={s.stepDesc}>
148148- Get signed webhook deliveries and automatic record creation in real time with
149149- retries.
150150- </p>
151151- </li>
152152- </ol>
189189+ </thead>
190190+ <tbody>
191191+ {topLexicons.map((row) => (
192192+ <tr key={row.lexicon}>
193193+ <td>
194194+ <a href={`/lexicons/${row.lexicon}`}>
195195+ <NsidCode>{row.lexicon}</NsidCode>
196196+ </a>
197197+ </td>
198198+ <td class={s.countCell}>{row.count}</td>
199199+ </tr>
200200+ ))}
201201+ </tbody>
202202+ </Table>
203203+ </div>
204204+ </Container>
153205 </section>
154154- </Container>
206206+ )}
155207 </AppShell>,
156208 {
157157- title: "Airglow — Webhooks & Automations for the AT Protocol",
209209+ title: "Airglow | Automations for the AT Protocol",
158210 description:
159159- "Automate Bluesky and the AT Protocol. Set up webhooks, create records, and filter Jetstream events by lexicon.",
211211+ "Automations for the AT Protocol. Subscribe to Jetstream events, match a lexicon, and send a webhook, create a record, or post to Bluesky.",
160212 },
161213 );
162214});
215215+216216+function FlowArrow() {
217217+ return (
218218+ <div class={s.flowArrow} aria-hidden="true">
219219+ <svg viewBox="0 0 100 12" preserveAspectRatio="none">
220220+ <line
221221+ x1="0"
222222+ y1="6"
223223+ x2="100"
224224+ y2="6"
225225+ stroke="var(--color-border)"
226226+ stroke-width="1"
227227+ stroke-dasharray="3 4"
228228+ vector-effect="non-scaling-stroke"
229229+ />
230230+ </svg>
231231+ <span class={s.faDot} />
232232+ <span class={`${s.faDot} ${s.faDot2}`} />
233233+ </div>
234234+ );
235235+}
+2-2
app/routes/lexicons/[nsid].tsx
···3838 </div>
3939 </Container>
4040 </AppShell>,
4141- { title: "Not Found — Airglow" },
4141+ { title: "Not Found | Airglow" },
4242 );
4343 }
4444···179179 </Container>
180180 </AppShell>,
181181 {
182182- title: `${nsid} — Airglow`,
182182+ title: `${nsid} | Airglow`,
183183 description: description
184184 ? `${description.replace(/\.?$/, ".")} Browse automations for ${nsid} on Airglow.`
185185 : `Browse automations using the ${nsid} AT Protocol lexicon on Airglow.`,
+2-2
app/routes/lexicons/index.tsx
···5555 </Container>
5656 </AppShell>,
5757 {
5858- title: "AT Protocol Lexicons — Airglow",
5858+ title: "AT Protocol Lexicons | Airglow",
5959 description:
6060- "Browse AT Protocol lexicons with active automations on Airglow. Discover webhooks and automations for Bluesky and the AT Protocol network.",
6060+ "Browse AT Protocol lexicons with active automations on Airglow. Discover webhooks and automations for Bluesky and the AT Protocol.",
6161 },
6262 );
6363});