PageSourceSearch

https://foundrynet.io/

html foundrynet.io collected 2026-10-03 21:12:23 UTC 118,570 bytes, 2,090 lines download raw bytes

1<!doctype html>
2<html lang="en">
3<head>
4<!-- Privacy-friendly analytics by Plausible -->
5<script async src="https://plausible.io/js/pa-QdPlluyoOE1jovqrkv-ed.js"></script>
vendor: 1 bytes, line 5
5
6<script>
7  window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};
8  plausible.init()
9</script>
9
10<meta charset="utf-8">
11<meta name="viewport" content="width=device-width,initial-scale=1">
12<meta name="google-site-verification" content="-Vk03AgXINQMCfsTNJqtCnbcdC92p3WtyyDDOCnckAU" />
13<title>Forge: Canonical Data Model for Industrial Equipment</title>
14<link rel="canonical" href="https://foundrynet.io/">
15<meta name="description" content="985-field canonical data model for industrial telemetry. ISA-95 categorized. Deterministic resolution across <span data-fn="prod.oem_families">18</span> OEM families. Docker container.">
16<meta property="og:title" content="Forge: Canonical Data Model for Industrial Equipment">
17<meta property="og:description" content="985-field canonical data model for industrial telemetry. ISA-95 categorized. Deterministic resolution across <span data-fn="prod.oem_families">18</span> OEM families. Docker container.">
18<link rel="preconnect" href="https://fonts.googleapis.com">
19<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
20<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
21<script type="application/ld+json">
22{
23  "@context": "https://schema.org",
24  "@graph": [
25    {
26      "@type": "TechArticle",
27      "@id": "https://foundrynet.io/#article",
28      "headline": "How to connect an AI agent to industrial equipment",
29      "name": "Foundry Labs: Connect your AI agent to industrial equipment",
30      "description": "The integration project kills 40% of industrial AI deployments. Forge collapses it into one MCP call: <span data-fn="prod.protocols">14</span> protocols, <span data-fn="prod.oem_families">18</span> manufacturer families and <span data-fn="prod.mappings">2,086</span> cross-OEM mappings normalize raw vendor telemetry into one canonical schema any AI agent can read.",
31      "url": "https://foundrynet.io/",
32      "mainEntityOfPage": {"@type": "WebPage", "@id": "https://foundrynet.io/"},
33      "inLanguage": "en",
34      "dateModified": "2026-08-17",
35      "author": {
36          "@type": "Organization",
37          "name": "Foundry Labs LLC",
38          "url": "https://foundrynet.io",
39          "email": "[email protected]",
40          "logo": "https://foundrynet.io/logo-mark.png"
41        },
42      "publisher": {
43          "@type": "Organization",
44          "name": "Foundry Labs LLC",
45          "url": "https://foundrynet.io",
46          "email": "[email protected]",
47          "logo": "https://foundrynet.io/logo-mark.png"
48        },
49      "about": {
50          "@type": "SoftwareApplication",
51          "name": "Forge by Foundry Labs",
52          "url": "https://forge.foundrynet.io"
53        },
54      "proficiencyLevel": "Expert",
55      "dependencies": "An MCP-compatible AI client (Claude, Cursor, LangChain, CrewAI, AutoGen) and a Forge API key. No new hardware, no PLC changes, read-only access to existing equipment.",
56      "keywords": "industrial AI infrastructure, MCP server for manufacturing, equipment canonical data model, cross-vendor equipment data, AI agent industrial equipment, Modbus OPC UA MTConnect, canonical schema manufacturing"
57    },
58    {
59      "@type": "SoftwareApplication",
60      "@id": "https://forge.foundrynet.io#software",
61      "name": "Forge by Foundry Labs",
62      "url": "https://forge.foundrynet.io",
63      "applicationCategory": "BusinessApplication",
64      "applicationSubCategory": "Industrial AI Infrastructure",
65      "operatingSystem": "Cloud (HTTP API, MCP)",
66      "description": "The industrial AI infrastructure kernel. Forge resolves telemetry from any manufacturer into one canonical schema and exposes it to any AI agent over 32 MCP tools. 14 industrial protocols, <span data-fn="prod.oem_families">18</span> OEM families, <span data-fn="prod.mappings">2,086</span> cross-vendor mappings. Read-only by design.",
67      "softwareVersion": "3.4.4",
68      "featureList": [
69        "32 MCP tools over Streamable HTTP",
70        "14 industrial protocols: MQTT, Modbus TCP, Modbus RTU, OPC UA, SNMP, BACnet, ROS2, J1939/CAN, MAVLink, MTConnect, EtherNet/IP, PROFINET, REST, CSV",
71        "<span data-fn="prod.mappings">2,086</span> cross-OEM tag mappings across <span data-fn="prod.oem_families">18</span> manufacturer families",
72        "Physics-validated normalization to a canonical schema",
73        "Failure prediction and plain-English root-cause diagnosis",
74        "OEE, shift reports and work-order tools",
75        "Guardrails, supervision and tamper-evident integrity records",
76        "Read-only: never sends commands to equipment",
77        "Free tier available"
78      ],
79      "offers": {
80        "@type": "Offer",
81        "price": "0",
82        "priceCurrency": "USD",
83        "description": "Free tier available. Paid plans metered from $0.30 per 1,000 normalized telemetry events, or a flat platform fee for production.",
84        "url": "https://forge.foundrynet.io/pricing"
85      },
86      "provider": {
87          "@type": "Organization",
88          "name": "Foundry Labs LLC",
89          "url": "https://foundrynet.io",
90          "email": "[email protected]",
91          "logo": "https://foundrynet.io/logo-mark.png"
92        }
93    }
94  ]
95}
96</script>
vendor: 1 bytes, line 96
96
97<script type="application/ld+json">
98{
99  "@context": "https://schema.org",
100  "@type": "HowTo",
101  "@id": "https://foundrynet.io/#howto-connect-agent",
102  "name": "Connect an AI agent to industrial equipment with Forge",
103  "description": "Connect any MCP-compatible AI agent to any machine on the factory floor, from any manufacturer, over any of 14 industrial protocols. Forge resolves raw vendor telemetry into one canonical schema the agent can read, in a single command.",
104  "totalTime": "PT30S",
105  "estimatedCost": {
106    "@type": "MonetaryAmount",
107    "currency": "USD",
108    "value": "0"
109  },
110  "supply": [
111    {"@type": "HowToSupply", "name": "A Forge API key (free tier, no card required)"},
112    {"@type": "HowToSupply", "name": "Equipment reachable over MQTT, Modbus TCP, Modbus RTU, OPC UA, SNMP, BACnet, ROS2, J1939/CAN, MAVLink, MTConnect, EtherNet/IP, PROFINET, REST or CSV"}
113  ],
114  "tool": [
115    {"@type": "HowToTool", "name": "An MCP-compatible AI client such as Claude Code, Cursor, LangChain, CrewAI or AutoGen"}
116  ],
117  "step": [
118    {
119      "@type": "HowToStep",
120      "position": 1,
121      "name": "Get a Forge API key",
122      "text": "Create an account at forge.foundrynet.io and copy your API key. The free tier needs no credit card and no sales call.",
123      "url": "https://forge.foundrynet.io/pricing"
124    },
125    {
126      "@type": "HowToStep",
127      "position": 2,
128      "name": "Add the Forge MCP server to your agent",
129      "text": "Register the Forge MCP endpoint with your client over Streamable HTTP, passing your API key as a bearer token: claude mcp add --transport http forge https://mcp.foundrynet.io/mcp --header 'Authorization: Bearer YOUR_API_KEY'",
130      "url": "https://foundrynet.io/docs"
131    },
132    {
133      "@type": "HowToStep",
134      "position": 3,
135      "name": "Normalize a reading from your equipment",
136      "text": "Call normalize_telemetry with a raw payload and the OEM name. Vendor tags such as Spindle_Speed or spd_rpm become the canonical field spindle_speed_rpm, returned with a coverage percentage and a confidence score. Works for any of the 18 supported OEM families.",
137      "url": "https://foundrynet.io/docs"
138    },
139    {
140      "@type": "HowToStep",
141      "position": 4,
142      "name": "Predict, diagnose and govern",
143      "text": "Your agent now has 32 MCP tools: failure prediction, root-cause diagnosis, OEE, shift reports, work orders and guardrail pre-checks. Every reading and action is hash-chained and tamper-evident. Forge is read-only, so an agent can recommend but only your operator can act.",
144      "url": "https://foundrynet.io/docs"
145    }
146  ]
147}
148</script>
148
149<style>
150  :root {
151    --bg:        #08080d;
152    --bg-elev:   #0c0f17;
153    --bg-term:   #000000;
154    --bg-code:   #050609;
155    --rule:      #16202e;
156    --rule-soft: #0f1722;
157    --fg:        #d8dfeb;
158    --fg-mid:    #8d99ad;
159    --fg-dim:    #5a6678;
160    --jade:      #1a9058;
161    --jade-soft: #2dbb78;
162    --jade-glow: rgba(45, 187, 120, 0.35);
163    --amber:     #f0a030;
164    --amber-glow: rgba(240, 160, 48, 0.35);
165    --max:       1020px;
166  }
167  * { box-sizing: border-box; }
168  html, body { margin: 0; padding: 0; background: var(--bg); color: var(--fg); }
169  body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
170         font-size: 15px; line-height: 1.7; font-weight: 400;
171         -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
172  h1, h2, h3 { font-family: 'JetBrains Mono', ui-monospace, monospace;
173               font-weight: 500; margin: 0; letter-spacing: -0.01em; }
174  h1 { font-size: clamp(36px, 5.5vw, 60px); line-height: 1.05;
175       color: var(--fg); font-weight: 600; letter-spacing: -0.025em; }
176  h2 { font-size: clamp(22px, 2.6vw, 30px); color: var(--fg);
177       margin-bottom: 18px; font-weight: 500; letter-spacing: -0.01em; }
178  h3 { font-size: 11px; color: var(--jade-soft); margin-bottom: 10px;
179       font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; }
180  p  { margin: 0 0 14px; color: var(--fg); }
181  a  { color: var(--jade-soft); text-decoration: none;
182       transition: color .15s ease; }
183  a:hover { color: var(--amber); }
184  ul, ol { color: var(--fg-mid); padding-left: 20px; line-height: 1.85; }
185  li { margin: 4px 0; }
186  code { font-family: 'JetBrains Mono', monospace; font-size: 0.92em;
187         color: var(--amber); background: transparent; }
188
189  section { padding: 90px 28px; max-width: var(--max); margin: 0 auto; }
190  section.first { padding-top: 130px; }
191
192  .label { font-family: 'JetBrains Mono', monospace; font-size: 11px;
193           color: var(--fg-dim); font-weight: 400; letter-spacing: 0.22em;
194           text-transform: uppercase; }
195
196  /* HERO ---------------------------------------------------------- */
197  .hero p.tagline { font-family: 'JetBrains Mono', ui-monospace, monospace;
198                    font-size: clamp(19px, 2.8vw, 28px); line-height: 1.28;
199                    color: var(--fg); opacity: 0.72; font-weight: 400;
200                    letter-spacing: -0.015em; margin: 18px 0 0; max-width: 680px; }
201  .hero p.sub { font-size: 22px; color: var(--fg-mid); margin: 22px 0 32px;
202                max-width: 640px; font-weight: 300; line-height: 1.5; }
203  .proof { color: var(--jade-soft); font-size: 12px; letter-spacing: 0.18em;
204           text-transform: uppercase; margin-bottom: 44px; font-weight: 400;
205           font-family: 'JetBrains Mono', monospace; }
206  .proof .dot { color: var(--rule); margin: 0 12px; }
207  .ctas { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 64px; }
208  .btn { display: inline-block; padding: 13px 24px; border-radius: 4px;
209         font-family: 'JetBrains Mono', monospace; font-size: 13px;
210         font-weight: 500; letter-spacing: 0.02em;
211         transition: all .18s ease; cursor: pointer; }
212  .btn.primary { background: var(--jade); color: #050609;
213                 box-shadow: 0 0 0 1px var(--jade), 0 4px 24px rgba(26, 144, 88, 0.18); }
214  .btn.primary:hover { background: var(--jade-soft); color: #050609;
215                       box-shadow: 0 0 0 1px var(--jade-soft), 0 6px 32px rgba(45, 187, 120, 0.32); }
216  .btn.secondary { background: transparent; color: var(--fg);
217                   border: 1px solid var(--rule); }
218  .btn.secondary:hover { border-color: var(--jade-soft); color: var(--jade-soft); }
219
220  /* SIGNUP PANEL --------------------------------------------------- */
221  .signup { display: none; margin: 0 0 64px; padding: 24px;
222            border: 1px solid var(--rule); border-left: 2px solid var(--jade);
223            background: var(--bg-elev); border-radius: 4px;
224            max-width: 640px; }
225  .signup.open { display: block; }
226  .signup .row { display: flex; flex-wrap: wrap; gap: 10px;
227                 margin-top: 8px; }
228  .signup label { font-family: 'JetBrains Mono', monospace; font-size: 11px;
229                  color: var(--fg-dim); letter-spacing: 0.18em;
230                  text-transform: uppercase; }
231  .signup input[type=email] { flex: 1 1 280px; padding: 12px 14px;
232                              background: var(--bg-code); color: var(--fg);
233                              font-family: 'JetBrains Mono', monospace;
234                              font-size: 13px; border: 1px solid var(--rule);
235                              border-radius: 4px; outline: none;
236                              transition: border-color .15s; }
237  .signup input[type=email]:focus { border-color: var(--jade-soft); }
238  .signup .help { color: var(--fg-dim); font-size: 12px; margin-top: 10px; }
239  .signup-options { display: grid; grid-template-columns: 1fr 1fr;
240                    gap: 14px; }
241  .signup-options > div { background: var(--bg-code);
242                          border: 1px solid var(--rule); border-radius: 5px;
243                          padding: 18px; display: flex; flex-direction: column;
244                          gap: 10px; }
245  .signup-options .free-option { border-left: 2px solid var(--jade); }
246  .signup-options .paid-option { border-left: 2px solid var(--amber); }
247  .signup-options h3 { font-family: 'JetBrains Mono', monospace;
248                       font-size: 13px; color: var(--fg);
249                       margin: 0; font-weight: 500; letter-spacing: 0.02em; }
250  .signup-options p  { color: var(--fg-mid); font-size: 12.5px;
251                       margin: 0 0 4px; line-height: 1.5; }
252  .signup-options input[type=email] { width: 100%; padding: 10px 12px;
253                                      background: var(--bg); color: var(--fg);
254                                      border: 1px solid var(--rule);
255                                      border-radius: 4px;
256                                      font-family: 'JetBrains Mono', monospace;
257                                      font-size: 12px; outline: none; }
258  .signup-options input[type=email]:focus { border-color: var(--jade-soft); }
259  .signup-options button { width: 100%; }
260  @media (max-width: 600px) { .signup-options { grid-template-columns: 1fr; } }
261  .signup .err  { color: var(--amber); font-size: 12px; margin-top: 10px;
262                  display: none; font-family: 'JetBrains Mono', monospace; }
263  .signup .err.show { display: block; }
264  .signup .keybox { margin-top: 10px; padding: 14px; background: var(--bg-code);
265                    border: 1px dashed var(--jade); border-radius: 4px;
266                    color: var(--amber); font-family: 'JetBrains Mono', monospace;
267                    font-size: 13px; word-break: break-all; line-height: 1.5; }
268  .signup .copy { margin-top: 10px; }
269  .signup .ok-line { color: var(--jade-soft); font-family: 'JetBrains Mono', monospace;
270                     font-size: 12px; letter-spacing: 0.06em; margin-top: 14px; }
271  .signup h3 { margin-bottom: 8px; }
272
273  /* TERMINAL MOCKUP ----------------------------------------------- */
274  .term { background: var(--bg-term); border: 1px solid var(--rule);
275          border-radius: 6px; padding: 26px 28px;
276          font-family: 'JetBrains Mono', monospace; font-size: 13.5px;
277          line-height: 1.95; max-width: 740px;
278          box-shadow: 0 24px 80px rgba(0,0,0,.6),
279                      0 0 0 1px rgba(45, 187, 120, 0.04); }
280  .term .you, .term .feed { color: var(--jade-soft);
281                            text-shadow: 0 0 10px var(--jade-glow); }
282  .term .you::before, .term .feed::before { content: "> "; color: var(--jade);
283                                            text-shadow: 0 0 10px var(--jade-glow); }
284  .term .ok   { color: var(--jade-soft); opacity: 0.95; }
285  .term .ok::before { content: "✓ "; color: var(--jade); }
286  .term .fire { color: var(--amber); font-weight: 500;
287                text-shadow: 0 0 12px var(--amber-glow); }
288  .term .fire::before { content: "🔥 "; }
289  .term .gap { display: block; height: 12px; }
290
291  /* BEYOND MACHINES (operations pitch) ---------------------------- */
292  .beyond { display: grid; grid-template-columns: 1fr 1fr; gap: 36px;
293            margin-top: 40px; }
294  .beyond .col h3 { font-family: 'JetBrains Mono', monospace;
295                    color: var(--jade-soft); font-size: 12px;
296                    font-weight: 500; letter-spacing: 0.18em;
297                    text-transform: uppercase; margin-bottom: 14px; }
298  .beyond .col p { color: var(--fg-mid); font-size: 14.5px;
299                   line-height: 1.65; margin: 0 0 18px;
300                   font-weight: 400; }
301  .term.compact { padding: 18px 20px; font-size: 12.5px;
302                  line-height: 1.85; max-width: none; box-shadow: none;
303                  border: 1px solid var(--rule); }
304  .beyond-foot { margin-top: 32px; color: var(--fg-mid);
305                 font-size: 14px; max-width: 720px;
306                 font-style: normal; }
307  .beyond-foot strong { color: var(--fg); font-weight: 500; }
308  .beyond-sub { color: var(--fg-mid); font-size: 16px; line-height: 1.55;
309                margin: 6px 0 0; max-width: 760px; font-weight: 300; }
310  @media (max-width: 720px) {
311    .beyond { grid-template-columns: 1fr; }
312  }
313
314  /* HOW IT WORKS -------------------------------------------------- */
315  .grid3 { display: grid; gap: 36px;
316           grid-template-columns: repeat(3, 1fr); margin-top: 40px; }
317  .col h3 { display: flex; align-items: baseline; gap: 12px; }
318  .col h3 .num { color: var(--amber); font-size: 12px; letter-spacing: 0.1em; }
319  .col p { color: var(--fg-mid); font-size: 14.5px; line-height: 1.65;
320           margin: 0; font-weight: 400; }
321
322  /* CONNECTIONS --------------------------------------------------- */
323  .pillrow { display: flex; flex-wrap: wrap; align-items: center;
324             gap: 8px 8px; margin-bottom: 22px; }
325  .pillrow .label { width: 100%; margin-bottom: 8px; }
326  .pill { font-family: 'JetBrains Mono', monospace; font-size: 12.5px;
327          color: var(--fg-mid); background: transparent;
328          border: 1px solid var(--rule); border-radius: 3px;
329          padding: 7px 13px; font-weight: 400;
330          transition: all .15s ease; }
331  .pill:hover { color: var(--jade-soft); border-color: rgba(45,187,120,0.35); }
332  .footnote { color: var(--fg-dim); font-size: 13px; line-height: 1.7;
333              margin-top: 24px; max-width: 720px; font-weight: 400; }
334
335  /* PRICING (outlined cards) -------------------------------------- */
336  .grid4 { display: grid; gap: 14px;
337           grid-template-columns: repeat(4, 1fr); margin: 32px 0 28px; }
338  .card { background: transparent;
339          border: 1px solid var(--rule);
340          border-top: 1px solid rgba(26, 144, 88, 0.45);
341          border-radius: 4px; padding: 26px 22px;
342          transition: border-color .2s ease, transform .2s ease; }
343  .card:hover { transform: translateY(-2px); border-top-color: var(--jade-soft); }
344  .card .name  { color: var(--fg-mid); font-size: 11px; font-weight: 400;
345                 letter-spacing: 0.18em; text-transform: uppercase;
346                 margin-bottom: 14px; font-family: 'JetBrains Mono', monospace; }
347  .card .price { color: var(--fg); font-size: 28px; font-weight: 500;
348                 margin-bottom: 12px; font-family: 'JetBrains Mono', monospace;
349                 letter-spacing: -0.02em; }
350  .card .price .unit { color: var(--fg-dim); font-size: 13px; font-weight: 400;
351                       letter-spacing: 0; margin-left: 2px; }
352  .card .desc  { color: var(--fg-mid); font-size: 13px; line-height: 1.55;
353                 font-weight: 400; }
354  .free-line { color: var(--fg-mid); font-size: 13px; line-height: 1.85;
355               font-weight: 400; max-width: 680px; margin-top: 22px; }
356  .free-line strong { color: var(--fg); font-family: 'JetBrains Mono', monospace;
357                      font-weight: 500; }
358
359  /* PRICING TIERS ------------------------------------------------- */
360  .tier-grid { display: grid; gap: 16px; margin: 32px 0 12px;
361               grid-template-columns: repeat(3, 1fr); }
362  .tier { background: var(--bg-elev); border: 1px solid var(--rule);
363          border-top: 1px solid var(--rule);
364          border-radius: 5px; padding: 26px 24px;
365          display: flex; flex-direction: column; gap: 16px;
366          transition: border-color .2s ease, transform .2s ease; }
367  .tier:hover { transform: translateY(-2px); }
368  .tier-starter   { border-top: 2px solid var(--jade);
369                    box-shadow: 0 0 0 1px rgba(26,144,88,.18); }
370  .tier-growth    { border-top: 1px solid rgba(45,187,120,.45); }
371  .tier-enterprise{ border-top: 1px solid rgba(240,160,48,.45); }
372  .tier-head { display: flex; justify-content: space-between; align-items: baseline; }
373  .tier .name { font-family: 'JetBrains Mono', monospace; font-size: 11px;
374                color: var(--jade-soft); letter-spacing: 0.22em;
375                text-transform: uppercase; font-weight: 500; }
376  .tier-enterprise .name { color: var(--amber); }
377  .tier .badge { font-family: 'JetBrains Mono', monospace; font-size: 10px;
378                 color: var(--fg-dim); letter-spacing: 0.06em; }
379  .tier .badge.recommended { color: var(--jade-soft); }
380  .tier .price-list { display: flex; flex-direction: column; gap: 6px; }
381  .tier .line { display: flex; justify-content: space-between; align-items: baseline;
382                font-family: 'JetBrains Mono', monospace; font-size: 13px;
383                color: var(--fg-mid); padding: 4px 0;
384                border-bottom: 1px dashed var(--rule-soft); }
385  .tier .line:last-child { border-bottom: none; }
386  .tier .line .what { color: var(--fg-mid); }
387  .tier .line .amt  { color: var(--fg); font-weight: 500; }
388  .tier-growth .line .amt,
389  .tier-enterprise .line .amt { color: var(--jade-soft); }
390  .tier .desc { color: var(--fg-mid); font-size: 13px; line-height: 1.55;
391                margin: 0; }
392  .tier input[type=email] { width: 100%; padding: 10px 12px;
393                            background: var(--bg-code); color: var(--fg);
394                            border: 1px solid var(--rule); border-radius: 4px;
395                            font-family: 'JetBrains Mono', monospace;
396                            font-size: 12px; outline: none; box-sizing: border-box; }
397  .tier input[type=email]:focus { border-color: var(--jade-soft); }
398  .tier .cta { display: inline-block; padding: 11px 16px; border-radius: 4px;
399               font-family: 'JetBrains Mono', monospace; font-size: 12px;
400               font-weight: 500; letter-spacing: 0.04em; text-align: center;
401               transition: all .15s ease; cursor: pointer; border: none; }
402  .tier-starter .cta { background: var(--jade); color: #050609;
403                       box-shadow: 0 0 0 1px var(--jade), 0 4px 18px rgba(26,144,88,.18); }
404  .tier-starter .cta:hover { background: var(--jade-soft); color: #050609; }
405  .tier-growth .cta, .tier-enterprise .cta {
406    background: transparent; color: var(--fg); border: 1px solid var(--rule); }
407  .tier-growth .cta:hover, .tier-enterprise .cta:hover {
408    border-color: var(--jade-soft); color: var(--jade-soft); }
409  .tier .hint { color: var(--fg-dim); font-size: 11px; margin: 0;
410                font-family: 'JetBrains Mono', monospace; letter-spacing: 0.04em; }
411  @media (max-width: 760px) { .tier-grid { grid-template-columns: 1fr; } }
412
413  /* INTEGRATORS --------------------------------------------------- */
414  .integrators p { color: var(--fg-mid); max-width: 700px;
415                   margin-bottom: 28px; font-size: 15.5px; font-weight: 400;
416                   line-height: 1.7; }
417  .small-note { color: var(--fg-dim); font-size: 12.5px;
418                margin-top: 18px; font-style: italic; font-weight: 400; }
419
420  /* MCP / CODE ---------------------------------------------------- */
421  pre.code, pre { background: var(--bg-term); border: 1px solid var(--rule);
422                  border-radius: 6px; padding: 18px 22px;
423                  font-family: 'JetBrains Mono', monospace; font-size: 12.5px;
424                  color: var(--fg); overflow-x: auto; line-height: 1.55;
425                  margin: 12px 0; max-width: 740px;
426                  box-shadow: 0 16px 48px rgba(0,0,0,.4); }
427  pre code { color: var(--fg); font-size: inherit; }
428  pre.code .key { color: var(--amber); }
429  pre.code .str { color: var(--jade-soft); }
430  .mcp-summary { color: var(--fg-mid); font-size: 14px; margin-bottom: 22px;
431                 font-weight: 400; }
432  .mcp-links a { margin-right: 24px; font-family: 'JetBrains Mono', monospace;
433                 font-size: 13px; }
434
435  /* DOCS SECTION (nested below landing) --------------------------- */
436  .docs-divider { border: 0; border-top: 1px solid var(--rule);
437                  max-width: var(--max); margin: 60px auto 0; }
438  .docs-intro { color: var(--fg-mid); font-size: 14.5px;
439                margin-bottom: 32px; max-width: 720px; }
440  .docs h2 { margin-top: 56px; margin-bottom: 14px; }
441  .docs h2:first-of-type { margin-top: 0; }
442  .docs h3 { color: var(--jade-soft); margin-top: 30px; margin-bottom: 8px; }
443  .docs p { color: var(--fg-mid); font-size: 14px; line-height: 1.7; }
444  .docs ol, .docs ul { color: var(--fg-mid); font-size: 14px; }
445
446  /* LEGAL SECTION (TOS + Privacy, anchored at #terms / #privacy) -- */
447  .legal h1 { font-family: 'JetBrains Mono', monospace;
448              color: var(--jade-soft); font-size: 24px;
449              font-weight: 500; margin: 0 0 8px;
450              letter-spacing: -0.01em; }
451  .legal h2 { color: var(--amber); font-size: 16px;
452              margin: 40px 0 12px; font-family: 'JetBrains Mono', monospace; }
453  .legal h2:first-of-type { margin-top: 24px; }
454  .legal h3 { color: var(--fg); font-size: 14px;
455              margin: 24px 0 8px; letter-spacing: 0;
456              text-transform: none; font-family: 'JetBrains Mono', monospace; }
457  .legal p  { color: var(--fg-mid); font-size: 14px; line-height: 1.8;
458              margin: 0 0 14px; }
459  .legal .updated { color: var(--fg-dim); font-size: 12px;
460                    margin-bottom: 32px;
461                    font-family: 'JetBrains Mono', monospace;
462                    letter-spacing: 0.06em; }
463  .legal hr { border: none; border-top: 1px solid var(--rule);
464              margin: 48px 0; }
465  .endpoint { border-top: 1px solid var(--rule); padding-top: 20px;
466              margin-top: 26px; }
467  .endpoint:first-of-type { margin-top: 18px; }
468  .endpoint .method { display: inline-block; color: var(--bg);
469                      background: var(--jade); padding: 1px 9px;
470                      border-radius: 3px; font-weight: 600; font-size: 11px;
471                      vertical-align: 2px; font-family: 'JetBrains Mono', monospace;
472                      letter-spacing: 0.05em; }
473  .endpoint .method.get { background: var(--amber); }
474  .endpoint .path { color: var(--fg); font-size: 14px; }
475  .endpoint .desc { color: var(--fg-mid); margin: 6px 0 12px;
476                    font-size: 14px; line-height: 1.65; }
477
478  /* FOOTER (single line) ----------------------------------------- */
479  footer { border-top: 1px solid var(--rule); padding: 28px;
480           max-width: var(--max); margin: 80px auto 0;
481           color: var(--fg-dim); font-size: 12px; text-align: center;
482           font-family: 'JetBrains Mono', monospace; font-weight: 400;
483           letter-spacing: 0.02em; line-height: 1.8; }
484  footer .sep { color: var(--rule); margin: 0 12px; }
485  footer a { color: var(--fg-mid); }
486  footer a:hover { color: var(--jade-soft); }
487
488  /* RESPONSIVE --------------------------------------------------- */
489  @media (max-width: 760px) {
490    section { padding: 70px 22px; }
491    section.first { padding-top: 90px; }
492    .grid3 { grid-template-columns: 1fr; gap: 32px; }
493    .grid4 { grid-template-columns: 1fr 1fr; }
494    .term { font-size: 12px; padding: 20px; }
495    .hero p.tagline { font-size: 17px; margin-top: 14px; }
496    .hero p.sub { font-size: 18px; }
497    footer { font-size: 11px; }
498    footer .sep { margin: 0 8px; }
499  }
500  @media (max-width: 460px) {
501    .grid4 { grid-template-columns: 1fr; }
502    footer .sep.collapse { display: block; visibility: hidden; height: 4px; }
503  }
504  /* FN-NAV-CSS-OPEN */
505
506  /* TOP NAV ─────────────────────────────────────────────── */
507  .topnav { position:sticky; top:0; z-index:8000;
508            background:rgba(8,8,13,0.92); backdrop-filter:blur(10px);
509            -webkit-backdrop-filter:blur(10px);
510            border-bottom:1px solid var(--rule-soft);
511            padding:12px 28px; display:flex; align-items:center;
512            gap:32px; }
513  .topnav-brand { font-family:'JetBrains Mono',monospace;
514                  font-size:13px; font-weight:600; letter-spacing:0.02em;
515                  color:var(--fg); text-decoration:none; }
516  .topnav-brand:hover { color:var(--jade-soft); }
517  .topnav-dropdown { position:relative; }
518  .topnav-dropdown summary { font-family:'JetBrains Mono',monospace;
519                             font-size:12px; color:var(--fg-mid);
520                             letter-spacing:0.04em; cursor:pointer;
521                             list-style:none; padding:6px 10px;
522                             border-radius:3px; user-select:none;
523                             transition:color .15s ease; }
524  .topnav-dropdown summary::-webkit-details-marker { display:none; }
525  .topnav-dropdown summary:hover { color:var(--jade-soft); }
526  .topnav-dropdown summary::after { content:" ▾"; font-size:9px;
527                                    color:var(--fg-dim); margin-left:3px; }
528  .topnav-dropdown[open] summary { color:var(--jade-soft); }
529  .topnav-menu { position:absolute; top:calc(100% + 6px); left:0;
530                 min-width:190px; background:var(--bg-elev);
531                 border:1px solid var(--rule); border-radius:4px;
532                 padding:6px; box-shadow:0 8px 24px rgba(0,0,0,0.45);
533                 display:flex; flex-direction:column; gap:2px; }
534  .topnav-menu a { font-family:'JetBrains Mono',monospace; font-size:12px;
535                   color:var(--fg-mid); padding:8px 12px;
536                   text-decoration:none; border-radius:3px;
537                   transition:all .12s ease; }
538  .topnav-menu a:hover { background:var(--bg-code); color:var(--jade-soft); }
539  .topnav-menu a.active { color:var(--jade-soft);
540                          border-left:2px solid var(--jade-soft); padding-left:10px; }
541  .topnav-spacer { flex:1; }
542  .topnav-cta { font-family:'JetBrains Mono',monospace; font-size:12px;
543                color:var(--jade-soft); text-decoration:none;
544                letter-spacing:0.04em; padding:6px 10px;
545                transition:color .15s ease; }
546  .topnav-cta:hover { color:var(--amber); }
547  @media (max-width:640px) {
548    .topnav { padding:10px 14px; gap:8px 12px; flex-wrap:wrap; }
549    .topnav-spacer { flex-basis:100%; height:0; }
550    .topnav-cta { display:inline-block; font-size:12px; padding:4px 7px; }
551  }
552
553  /* FN-NAV-CSS-CLOSE */
554
555  /* per-machine cost calculator (added 2026-05-29) */
556  .price-calculator {
557    background: var(--bg-elev, #0c0f17);
558    border: 1px solid var(--rule, #16202e);
559    border-radius: 8px;
560    padding: 22px 26px;
561    margin: 0 auto 28px;
562    max-width: 760px;
563  }
564  .price-calculator .calc-intro {
565    margin: 0 0 14px;
566    font-size: 13px;
567    color: var(--fg-mid, #8d99ad);
568    letter-spacing: .04em;
569    text-transform: uppercase;
570  }
571  .price-calculator .calc-grid {
572    display: grid;
573    grid-template-columns: repeat(3, 1fr);
574    gap: 14px;
575  }
576  .price-calculator .calc-cell {
577    display: flex;
578    flex-direction: column;
579    gap: 4px;
580    padding: 14px 16px;
581    background: var(--bg, #08080d);
582    border: 1px solid var(--rule, #16202e);
583    border-radius: 6px;
584  }
585  .price-calculator .calc-cell .cad {
586    font-size: 11.5px;
587    color: var(--fg-dim, #5a6678);
588    letter-spacing: .08em;
589    text-transform: uppercase;
590  }
591  .price-calculator .calc-cell .est {
592    font-size: 18px;
593    color: var(--fg, #d8dfeb);
594    font-weight: 500;
595  }
596  .price-calculator .calc-foot {
597    margin: 14px 0 0;
598    font-size: 12px;
599    color: var(--fg-dim, #5a6678);
600  }
601  @media (max-width: 640px) {
602    .price-calculator .calc-grid { grid-template-columns: 1fr; }
603  }
604
605  /* ── Phase 1 additions: sandbox / coverage / per-asset / about ─────── */
606
607  /* SANDBOX SECTION ----------------------------------------------- */
608  .sandbox-section .sandbox-lede {
609    max-width: 720px; color: var(--fg-mid); font-size: 15px;
610    margin: 4px 0 28px; line-height: 1.65;
611  }
612  .sandbox-steps { list-style: none; padding: 0; margin: 0;
613                   display: flex; flex-direction: column; gap: 18px; }
614  .sandbox-steps > li { display: flex; gap: 18px;
615                        background: var(--bg-elev);
616                        border: 1px solid var(--rule);
617                        border-left: 2px solid var(--jade);
618                        border-radius: 5px; padding: 22px 24px; }
619  .sandbox-steps .step-num { font-family: 'JetBrains Mono', monospace;
620                             font-size: 13px; color: var(--jade-soft);
621                             letter-spacing: 0.18em; padding-top: 2px;
622                             min-width: 28px; }
623  .sandbox-steps .step-body { flex: 1; min-width: 0; }
624  .sandbox-steps h3 { font-family: 'JetBrains Mono', monospace;
625                      font-size: 13px; color: var(--fg);
626                      letter-spacing: 0.02em; text-transform: none;
627                      margin: 0 0 6px; font-weight: 500; }
628  .sandbox-steps p { color: var(--fg-mid); margin: 0 0 12px;
629                     font-size: 14px; line-height: 1.6; }
630  .sandbox-resp { margin-top: 12px; font-size: 12.5px; }
631  .sandbox-foot { margin-top: 22px; color: var(--fg-dim);
632                  font-size: 12.5px; max-width: 720px; line-height: 1.65; }
633
634  /* COVERAGE SECTION ---------------------------------------------- */
635  .coverage-section .coverage-lede {
636    max-width: 720px; color: var(--fg-mid); font-size: 14px;
637    margin: 4px 0 26px; line-height: 1.65;
638  }
639  .oem-grid { display: grid; grid-template-columns: repeat(4, 1fr);
640              gap: 10px; margin: 0 0 22px; }
641  .oem { display: flex; flex-direction: column; gap: 6px;
642         background: var(--bg-elev); border: 1px solid var(--rule);
643         border-radius: 4px; padding: 14px 16px; }
644  .oem-name { font-family: 'JetBrains Mono', monospace;
645              font-size: 13px; color: var(--fg); font-weight: 500;
646              letter-spacing: 0.01em; }
647  .oem-tier { font-family: 'JetBrains Mono', monospace;
648              font-size: 10.5px; letter-spacing: 0.12em;
649              text-transform: uppercase; font-weight: 500; }
650  .oem-tier.prod  { color: var(--jade-soft); }
651  .oem-tier.beta  { color: var(--amber); }
652  .oem-tier.early { color: var(--fg-dim); }
653  .coverage-foot { color: var(--fg-mid); font-size: 12.5px;
654                   max-width: 720px; line-height: 1.65;
655                   border-top: 1px dashed var(--rule); padding-top: 16px; }
656  @media (max-width: 760px) { .oem-grid { grid-template-columns: repeat(2, 1fr); } }
657
658  /* PRICING (Phase 1, per-asset headline) ------------------------ */
659  .pricing-lede { max-width: 720px; color: var(--fg-mid);
660                  font-size: 14px; margin: 4px 0 26px; line-height: 1.65; }
661  .pricing-tier-grid { grid-template-columns: 1fr 1.15fr 1fr; }
662  .tier-free       { border-top: 1px solid var(--rule); }
663  .tier-perasset   { border-top: 2px solid var(--jade);
664                     box-shadow: 0 0 0 1px rgba(26,144,88,.22),
665                                 0 8px 32px rgba(26,144,88,.10); }
666  .tier .price-display { display: flex; align-items: baseline; gap: 6px;
667                         font-family: 'JetBrains Mono', monospace; }
668  .tier .price-display .amount { font-size: 32px; color: var(--fg);
669                                 font-weight: 500; letter-spacing: -0.02em; }
670  .tier .price-display .unit   { font-size: 12px; color: var(--fg-dim);
671                                 letter-spacing: 0; }
672  .tier-features { list-style: none; padding: 0; margin: 6px 0 4px;
673                   display: flex; flex-direction: column; gap: 6px; }
674  .tier-features li { color: var(--fg-mid); font-size: 13px;
675                      line-height: 1.5; padding: 4px 0;
676                      border-bottom: 1px dashed var(--rule-soft); }
677  .tier-features li:last-child { border-bottom: none; }
678  .tier-features strong { color: var(--fg); font-weight: 500; }
679  .tier-features code   { font-size: 12.5px; }
680  .gate-tag { font-family: 'JetBrains Mono', monospace; font-size: 9.5px;
681              letter-spacing: 0.14em; text-transform: uppercase;
682              color: var(--amber); border: 1px solid var(--amber);
683              padding: 1px 6px; border-radius: 3px; margin-left: 4px; }
684  .tier-perasset .cta { background: var(--jade); color: #050609;
685                        box-shadow: 0 0 0 1px var(--jade),
686                                    0 4px 18px rgba(26,144,88,.20); }
687  .tier-perasset .cta:hover { background: var(--jade-soft); }
688  .onramp { display: flex; flex-direction: column; gap: 6px;
689            margin: 24px 0 4px; padding: 14px 18px;
690            background: var(--bg-code); border: 1px solid var(--rule);
691            border-radius: 4px; }
692  .onramp-label { font-family: 'JetBrains Mono', monospace; font-size: 11px;
693                  color: var(--fg-mid); letter-spacing: 0.18em;
694                  text-transform: uppercase; }
695  .onramp-rates { font-family: 'JetBrains Mono', monospace; font-size: 12.5px;
696                  color: var(--fg); }
697  .onramp-rates code { color: var(--jade-soft); }
698  .onramp-foot { color: var(--fg-dim); font-size: 12px; line-height: 1.55; }
699  @media (max-width: 900px) { .pricing-tier-grid { grid-template-columns: 1fr; } }
700
701  /* ABOUT + SECURITY ---------------------------------------------- */
702  .about-section .about-grid { display: grid;
703                               grid-template-columns: 1fr 1fr; gap: 28px; }
704  .about-section h3 { font-family: 'JetBrains Mono', monospace;
705                      font-size: 11px; color: var(--jade-soft);
706                      letter-spacing: 0.18em; text-transform: uppercase;
707                      font-weight: 500; margin-bottom: 14px; }
708  .about-section .about-col p { color: var(--fg-mid); font-size: 14px;
709                                line-height: 1.65; }
710  .about-section .muted { color: var(--fg-dim); font-size: 13px; }
711  .security-list { list-style: none; padding: 0; margin: 0 0 12px;
712                   display: flex; flex-direction: column; gap: 6px; }
713  .security-list li { color: var(--fg-mid); font-size: 13.5px;
714                      line-height: 1.55; padding: 4px 0;
715                      border-bottom: 1px dashed var(--rule-soft); }
716  .security-list li:last-child { border-bottom: none; }
717  .security-list strong { color: var(--fg); font-weight: 500; }
718  @media (max-width: 760px) { .about-section .about-grid { grid-template-columns: 1fr; } }
719
720  /* hide the now-superseded calculator (left in DOM for nobody, replaced) */
721  .price-calculator { display: none; }
722
723  /* 4-DOOR HUB ----------------------------------------------------- */
724  .hub { display:grid; gap:16px; grid-template-columns:1fr 1fr; margin-top:30px; }
725  .product-card { border:1px solid var(--rule); border-top:2px solid var(--jade);
726                  border-radius:6px; padding:22px 22px 18px; background:var(--bg-elev);
727                  display:flex; flex-direction:column; }
728  .product-card h4 { font-family:'JetBrains Mono',monospace; font-size:16px;
729                     font-weight:600; color:var(--fg); margin:0 0 8px; letter-spacing:-.01em; }
730  .product-card p  { color:var(--fg-mid); font-size:13.5px; line-height:1.55; margin:0 0 14px; flex:1; }
731  .product-card a  { font-family:'JetBrains Mono',monospace; font-size:12px; letter-spacing:.02em; }
732  @media (max-width:640px){ .hub { grid-template-columns:1fr; } }
733
734  /* STAT BAR (verified sources) ----------------------------------- */
735  .statbar { border:1px solid var(--rule); border-radius:6px;
736             background:var(--bg-elev); padding:26px 28px; margin-top:8px; }
737  .statbar .stat-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:24px; }
738  .statbar .stat .n { font-family:'JetBrains Mono',monospace; font-size:30px;
739                      color:var(--fg); font-weight:600; letter-spacing:-0.02em; }
740  .statbar .stat .t { color:var(--fg-mid); font-size:13px; line-height:1.5; margin-top:6px; }
741  .statbar .stat a { font-size:11px; color:var(--fg-dim); letter-spacing:0.04em;
742                     border-bottom:1px dotted var(--rule); }
743  .statbar .stat a:hover { color:var(--jade-soft); border-bottom-color:var(--jade-soft); }
744  .statbar .stat-foot { margin:22px 0 0; color:var(--fg); font-size:14px; font-weight:500; }
745  @media (max-width:760px){ .statbar .stat-grid { grid-template-columns:1fr; gap:18px; } }
746
747  /* 5-TIER PRICING ------------------------------------------------- */
748  .tier5 { display:grid; gap:14px; grid-template-columns:repeat(3,1fr); margin-top:30px; }
749  .tier5 .t5 { background:var(--bg-elev); border:1px solid var(--rule);
750               border-top:1px solid rgba(26,144,88,.4); border-radius:5px;
751               padding:24px 22px; display:flex; flex-direction:column; gap:12px;
752               transition:transform .2s ease, border-color .2s ease; }
753  .tier5 .t5:hover { transform:translateY(-2px); border-top-color:var(--jade-soft); }
754  .tier5 .t5.feature { border-top:2px solid var(--jade);
755                       box-shadow:0 0 0 1px rgba(26,144,88,.18); }
756  .tier5 .t5.rescue { border-top:1px solid rgba(240,160,48,.5); }
757  .tier5 .t5 .n { font-family:'JetBrains Mono',monospace; font-size:11px;
758                  color:var(--jade-soft); letter-spacing:0.2em; text-transform:uppercase; }
759  .tier5 .t5.rescue .n { color:var(--amber); }
760  .tier5 .t5 .p { font-family:'JetBrains Mono',monospace; font-size:24px;
761                  color:var(--fg); font-weight:600; letter-spacing:-0.02em; }
762  .tier5 .t5 .p .u { font-size:12px; color:var(--fg-dim); font-weight:400; letter-spacing:0; }
763  .tier5 .t5 .d { color:var(--fg-mid); font-size:13px; line-height:1.55; flex:1; }
764  .tier5 .t5 .cta { display:inline-block; text-align:center; padding:10px 14px;
765                    border-radius:4px; font-family:'JetBrains Mono',monospace;
766                    font-size:12px; font-weight:500; letter-spacing:0.03em;
767                    border:1px solid var(--rule); color:var(--fg); }
768  .tier5 .t5 .cta:hover { border-color:var(--jade-soft); color:var(--jade-soft); }
769  .tier5 .t5.feature .cta { background:var(--jade); color:#050609; border-color:var(--jade); }
770  .tier5 .t5.feature .cta:hover { background:var(--jade-soft); color:#050609; }
771  /* FN-PLATFORM-TIER: the licensing lane is a different motion (no checkout,
772     negotiated, usage-based) so it spans the grid instead of sitting as a 7th
773     orphan card in a 3-column row. */
774  .tier5 .t5.platform { grid-column:1 / -1; border-top:2px solid var(--jade);
775    background:linear-gradient(180deg,rgba(45,187,120,.05),var(--bg-elev) 60%); }
776  .tier5 .t5.platform .n { color:var(--jade-soft); }
777  .tier5 .t5.platform .cta { background:var(--jade); color:#050609; border-color:var(--jade); }
778  .tier5 .t5.platform .cta:hover { background:var(--jade-soft); color:#050609; }
779  @media (max-width:560px){ .tier5 .t5.platform { grid-column:auto; } }
780  @media (max-width:860px){ .tier5 { grid-template-columns:1fr 1fr; } }
781  @media (max-width:560px){ .tier5 { grid-template-columns:1fr; } }
782
783  /* INTELLIGENCE ROWS --------------------------------------------- */
784  .intel { display:flex; flex-direction:column; gap:0; margin-top:30px;
785           border:1px solid var(--rule); border-radius:6px; overflow:hidden; }
786  .intel .row { display:grid; grid-template-columns:130px 1fr; gap:20px;
787                padding:20px 24px; border-bottom:1px solid var(--rule-soft); }
788  .intel .row:last-child { border-bottom:none; }
789  .intel .row .k { font-family:'JetBrains Mono',monospace; font-size:12px;
790                   color:var(--jade-soft); letter-spacing:0.14em; text-transform:uppercase;
791                   padding-top:2px; }
792  .intel .row .v { color:var(--fg-mid); font-size:14px; line-height:1.6; }
793  .intel .row .v strong { color:var(--fg); font-weight:500; }
794  @media (max-width:640px){ .intel .row { grid-template-columns:1fr; gap:6px; } }
795
796  /* PRODUCT STATS STRIP -------------------------------------------- */
797  .statstrip { display:flex; flex-wrap:wrap; justify-content:center; align-items:baseline;
798               gap:8px 20px; margin-top:28px; font-family:'JetBrains Mono',monospace;
799               font-size:13px; color:var(--fg-mid); border:1px solid var(--rule);
800               border-radius:6px; padding:18px 22px; background:var(--bg-elev); }
801  .statstrip b { color:var(--fg); font-weight:600; }
802  .statstrip .sep { color:var(--rule); }
803  .src { font-size:inherit; }
804
805  /* Lead capture */
806  .leadform { background:var(--bg-elev); border:1px solid var(--rule); border-top:2px solid var(--jade);
807              border-radius:8px; padding:36px 32px; margin-top:22px; }
808  .leadform > p { margin:0 0 4px; color:var(--fg-mid); }
809  .leadform form { display:flex; flex-direction:column; gap:12px; max-width:520px; margin-top:20px; position:relative; }
810  .leadform input, .leadform select, .leadform textarea {
811              width:100%; padding:12px 14px; background:var(--bg-code); border:1px solid var(--rule);
812              border-radius:4px; color:var(--fg); font-family:'JetBrains Mono',monospace; font-size:13px; }
813  .leadform textarea { resize:vertical; font-family:'Inter',sans-serif; }
814  .leadform select:invalid { color:var(--fg-dim); }
815  .leadform input:focus, .leadform select:focus, .leadform textarea:focus {
816              outline:none; border-color:var(--jade-soft); }
817  .leadform .btn { align-self:flex-start; border:none; }
818  .leadform .hp { position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }
819  .leadform .note { color:var(--fg-dim); font-size:12px; margin-top:14px; }
820  .leadform .lead-status { color:var(--jade-soft); font-family:'JetBrains Mono',monospace;
821              font-size:13px; margin-top:12px; }
822
823/* ── responsive hardening: mobile horizontal-overflow fixes ─────────────── */
824/* grid/flex children default to min-width:auto and refuse to shrink below their
825   min-content width; a single long token then widens the whole document. */
826.card{min-width:0;overflow-wrap:anywhere}
827.pill{max-width:100%;overflow-wrap:anywhere}
828table.map{display:block;overflow-x:auto;max-width:100%}
829pre,pre.code{max-width:100%}
830/* the footer template has no whitespace between links, so the line cannot wrap;
831   a zero-width space after each separator gives it a break opportunity. */
832footer .sep::after{content:"\200B"}
833/* root-page navs only got flex-wrap inside a <768px media query, so at exactly
834   768px the nav ran 3px past the viewport instead of wrapping. */
835.topnav{flex-wrap:wrap}
836</style>
837<!-- FN-FAVICON -->
838<link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16 32x32 48x48">
839<link rel="icon" href="/icon-512.png" type="image/png" sizes="512x512">
840<link rel="apple-touch-icon" href="/apple-touch-icon.png">
841<!-- /FN-FAVICON -->
842</head>
843<body>
844<!-- FN-NAV-OPEN -->
845<nav class="topnav">
846  <a class="topnav-brand" href="/">FoundryNet</a>
847  <details class="topnav-dropdown">
848    <summary>Industries</summary>
849    <div class="topnav-menu">
850      <a href="/cnc">Manufacturing</a>
851      <a href="/datacenters">Data cent
851ers</a>
852      <a href="/energy">Energy</a>
853      <a href="/coldchain">Cold chain</a>
854    </div>
855  </details>
856  <span class="topnav-spacer"></span>
857  <a class="topnav-cta" href="https://forge.foundrynet.io">Forge</a>
858  <a class="topnav-cta" href="/cdm/">Data Model</a>
859  <a class="topnav-cta" href="/sprint">Sprint</a>
860  <a class="topnav-cta" href="/licensing">Licensing</a>
861  <a class="topnav-cta" href="https://forge.foundrynet.io/pricing">Pricing</a>
862  <a class="topnav-cta" href="/docs">Docs</a>
863  <a class="topnav-cta" href="/technology">Technology</a>
864</nav>
865<!-- FN-NAV-CLOSE -->
866<!-- FN-NAV-CLOSE -->
867
868<!-- ── 1. HERO ─────────────────────────────────────────────────────── -->
869<section class="hero first">
870  <h1>Canonical Data Model for Industrial Equipment</h1>
871  <p class="tagline">985 fields. <span data-fn="prod.oem_families">18</span> OEM families. ISA-95 categorized. Deterministic resolution. Ships as a Docker container.</p>
872  <p class="sub">The semantic dictionary your platform framework needs. Take any AI agent and make it work on any machine from any manufacturer: <span data-fn="prod.protocols">14</span> protocols, one canonical schema, governed data from any machine on the floor.</p>
873  <p class="hero-cta" style="margin:22px 0 4px;display:flex;gap:12px;flex-wrap:wrap;">
874    <a href="/docs" class="btn primary">Developer Sandbox, Free &rarr;</a>
875    <a href="/licensing" class="btn secondary">Platform License &rarr;</a>
876  </p>
877  <!-- FN-AUDIENCE-SPLIT -->
878  <style>
879  .aud{display:grid;grid-template-columns:1fr 1fr;gap:0;margin:26px 0 4px;max-width:820px;
880    border:1px solid var(--rule);border-radius:10px;overflow:hidden;}
881  @media(max-width:720px){.aud{grid-template-columns:1fr}}
882  .aud .a{padding:20px 22px;background:var(--bg-elev);border-right:1px solid var(--rule-soft);}
883  .aud .a:last-child{border-right:0;background:rgba(45,187,120,.045);}
884  @media(max-width:720px){.aud .a{border-right:0;border-bottom:1px solid var(--rule-soft)}
885                          .aud .a:last-child{border-bottom:0}}
886  .aud .who{display:block;font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.2em;
887    text-transform:uppercase;color:var(--fg-dim);margin-bottom:8px;}
888  .aud .a:last-child .who{color:var(--jade-soft);}
889  .aud .what{margin:0;font-size:15.5px;line-height:1.6;color:var(--fg-mid);}
890  .aud .what b{color:var(--fg);font-weight:500;}
891  </style>
892  <div class="aud">
893    <div class="a">
894      <span class="who">For developers</span>
895      <p class="what"><b>Normalize any vendor in 30 seconds.</b> One API call, one MCP tool, no integration project. <a href="/docs">Try the free sandbox &rarr;</a></p>
896    </div>
897    <div class="a">
898      <span class="who">For platforms</span>
899      <p class="what"><b>License the engine.</b> Ship a cross-vendor canonical data model as your feature. Next quarter. <a href="/licensing">See platform licensing &rarr;</a></p>
900    </div>
901  </div>
902  <!-- /FN-AUDIENCE-SPLIT -->
903  <p class="hero-readonly"><b>Read-only.</b> Forge reads data from your equipment. It never sends commands. Your machines are safe.</p>
904  <div class="ctas">
905    <a href="/licensing" class="btn primary">License the engine →</a>
906    <a href="/docs" class="btn secondary">Try the free sandbox →</a>
907    <!-- FN-DEMO-CTA --><a href="https://calendly.com/foundry25/30min?utm_source=foundrynet&amp;utm_medium=site&amp;utm_content=home-hero" class="btn secondary" target="_blank" rel="noopener">Book a 20-minute technical intro →</a><!-- /FN-DEMO-CTA -->
908  </div>
909
910  <!-- ── TRY IT NOW: evaluating Forge IS deploying Forge ──────────────── -->
911  <style>
912  .trynow{margin:40px 0 8px;padding:24px;border:1px solid var(--jade);border-radius:12px;
913    background:var(--bg-code);box-shadow:0 0 0 1px rgba(16,185,129,.06),0 18px 50px -30px rgba(16,185,129,.35);}
914  .trynow-head{font-family:'JetBrains Mono',monospace;font-size:18px;font-weight:600;color:var(--fg);
915    margin:0 0 4px;letter-spacing:-.01em;}
916  .trynow-sub{color:var(--fg-mid);font-size:13px;margin:0 0 16px;}
917  .trynow pre.code{margin:0;overflow-x:auto;}
918  .trynow .code.out{margin-top:12px;border-left:2px solid var(--jade);}
919  .trynow-resp-label{display:inline-block;font-family:'JetBrains Mono',monospace;font-size:10px;
920    letter-spacing:.14em;text-transform:uppercase;color:var(--jade-soft);margin:14px 0 2px;}
921  .trynow-actions{display:flex;flex-wrap:wrap;align-items:center;gap:14px;margin-top:18px;}
922  .trynow-note{color:var(--fg-mid);font-size:13.5px;line-height:1.55;margin:16px 0 0;}
923  .trynow-note b{color:var(--fg);font-weight:600;}
924
925  /* READ-ONLY SAFETY ---------------------------------------------- */
926  .hero-readonly{display:inline-block;margin:18px 0 0;padding:10px 16px;
927    border:1px solid var(--jade);border-radius:8px;background:rgba(16,185,129,.05);
928    color:var(--fg-mid);font-size:14px;line-height:1.55;}
929  .hero-readonly b{color:var(--jade-soft);font-weight:600;}
930  .readonly-callout{margin:38px 0 0;padding:26px 28px;border:1px solid var(--jade);
931    border-left:3px solid var(--jade);border-radius:12px;background:var(--bg-code);
932    box-shadow:0 0 0 1px rgba(16,185,129,.06),0 18px 50px -30px rgba(16,185,129,.35);}
933  .readonly-callout .ro-head{font-family:'JetBrains Mono',monospace;font-size:19px;
934    font-weight:600;color:var(--fg);margin:0 0 10px;letter-spacing:-.01em;}
935  .readonly-callout p{margin:0;color:var(--fg-mid);font-size:15px;line-height:1.65;}
936  .readonly-callout p + p{margin-top:10px;}
937  .readonly-callout b{color:var(--fg);font-weight:600;}
938  </style>
939  <div class="trynow">
940    <div class="trynow-head">Try it now. Your data, 30 seconds, free.</div>
941    <div class="trynow-sub">Paste a reading from your own equipment. No key needed to see it work.</
941div>
942    <pre class="code"><code>curl -X POST https://forge.foundrynet.io/v1/normalize \
943  -H "Content-Type: application/json" \
944  -d '{"oem": "haas", "data": {"Spindle_Speed": 8500, "CoolantTemp": 24.1}}'</code></pre>
945    <span class="trynow-resp-label">response</span>
946    <pre class="code out"><code>{
947  "normalized": {
948    "spindle_speed_rpm": 8500,
949    "sensor_readings.coolant_temp": 24.1
950  },
951  "coverage_pct": 100,
952  "confidence": 0.97
953}</code></pre>
954    <div class="trynow-actions">
955      <a href="/quickstart" class="btn primary">Get your API key →</a>
956      <a href="/docs" class="btn secondary">See it work →</a>
957    </div>
958    <p class="trynow-note">No sales call. No proof of concept. No integration project. <b>Paste your data, see the result. That IS the deployment.</b></p>
959  </div>
960
961  <div class="term">
962    <div class="you">claude mcp add --transport http forge https://mcp.foundrynet.io/mcp --header 'Authorization: Bearer YOUR_API_KEY'</div>
963    <span class="gap"></span>
964    <div class="you">forge.normalize_telemetry({ data: {"Spindle_Speed": 2200, "spd_load": 85}, oem: "fanuc" })</div>
965    <div class="ok">{ spindle_speed_rpm: 2200, spindle_load_pct: 85, coverage: 100%, confidence: 0.97 }</div>
966    <span class="gap"></span>
967    <div class="feed">Your agent understands the machine. Took 30 seconds.</div>
968  </div>
969  <p class="trynow-note" style="margin-top:14px;">Need the key that goes in
970  <code>YOUR_API_KEY</code>? <a href="/quickstart">Get one free in 10 seconds</a>: email address, no card, 3 machines.</p>
971</section>
972
973<!-- ── 2. THE PROBLEM (3 cards, cited) ───────────────────────────────── -->
974<section>
975  <h3>The problem</h3>
976  <h2>Industrial AI keeps failing. Here's why.</h2>
977  <div class="grid3" style="margin-top:34px;">
978    <div class="card">
979      <div class="desc" style="font-weight:600;color:var(--fg);font-size:15px;">
979Every vendor names the same measurement differently</div>
980      <p style="margin:8px 0 0;">A Fanuc reports spindle speed one way, a Haas another, a Siemens a third, in different units and scales. A frontier model can usually interpret them. But &ldquo;usually&rdquo; isn&rsquo;t a spec: ask twice and the interpretation can change, and nobody owns whether it&rsquo;s right. 79% of manufacturing leaders say integration challenges and poor data quality limit AI&rsquo;s real impact (<a class="src" href="https://www.l2l.com/blog/manufacturing-data-report-press-release" target="_blank" rel="noopener">L2L 2026</a>).</p>
981    </div>
982    <div class="card">
983      <div class="desc" style="font-weight:600;color:var(--fg);font-size:15px;">Most data isn&rsquo;t ready for agents</div>
984      <p style="margin:8px 0 0;">Only 15% of enterprises are fully prepared to run agentic AI in production, and the most-cited barrier is data quality and lineage &mdash; knowing what a value means and where it came from (<a class="src" href="https://www.fivetran.com/press/fivetran-launches-2026-agentic-ai-readiness-index-revealing-gap-between-enterprise-investment-and-data-preparedness-for-agentic-ai" target="_blank" rel="noopener">Fivetran 2026</a>). A bare &ldquo;4.2&rdquo; is ambiguous until someone declares the unit. Forge makes that declaration explicit and carries it with the value.</p>
985    </div>
986    <div class="card">
987      <div class="desc" style="font-weight:600;color:var(--fg);font-size:15px;">No audit trail, no production</div>
988      <p style="margin:8px 0 0;">Gartner expects over 40% of agentic AI projects to be canceled by 2027, citing rising costs, unclear business value, and inadequate risk controls (<a class="src" href="https://www.gartner.com/en/newsroom/press-releases/2025-06-25-gartner-predicts-over-40-percent-of-agentic-ai-projects-will-be-canceled-by-end-of-2027" target="_blank" rel="noopener">Gartner</a>). In an industrial setting, an agent decision nobody can reconstruct is a liability. Forge addresses the risk-control piece directly: every reading is validated, versioned, and hash-verifiable.</p>
989    </div>
990  </div>
991</section>
992
993<!-- ── 3. THE SOLUTION (PREDICT / DIAGNOSE / GOVERN) ─────────────────── -->
994<section>
995  <h3>What you license</h3>
996  <h2>A verified vocabulary your platform embeds.</h2>
997  <p class="beyond-sub">Not another platform to run beside the one you already have. Forge is the
998  layer that makes your customers' data mean the same thing across every vendor that talks to you
999  &mdash; and lets you prove it.</p>
1000  <div class="grid3" style="margin-top:34px;">
1001    <div class="card">
1002      <div class="desc" style="font-weight:600;color:var(--fg);font-size:15px;">One vocabulary, versioned</div>
1003      <p style="margin:8px 0 0;"><span data-fn="prod.canonical_fields">985</span> canonical fields with declared units and types, resolved from 3,444 curated
1004      vendor mappings across <span data-fn="prod.oem_families">18</span> OEM families and <span data-fn="prod.protocols">14</span> protocols. Every corpus release is versioned, so the
1005      reading you stored last year is still interpretable this year.</p>
1006    </div>
1007    <div class="card">
1008      <div class="desc" style="font-weight:600;color:var(--fg);font-size:15px;">It abstains</div>
1009      <p style="margin:8px 0 0;">Physics bounds, sentinel detection, and type checks run on every value. A
1010      register boundary or an impossible reading resolves to null <em>with a reason</em>, never to a number
1011      your model will treat as real. Confidence travels with the mapping.</p>
1012    </div>
1013    <div class="card">
1014      <div class="desc" style="font-weight:600;color:var(--fg);font-size:15px;">It is provable</div>
1015      <p style="margin:8px 0 0;">Every response carries a corpus-versioned hash over the canonical output.
1016      Re-hash an archived reading and you can show which decision tables produced it. Same input, same
1017      bytes, every call &mdash; which is what an audit, an SLA, or a safety review actually requires.</p>
1018    </div>
1019  </div>
1020  <p class="beyond-sub" style="margin-top:26px;">Forge also ships a working agent on its own model
1021  &mdash; prediction, diagnosis, OEE, shift reports, work orders. That is proof the vocabulary is
1022  complete enough to build on, not the product you are buying.
1023  <a href="/reference-agent">See the reference agent &rarr;</a></p>
1024</section>
1025
1026<!-- ── 4. HOW IT WORKS (4 steps) ────────────────────────────────────── -->
1027<section class="sandbox-section">
1028  <h3>How it works</h3>
1029  <h2>From raw protocols to working agent in four steps.</h2>
1030  <ol class="sandbox-steps" style="margin-top:28px;">
1031    <li>
1032      <span class="step-num">01</span>
1033      <div class="step-body">
1034        <h3>Connect your equipment</h3>
1035        <p>Forge reads from any PLC, controller, or sensor through 14 industrial protocols: MQTT, Modbus TCP, Modbus RTU, OPC UA, SNMP, BACnet, ROS2, J1939/CAN, MAVLink, MTConnect, EtherNet/IP, PROFINET, REST, CSV. Point Forge at your equipment and it auto-detects the protocol and starts reading. No hardware to install. No PLC code to write. No downtime. Your existing equipment is already compatible.</p>
1036        <p><b>Read-only.</b> Forge reads data from your equipment. It never sends commands. Your machines are safe.</p>
1037      </div>
1038    </li>
1039    <li>
1040      <span class="step-num">02</span>
1041      <div class="step-body">
1042        <h3>Your data normalizes automatically</h3>
1043        <p>Raw vendor tags become universal fields. <span data-fn="prod.mappings">2,086</span> cross-OEM mappings across <span data-fn="prod.oem_families">18</span> manufacturer families. Your Fanuc's "Spindle_Speed" and your Haas's "spd_rpm" become one universal field: <code>spindle_speed_rpm</code>. The engine handles the messy reality of factory floors: renamed tags, unit mismatches, sentinel values, and offline equipment. Unresolved tags are researched, cached and confirmed, then ship as verified mappings in signed corpus updates.</p>
1044      </div>
1045    </li>
1046    <li>
1047      <span class="step-num">03</span>
1048      <div class="step-body">
1049        <h3>Connect your AI agent</h3>
1050        <p>One line. Your agent calls 32 MCP tools: normalize, predict, diagnose, calculate OEE, generate shift reports, manage work orders, read and pre-check safety guardrails. Works with Claude, Cursor, LangChain, CrewAI, AutoGen, or any MCP-compatible client.</p>
1051        <pre class="code"><code>claude mcp add --transport http forge https://mcp.foundrynet.io/mcp --header 'Authorization: Bearer YOUR_API_KEY'</code></pre>
1052      </div>
1053    </li>
1054    <li>
1055      <span class="step-num">04</span>
1056      <div class="step-body">
1057        <h3>Govern and verify</h3>
1058        <p>Every agent action is supervised and recorded. Your operations team sees what every agent is doing, approves actions, and has a tamper-evident record of everything that happened.</p>
1059      </div>
1060    </li>
1061  </ol>
1062
1063  <!-- ── PAYLOAD DEMO: raw vendor telemetry → canonical output ─────────── -->
1064  <style>
1065  .payload-demo{margin:34px 0 0;border:1px solid var(--rule);border-radius:12px;
1066    background:var(--bg-code);overflow:hidden;}
1067  .payload-demo[open]{border-color:var(--jade);
1068    box-shadow:0 0 0 1px rgba(16,185,129,.06),0 18px 50px -30px rgba(16,185,129,.35);}
1069  .payload-demo > summary{list-style:none;cursor:pointer;padding:18px 22px;
1070    display:flex;align-items:center;gap:12px;
1071    font-family:'JetBrains Mono',monospace;font-size:13.5px;color:var(--fg);
1072    transition:background .15s ease;}
1073  .payload-demo > summary::-webkit-details-marker{display:none}
1074  .payload-demo > summary:hover{background:rgba(45,187,120,.04)}
1075  .payload-demo > summary:focus-visible{outline:1px solid var(--jade-soft);outline-offset:-3px}
1076  .pd-caret{color:var(--jade-soft);font-size:11px;flex:0 0 auto;
1077    transition:transform .18s ease;transform-origin:50% 50%;}
1078  .payload-demo[open] .pd-caret{transform:rotate(90deg)}
1079  .pd-title{flex:1 1 auto;min-width:0;font-weight:500;letter-spacing:-.01em}
1080  .pd-hint{flex:0 0 auto;font-size:10px;letter-spacing:.14em;text-transform:uppercase;
1081    color:var(--fg-dim);border:1px solid var(--rule);border-radius:4px;padding:4px 7px}
1082  .pd-body{padding:0 22px 24px;display:grid;gap:18px;align-items:start;
1083    grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);}
1084  .pd-col{min-width:0}
1085  .pd-label{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.14em;
1086    text-transform:uppercase;margin:0 0 8px;}
1087  .pd-label-raw{color:var(--fg-dim)}
1088  .pd-label-out{color:var(--jade-soft)}
1089  .payload-demo pre.code{margin:0;max-width:100%;font-size:12px;padding:16px 18px;
1090    box-shadow:none;}
1091  .payload-demo pre.code.raw{border-left:2px solid var(--fg-dim)}
1092  .payload-demo pre.code.out{border-left:2px solid var(--jade)}
1093  .pd-arrow{align-self:center;color:var(--jade);font-size:20px;line-height:1;
1094    font-family:'JetBrains Mono',monospace}
1095  .pd-note{margin:10px 0 0;color:var(--fg-mid);font-size:13px;line-height:1.6}
1096  .pd-note code{color:var(--amber);font-size:.94em}
1097  .pd-foot{grid-column:1/-1;margin:8px 0 0;padding:14px 0 0;
1098    border-top:1px solid var(--rule-soft);
1099    color:var(--fg-mid);font-size:13.5px;line-height:1.6}
1100  .pd-foot b{color:var(--fg);font-weight:600}
1101  .pd-foot code{color:var(--amber);font-size:.94em}
1102  @media (max-width:860px){
1103    .pd-body{grid-template-columns:minmax(0,1fr)}
1104    .pd-arrow{transform:rotate(90deg);justify-self:start}
1105    .payload-demo > summary{font-size:12.5px;padding:16px 16px;gap:10px}
1106    .pd-hint{display:none}
1107    .pd-body{padding:0 16px 20px}
1108  }
1109  </style>
1110  <details class="payload-demo">
1111    <summary>
1112      <span class="pd-caret" aria-hidden="true">▶</span>
1113      <span class="pd-title">See exactly what comes back: raw vendor telemetry → Forge canonical output</span>
1114      <span class="pd-hint">example</span>
1115    </summary>
1116    <div class="pd-body">
1117      <div class="pd-col">
1118        <div class="pd-label pd-label-raw">Before · raw vendor telemetry</div>
1119        <pre class="code raw"><code>{
1120  <span class="key">"S1Temp"</span>:   72.1,
1121  <span class="key">"S1Load"</span>:   847,
1122  <span class="key">"SP_SPEED"</span>: 8500,
1123  <span class="key">"aa_000"</span>:   3.2
1124}</code></pre>
1125        <p class="pd-note">Four values off the controller. No names an agent can reason about, no units, no scale. <code>847</code> could be anything.</p>
1126      </div>
1127      <div class="pd-arrow" aria-hidden="true">→</div>
1128      <div class="pd-col">
1129        <div class="pd-label pd-label-out">After · Forge canonical output</div>
1130        <pre class="code out"><code>{
1131  <span class="key">"spindle_temperature_c"</span>:  72.1,
1132  <span class="key">"spindle_load_pct"</span>:      84.7,
1133  <span class="key">"spindle_speed_rpm"</span>:     8500,
1134  <span class="key">"coolant_pressure_bar"</span>:  3.2,
1135  <span class="key">"oem"</span>:                   <span class="str">"haas"</span>,
1136  <span class="key">"coverage_pct"</span>:          100,
1137  <span class="key">"match_type"</span>:            <span class="str">"corpus"</span>
1138}</code></pre>
1139        <p class="pd-note">Named, typed, unitized. <code>847</code> was tenths of a percent, so it lands as <code>84.7</code>. <code>aa_000</code> was coolant pressure in bar. The <code>_c</code> comes from the unit the Haas pack declares for <code>S1Temp</code> &mdash; declare it on the tag instead (<code>S1Temp (degF)</code>) and Forge converts. It reads a declared unit; it never infers one.</p>
1140      </div>
1141      <p class="pd-foot">Same shape from every manufacturer. A Fanuc, a Haas, and a Mazak all return <code>spindle_speed_rpm</code>, so your agent is written once. <b>coverage_pct</b> tells you how much of the payload was mapped and <b>match_type</b> tells you how, so you always know whether to trust the reading.</p>
1142    </div>
1143  </details>
1144</section>
1145
1146<!-- ── 5. BUILT-IN INTELLIGENCE ─────────────────────────────────────── -->
1147
1148
1149<!-- ── 6. DEPLOY (Edge / Cloud / Fleet / Hybrid) ────────────────────── -->
1150<section>
1151  <h3>Deploy</h3>
1152  <h2>
1152Runs anywhere your equipment lives.</h2>
1153  <div class="grid4" style="margin-top:30px;">
1154    <div class="card">
1155      <div class="desc" style="font-weight:600;color:var(--fg);font-size:15px;">EDGE</div>
1156      <p style="margin:8px 0 0;">Docker on any 4 GB Linux host, NUC, or industrial PC. Values never leave your building &mdash; only tag names and counts are shared for corpus updates.</p>
1157    </div>
1158    <div class="card">
1159      <div class="desc" style="font-weight:600;color:var(--fg);font-size:15px;">CLOUD</div>
1160      <p style="margin:8px 0 0;"><b style="color:var(--fg);">No hardware to buy.</b> No industrial PCs. No edge servers. No IT approval process. Forge runs in the cloud, hosted at forge.foundrynet.io. Instant sandbox, full MCP gateway. Your data arrives through the protocols your equipment already speaks. When you want edge deployment, run the same Docker container on any 4 GB Linux host. Same kernel, your choice of where it runs.</p>
1161    </div>
1162    <div class="card">
1163      <div class="desc" style="font-weight:600;color:var(--fg);font-size:15px;">FLEET</div>
1164      <p style="margin:8px 0 0;">AWS Greengrass component. Azure IoT Edge module. Deploy to thousands of devices through your existing edge infrastructure.</p>
1165    </div>
1166    <div class="card">
1167      <div class="desc" style="font-weight:600;color:var(--fg);font-size:15px;">HYBRID</div>
1168      <p style="margin:8px 0 0;">Edge for real-time intelligence. Cloud sync for fleet analytics and cross-site learning.</p>
1169    </div>
1170  </div>
1171</section>
1172
1173<!-- ── 6b. GUARDRAILS ───────────────────────────────────────────────── -->
1174<section id="guardrails">
1175  <h3>Guardrails</h3>
1176  <h2>Deterministic safety. No alert fatigue.</h2>
1177  <p class="beyond-sub">Define hard boundaries for your equipment. If an agent tries to exceed them, the kernel blocks it instantly. No human approval needed, no rubber-stamping, no latency. Your plant engineer sets the rules. No agent can override them.</p>
1178  <div class="readonly-callout" style="margin-top:30px;">
1179    <div class="ro-head">Guardrails are not alerts</div>
1180    <p>They don't ask for permission. They enforce the rules your engineers set, at the software layer: a rejected automation never leaves the kernel, and a denied agent never reads the machine. Every block is logged. Your operators stop babysitting approval prompts and start doing their actual jobs.</p>
1181    <p><b>An engineer writes the rule in plain English. The kernel enforces it deterministically, forever.</b></p>
1182  </div>
1183  <div class="grid3" style="margin-top:34px;">
1184    <div class="card">
1185      <div class="desc" style="font-weight:600;color:var(--fg);font-size:15px;">SET A HARD LIMIT</div>
1186      <p style="margin:8px 0 0;">"Block if spindle_speed_rpm &gt; 12000." Value bounds, rate-of-change limits, physics-validation failures, and compound rules like "spindle load above 90% AND coolant above 45&deg;C." Parsed once, reviewed by you, then enforced identically on every reading.</p>
1187    </div>
1188    <div class="card">
1189      <div class="desc" style="font-weight:600;color:var(--fg);font-size:15px;">AGENTS CHECK BEFORE THEY ACT</div>
1190      <p style="margin:8px 0 0;">Agents can read your guardrails and test a proposed value against them before attempting anything. They self-correct instead of getting silently rejected. What they cannot do is create, edit, or disable a guardrail. That takes a human.</p>
1191    </div>
1192    <div class="card">
1193      <div class="desc" style="font-weight:600;color:var(--fg);font-size:15px;">EVERY BLOCK IS ON THE RECORD</div>
1194      <p style="margin:8px 0 0;">What was blocked, when, why, and which agent tried. Deleting a guardrail is a soft delete with an audit trail, so "who removed the spindle limit, and when" is always answerable. Your compliance team gets proof, not assurances.</p>
1195    </div>
1196  </div>
1197  <p class="beyond-sub" style="margin-top:26px;">Guardrails block any agent-proposed action that violates a rule your engineers set, and log every block. Forge is read-only: it never sends commands to your machines, and your operators remain the only ones who can act on equipment.</p>
1198</section>
1199
1200<!-- ── 6c. WORKS WITH ───────────────────────────────────────────────── -->
1201<style>
1202  .ww-grid { display: grid; gap: 12px; margin-top: 30px;
1203             grid-template-columns: repeat(4, 1fr); }
1204  @media (max-width: 860px) { .ww-grid { grid-template-columns: repeat(2, 1fr); } }
1205  @media (max-width: 460px) { .ww-grid { grid-template-columns: 1fr; } }
1206  .ww-item { display: block; border: 1px solid var(--rule); border-radius: 10px;
1207             background: var(--bg-elev); padding: 16px 18px; text-decoration: none;
1208             transition: border-color .16s ease, transform .16s ease; }
1209  a.ww-item:hover { border-color: var(--jade); transform: translateY(-1px); }
1210  .ww-name { color: var(--fg); font-weight: 600; font-size: 15px; letter-spacing: -.01em; }
1211  .ww-kind { color: var(--fg-dim); font-size: 12px; margin-top: 3px;
1212             font-family: 'JetBrains Mono', ui-monospace, monospace; letter-spacing: .04em; }
1213  .ww-ready { color: var(--jade-soft); font-size: 11.5px; margin-top: 9px;
1214              font-family: 'JetBrains Mono', ui-monospace, monospace; }
1215  .ww-note { color: var(--fg-mid); font-size: 14.5px; margin-top: 26px; }
1216</style>
1217<section id="works-with">
1218  <h3>Works with</h3>
1219  <h2>Fits the stack you already run.</h2>
1220  <p class="beyond-sub">Forge resolves your equipment data, then pushes it wherever it needs to go. Four of these ship as pre-built templates: the field mapping, the exact payload the platform expects, and a curl command that creates the automation. The rest is any endpoint that accepts a webhook.</p>
1221
1222  <div class="ww-grid">
1223    <a class="ww-item" href="https://forge.foundrynet.io/templates/sitewise?utm_source=foundrynet&utm_medium=works_with&utm_campaign=sitewise">
1224      <div class="ww-name">AWS IoT SiteWise</div>
1225      <div class="ww-kind">DATA STREAMING</div>
1226      <div class="ww-ready">Template &middot; ~5 min</div>
1227    </a>
1228    <a class="ww-item" href="https://forge.foundrynet.io/templates/servicenow?utm_source=foundrynet&utm_medium=works_with&utm_campaign=servicenow">
1229      <div class="ww-name">ServiceNow</div>
1230      <div class="ww-kind">INCIDENT MANAGEMENT</div>
1231      <div class="ww-ready">Template &middot; ~10 min</div>
1232    </a>
1233    <a class="ww-item" href="https://forge.foundrynet.io/templates/salesforce?utm_source=foundrynet&utm_medium=works_with&utm_campaign=salesforce">
1234      <div class="ww-name">Salesforce</div>
1235      <div class="ww-kind">FIELD SERVICE</div>
1236      <div class="ww-ready">Template &middot; ~10 min</div>
1237    </a>
1238    <a class="ww-item" href="https://forge.foundrynet.io/templates/pagerduty?utm_source=foundrynet&utm_medium=works_with&utm_campaign=pagerduty">
1239      <div class="ww-name">PagerDuty</div>
1240      <div class="ww-kind">ALERTING</div>
1241      <div class="ww-ready">Template &middot; ~2 min</div>
1242    </a>
1243    <a class="ww-item" href="https://forge.foundrynet.io/templates/pagerduty?utm_source=foundrynet&utm_medium=works_with&utm_campaign=opsgenie">
1244      <div class="ww-name">Opsgenie</div>
1245      <div class="ww-kind">ALERTING</div>
1246      <div class="ww-ready">Template &middot; ~2 min</div>
1247    </a>
1248    <div class="ww-item">
1249      <div class="ww-name">Slack</div>
1250      <div class="ww-kind">NOTIFICATION</div>
1251      <div class="ww-ready">Incoming webhook</div>
1252    </div>
1253    <div class="ww-item">
1254      <div class="ww-name">AWS Greengrass &middot; Azure IoT Edge</div>
1255      <div class="ww-kind">FLEET DEPLOYMENT</div>
1256      <div class="ww-ready">Native component</div>
1257    </div>
1258    <div class="ww-item">
1259      <div class="ww-name">Any webhook endpoint</div>
1260      <div class="ww-kind">HTTPS &middot; POST / PUT / PATCH</div>
1261      <div class="ww-ready">Bearer &middot; Basic &middot; HMAC</div>
1262    </div>
1263  </div>
1264
1265  <p class="ww-note">An integration is a URL, a method, headers, an auth mode, and a JSON body template. If your system accepts a webhook, Forge can drive it, and the condition that fires it is written against a canonical field name that stays the same when you add a machine from a different manufacturer. <a href="https://forge.foundrynet.io/templates?utm_source=foundrynet&utm_medium=works_with&utm_campaign=hub">See all templates &rarr;</a></p>
1266</section>
1267
1268<!-- ── 7. PRICING (6 tiers) ─────────────────────────────────────────── -->
1269<section id="pricing">
1270  <h3>Pricing</h3>
1271  <h2>Start free. Pay when agents go to production.</h2>
1272  <p class="pricing-lede">Try the engine free on your own machine. Integrate directly through the API. Or license the engine and ship a cross-vendor canonical data model as a feature of your own platform.</p>
1273  <div class="tier5">
1274    <div class="t5 feature">
1275      <div class="n">Developer Sandbox</div>
1276      <div class="p">Free</div>
1277      <div class="d">The sandbox build: <span data-fn="sandbox.canonical_fields">
1277719</span> canonical fields, <span data-fn="sandbox.packs">29</span> packs. Full API, no time limit, no key required. Runs entirely on your own machine. <code>docker pull ghcr.io/foundrynet/forge-sandbox</code>. Send your vendor's tags, see what comes back.</div>
1278      <a class="cta" href="/docs">Read the quickstart →</a>
1279    </div>
1280    <div class="t5">
1281      <div class="n">Forge API</div>
1282      <div class="p">$0.30<span class="u">/1,000 normalized events</span></div>
1283      <div class="d">For developers integrating Forge directly. No subscription, no minimum, no seat count. Graduated volume tiers down to $0.06 per 1,000. $0.10 predict, $0.10 diagnose, $0.20 fleet, $0.02 integrity proof, per action. Free: identify, coverage, mapping, OEE, shift report, energy, agent cards. Self-serve. API key issued by email.</div>
1284      <a class="cta" href="https://forge.foundrynet.io/pricing">Get API key, free →</a>
1285    </div>
1286    <!-- FN-PLATFORM-TIER -->
1287    <div class="t5 platform">
1288      <div class="n">Platform License: embed the engine</div>
1289      <div class="p">$0.15<span class="u"> per 1,000 normalized events · graduated tiers to $0.03 at scale</span></div>
1290      <div class="d"><b style="color:var(--fg);">For platforms that want a cross-vendor canonical data model as a native feature.</b> You already connect to equipment. License the engine that makes your platform understand every vendor that talks to it: <span data-fn="prod.canonical_fields">985</span> canonical fields, <span data-fn="prod.mappings">2,086</span> mappings, <span data-fn="prod.oem_families">18</span> OEM families, <span data-fn="prod.protocols">14</span> protocols. Self-improving corpus, unit conversion, sentinel detection, physics validation, MCP-native. Docker drop-in, outbound HTTPS only, no inbound ports, remote corpus updates with no restart. Tag names and counts leave the site; values never do. <b style="color:var(--fg);">Your brand. Your deployment. Your revenue.</b> Annual platform license sized to your deployment. 14-day paid pilot ($25,000), credited in full against year one. <b style="color:var(--fg);">We license to platforms. We don't sell to your customers.</b></div>
1291      <a class="cta" href="/licensing">See platform licensing →</a>
1292    </div>
1293    <!-- /FN-PLATFORM-TIER -->
1294  </div>
1295</section>
1296
1297<!-- ── 8. VERTICALS (AI agents for X) ───────────────────────────────── -->
1298<section id="products">
1299  <h3>Verticals</h3>
1300  <h2>Built for manufacturing first.</h2>
1301  <p class="beyond-sub">The corpus is deepest where the equipment is hardest: CNC and robotics.
1302  Same kernel runs the rest.</p>
1303  <div class="hub" style="margin-top:28px;">
1304    <div class="product-card">
1305      <h4>AI agents for manufacturing</h4>
1306      <p>CNC, robotics, and mixed-vendor lines. Connect your agents to Fanuc, Siemens, Haas, ABB, KUKA,
1307      and 13 more through one data layer &mdash; the arm and the machine it tends in a single schema.</p>
1308      <a href="/cnc">Explore &rarr;</a>
1309    </div>
1310  </div>
1311  <p class="beyond-sub" style="margin-top:30px;font-size:14px;">Also supported on the same kernel:
1312  <a href="/datacenters">data centers</a> (UPS, PDUs, CRAC units) &middot;
1313  <a href="/energy">energy</a> (inverters, storage, grid assets) &middot;
1314  <a href="/coldchain">cold chain</a> (refrigeration controllers).</p>
1315</section>
1316
1317<!-- ── LEAD CAPTURE ────────────────────────────────────────────────── -->
1318<section id="contact">
1319  <h3>Talk to us</h3>
1320  <h2>See Forge on your equipment</h2>
1321  <div class="leadform">
1322    <p>Tell us what you're working with. We'll show you what Forge does with your data.</p>
1323    <form id="lead-form" novalidate>
1324      <input type="email" name="email" placeholder="Work email" required autocomplete="email">
1325      <select name="interest" required>
1326        <option value="" disabled selected>What brings you here?</option>
1327        <option value="pilot_rescue">Stalled AI pilot</option>
1328        <option value="equipment_finance">Equipment finance / leasing</option>
1329        <option value="integrator">System integrator</option>
1330        <option value="monitoring">Equipment monitoring</option>
1331        <option value="agent_builder">Building AI agents</option>
1332        <option value="other">Other</option>
1333      </select>
1334      <textarea name="message" placeholder="What equipment or challenge? (optional)" rows="3"></textarea>
1335      <input class="hp" type="text" name="hp" tabindex="-1" autocomplete="off" aria-hidden="true">
1336      <button type="submit" class="btn primary">Try your data</button>
1337    </form>
1338    <div class="lead-status" id="lead-status" hidden></div>
1339    <p class="note">
1339Or just email [email protected]. No sales pitch. Just a demo on your data.</p>
1340  </div>
1341  
1341<script>
1342  (function(){
1343    var f = document.getElementById('lead-form');
1344    if (!f) return;
1345    var statusEl = document.getElementById('lead-status');
1346    function show(msg){ statusEl.hidden = false; statusEl.textContent = msg; }
1347    f.addEventListener('submit', function(e){
1348      e.preventDefault();
1349      var email = (f.email.value || '').trim();
1350      if (!email || email.indexOf('@') < 1) { show('Please enter a valid work email.'); return; }
1351      var utmSource = new URLSearchParams(window.location.search).get('utm_source') || null;
1352      var payload = { email: email, interest: f.interest.value || null,
1353                      message: (f.message.value || '').trim() || null,
1354                      utm_source: utmSource, hp: f.hp.value || '' };
1355      var btn = f.querySelector('button[type=submit]');
1356      btn.disabled = true; show('Sending…');
1357      fetch('https://forge.foundrynet.io/v1/leads', {
1358        method: 'POST', headers: { 'Content-Type': 'application/json' },
1359        body: JSON.stringify(payload)
1360      }).then(function(r){
1361        if (!r.ok) throw new Error('http ' + r.status);
1362        return r.json();
1363      }).then(function(){
1364        if (window.plausible) plausible('Lead Submitted', { props: { interest: payload.interest || 'unspecified' } });
1365        f.reset(); show("✓ Got it, we'll be in touch within 24 hours.");
1366      }).catch(function(){
1367        // Network / 5xx fallback: open a pre-filled mail draft so the lead isn't lost.
1368        show('Could not reach the server. Opening an email draft…');
1369        var body = encodeURIComponent('Interest: ' + (payload.interest || '-') + '\n\n' + (payload.message || ''));
1370        window.location.href = 'mailto:[email protected]?subject=' +
1371          encodeURIComponent('Demo request: ' + email) + '&body=' + body;
1372      }).finally(function(){ btn.disabled = false; });
1373    });
1374  })();
1375  </script>
1375
1376</section>
1377
1378<!-- ── FOOTER ──────────────────────────────────────────────────────── -->
1379<!-- FN-SUBSTACK-FOOTER -->
1380<div style="text-align:center; padding:1.5rem 0; border-top:1px solid #333;">
1381  <a href="https://substack.com/@themachineagent" target="_blank" rel="noopener"
1382     style="color:#8b949e; text-decoration:none; font-size:14px;">
1383    Get the thesis behind this infrastructure: <span style="color:#58a6ff;">The Machine Agent</span>, weekly.
1384  </a>
1385</div>
1386<!-- /FN-SUBSTACK-FOOTER -->
1387<footer>
1388  Foundry Labs<span class="sep">·</span>Industrial AI Infrastructure<span class="sep collapse">·</span><a data-email href="#" class="foot-mail">[email protected]</a><span class="sep">·</span><a href="/docs">Docs</a><span class="sep">·</span><a href="/terms">Terms</a><span class="sep">·</span><a href="/privacy">Privacy</a><span class="sep">·</span><a href="https://assay.foundrynet.io">Assay</a>
1389  <span class="sep">·</span><a href="https://substack.com/@themachineagent" target="_blank">The Machine Agent</a>
1390</footer>
1391
1392<!-- ── CHAT WIDGET (floating) ─────────────────────────────────────── -->
1393<style>
1394  #fn-chat-fab { position:fixed; bottom:22px; right:22px; z-index:9000;
1395                 width:56px; height:56px; border-radius:50%;
1396                 background:var(--jade); border:none; cursor:pointer;
1397                 box-shadow:0 6px 24px rgba(26,144,88,.35);
1398                 color:#050609; display:flex; align-items:center; justify-content:center;
1399                 transition:transform .15s, background .15s; }
1400  #fn-chat-fab:hover { background:var(--jade-soft); transform:translateY(-2px); }
1401  #fn-chat-fab svg { width:26px; height:26px; }
1402  #fn-chat-panel { position:fixed; bottom:90px; right:22px; z-index:9000;
1403                   width:380px; max-width:calc(100vw - 32px);
1404                   height:560px; max-height:calc(100vh - 110px);
1405                   background:var(--bg-elev); border:1px solid var(--rule);
1406                   border-radius:8px; display:none; flex-direction:column;
1407                   box-shadow:0 10px 40px rgba(0,0,0,.5); overflow:hidden; }
1408  #fn-chat-panel.open { display:flex; }
1409  #fn-chat-head { padding:12px 16px; border-bottom:1px solid var(--rule);
1410                  display:flex; justify-content:space-between; align-items:center;
1411                  font-family:'JetBrains Mono',monospace; font-size:12px;
1412                  color:var(--fg-mid); }
1413  #fn-chat-head .tag { color:var(--amber); font-size:10px; letter-spacing:.18em;
1414                       text-transform:uppercase; padding:3px 8px;
1415                       border:1px solid var(--rule); border-radius:3px; }
1416  #fn-chat-head button { background:none; border:none; color:var(--fg-dim);
1417                         font-size:18px; cursor:pointer; padding:0 4px; }
1418  #fn-chat-log { flex:1; overflow-y:auto; padding:14px 16px;
1419                 font-size:13.5px; line-height:1.55; }
1420  #fn-chat-log .m { margin:10px 0; white-space:pre-wrap; }
1421  #fn-chat-log .m.u { color:var(--fg); }
1422  #fn-chat-log .m.b { color:var(--fg); border-left:2px solid var(--jade);
1423                      padding-left:10px; }
1424  #fn-chat-log .m.t { color:var(--fg-dim); font-family:'JetBrains Mono',monospace;
1425                      font-size:10.5px; padding:5px 8px; background:var(--bg-code);
1426                      border-radius:3px; }
1427  #fn-chat-log .role { font-family:'JetBrains Mono',monospace; font-size:9.5px;
1428                       letter-spacing:.18em; text-transform:uppercase;
1429                       color:var(--fg-dim); margin-bottom:3px; }
1430  #fn-chat-form { display:flex; gap:6px; padding:10px; border-top:1px solid var(--rule); }
1431  #fn-chat-form textarea { flex:1; resize:none; min-height:38px; max-height:110px;
1432                           padding:8px 10px; background:var(--bg-code); color:var(--fg);
1433                           border:1px solid var(--rule); border-radius:4px;
1434                           font-family:'Inter',sans-serif; font-size:13px; outline:none; }
1435  #fn-chat-form textarea:focus { border-color:var(--jade-soft); }
1436  #fn-chat-form button { padding:8px 14px; background:var(--jade); color:#050609;
1437                         border:none; border-radius:4px; cursor:pointer;
1438                         font-family:'JetBrains Mono',monospace; font-size:11.5px;
1439                         font-weight:500; letter-spacing:.04em; }
1440  #fn-chat-form button:hover { background:var(--jade-soft); }
1441  #fn-chat-form button:disabled { opacity:.55; cursor:not-allowed; }
1442  #fn-chat-upsell { display:none; padding:8px 14px; border-top:1px solid var(--rule);
1443                    background:var(--bg-code); color:var(--amber); font-size:11.5px;
1444                    text-align:center; }
1445  #fn-chat-upsell a { color:var(--amber); }
1446  #fn-chat-head .link { background:none; border:none; cursor:pointer;
1447                        color:var(--jade-soft); font-family:'JetBrains Mono',monospace;
1448                        font-size:10.5px; letter-spacing:.04em; padding:3px 6px;
1449                        text-decoration:none; }
1450  #fn-chat-head .link:hover { color:var(--amber); }
1451  #fn-chat-head a.link { display:inline-flex; align-items:center; }
1452  #fn-chat-signin { display:none; padding:14px 16px;
1453                    border-bottom:1px solid var(--rule); background:var(--bg-code); }
1454  #fn-chat-signin.open { display:block; }
1455  #fn-chat-signin p { margin:0 0 8px; color:var(--fg-mid); font-size:12px; line-height:1.5; }
1456  #fn-chat-signin p.hint { margin-top:10px; font-size:11px; color:var(--fg-dim); }
1457  #fn-chat-signin code { color:var(--amber); font-family:'JetBrains Mono',monospace; }
1458  #fn-chat-signin input { width:100%; padding:8px 10px; background:var(--bg);
1459                          color:var(--fg); border:1px solid var(--rule);
1460                          border-radius:4px; font-family:'JetBrains Mono',monospace;
1461                          font-size:12px; outline:none; box-sizing:border-box; }
1462  #fn-chat-signin input:focus { border-color:var(--jade-soft); }
1463  #fn-chat-signin .row { display:flex; gap:6px; margin-top:8px; align-items:center; }
1464  #fn-chat-signin button { padding:7px 12px; background:var(--jade); color:#050609;
1465                           border:none; border-radius:4px; cursor:pointer;
1466                           font-family:'JetBrains Mono',monospace; font-size:11px;
1467                           letter-spacing:.04em; }
1468  #fn-chat-signin button.link { background:none; color:var(--fg-dim); padding:5px; }
1469  #fn-chat-signin button.link:hover { color:var(--amber); }
1470  #fn-chat-signin .err { display:none; margin-top:8px; color:var(--amber);
1471                         font-size:11px; font-family:'JetBrains Mono',monospace; }
1472  #fn-chat-signin .err.show { display:block; }
1473  @media (max-width:480px){
1474    #fn-chat-panel { right:8px; left:8px; width:auto; bottom:78px; height:70vh; }
1475  }
1476</style>
1477
1478<button id="fn-chat-fab" aria-label="Open chat">
1479  <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
1480    <path d="M3 5.5C3 4.12 4.12 3 5.5 3h13C19.88 3 21 4.12 21 5.5v9c0 1.38-1.12 2.5-2.5 2.5H9l-4 4v-4h-.5C3.12 17 3 15.88 3 14.5v-9z"/>
1481  </svg>
1482</button>
1483
1484<div id="fn-chat-panel" role="dialog" aria-label="FoundryNet chat">
1485  <div id="fn-chat-head">
1486    <span>FoundryNet · <span style="color:var(--jade-soft)">ask anything</span></span>
1487    <span style="display:flex;align-items:center;gap:8px;">
1488      <span class="tag" id="fn-chat-tag">demo</span>
1489      <a id="fn-chat-mail" class="link"
1490         data-email href="#" title="Email FoundryNet">contact</a>
1491      <button type="button" id="fn-chat-auth" class="link">sign in</button>
1492      <button type="button" id="fn-chat-close" aria-label="Close">×</button>
1493    </span>
1494  </div>
1495  <div id="fn-chat-signin">
1496    <p>Paste your <code>fnet_</code> API key to switch to live mode.</p>
1497    <input type="password" id="fn-chat-key" placeholder="fnet_…" autocomplete="off" spellcheck="false">
1498    <div class="row">
1499      <button type="button" id="fn-chat-verify">Verify &amp; sign in</button>
1500      <button type="button" id="fn-chat-cancel" class="link">cancel</button>
1501    </div>
1502    <div class="err" id="fn-chat-keyerr"></div>
1503    <p class="hint">No key yet? <a href="https://forge.foundrynet.io/pricing">Get your API key</a>, takes about 2 minutes.</p>
1504  </div>
1505  <div id="fn-chat-log">
1506    <div class="m b">
1507      <div class="role">FoundryNet</div>Hi. Tell me about your machines and I'll help. Try: "I have a Fanuc Robodrill α-D21MiB5 on Line 6, can you connect it?"
1508    </div>
1509  </div>
1510  <form id="fn-chat-form" autocomplete="off">
1511    <textarea id="fn-chat-input" placeholder="Type a message…" rows="1"></textarea>
1512    <button type="submit" id="fn-chat-send">Send</button>
1513  </form>
1514  <div id="fn-chat-upsell">
1515    Demo mode. Get a free key at <a href="https://forge.foundrynet.io/pricing">forge.foundrynet.io/pricing</a>.
1516  </div>
1517</div>
1518
1519<script>
1520(function () {
1521  var API_BASE = 'https://forge.foundrynet.io';
1522
1523  var panel    = document.getElementById('signup-panel');
1524  // Self-serve signup UI was removed for the enterprise page, no-op if absent.
1525  if (!panel) return;
1526  var toggle   = document.getElementById('signup-toggle');
1527  var stepEmail   = document.getElementById('signup-step-email');
1528  var stepActive  = document.getElementById('signup-step-activating');
1529  var stepKey     = document.getElementById('signup-step-key');
1530  var emailFree = document.getElementById('signup-email-free');
1531  var emailPaid = document.getElementById('signup-email-paid');
1532  var btnFree   = document.getElementById('signup-free');
1533  var btnPaid   = document.getElementById('signup-paid');
1534  var errBox   = document.getElementById('signup-err');
1535  var keyBox   = document.getElementById('signup-key');
1536  var copyBtn  = document.getElementById('signup-copy');
1537  var metaLine = document.getElementById('signup-meta');
1538
1539  function openPanel() {
1540    panel.classList.add('open');
1541    panel.scrollIntoView({ behavior: 'smooth', block: 'center' });
1542  }
1543  function showError(msg) {
1544    errBox.textContent = msg;
1545    errBox.classList.add('show');
1546  }
1547  function clearError() {
1548    errBox.textContent = '';
1549    errBox.classList.remove('show');
1550  }
1551  function showStep(which) {
1552    stepEmail.style.display  = which === 'email'    ? '' : 'none';
1553    stepActive.style.display = which === 'activate' ? '' : 'none';
1554    stepKey.style.display    = which === 'key'      ? '' : 'none';
1555  }
1556
1557  toggle.addEventListener('click', function (e) {
1558    e.preventDefault();
1559    openPanel();
1560    showStep('email');
1561    setTimeout(function () { emailFree.focus(); }, 200);
1562  });
1563
1564  // Pricing-tier Starter card "Get free API key" CTA, same effect as
1565  // the hero "Get your API key" button: opens the signup panel scrolled
1566  // to email step, focuses the free input.
1567  // Sandbox / Coverage / Free tier CTAs, same effect as hero "Get a free
1568  // API key": open the signup panel scrolled to email step.
1569  ['sandbox-signup-toggle', 'coverage-signup-toggle', 'tier-free-cta',
1570   'tier-starter-cta' /* legacy id kept for backwards-compat */].forEac
1570h(function (id) {
1571    var el = document.getElementById(id);
1572    if (el) el.addEventListener('click', function (e) {
1573      e.preventDefault(); toggle.click();
1574    });
1575  });
1576
1577  // Per-asset Early Access CTA, POSTs to /v1/forms/fleet-interest on the
1578  // Forge API, which inserts into the fleet_interest Supabase table. Falls
1579  // back to a mailto: draft if the API call errors out so the visitor never
1580  // hits a dead button. Intentionally NOT a Stripe charge, the per-asset
1581  // ("Fleet") price is live in Stripe but kept gated to Early Access until
1582  // we have onboarding capacity.
1583  var btnEarlyAccess = document.getElementById('early-access-cta');
1584  if (btnEarlyAccess) btnEarlyAccess.addEventListener('click', async function () {
1585    var src = document.getElementById('signup-email-earlyaccess');
1586    var typed = (src && src.value || '').trim();
1587    if (!typed || typed.indexOf('@') < 1) {
1588      src && src.focus();
1589      if (src) { src.style.borderColor = 'var(--amber)';
1590        setTimeout(function(){ src.style.borderColor = ''; }, 1500); }
1591      return;
1592    }
1593    var origLabel = btnEarlyAccess.textContent;
1594    btnEarlyAccess.disabled = true;
1595    btnEarlyAccess.textContent = 'Sending…';
1596
1597    // Mailto fallback, used if fetch errors, the API returns 5xx, or the
1598    // visitor's network blocks fetch(). Preserves the lead via the visitor's
1599    // own email client; [email protected] is the inbox.
1600    function fallbackMailto() {
1601      var subject = encodeURIComponent('Early Access, Fleet pricing');
1602      var body = encodeURIComponent(
1603        'Hi FoundryNet,\n\n' +
1604        'I want to talk about per-machine pricing for our fleet.\n\n' +
1605        'Email: ' + typed + '\n' +
1606        'Company: \n' +
1607        'Fleet size (rough): \n' +
1608        'OEMs / models on the floor: \n' +
1609        'Current sampling rate: \n\n' +
1610        'Thanks.\n'
1611      );
1612      window.location.href =
1613        'mailto:[email protected]?subject=' + subject + '&body=' + body;
1614    }
1615
1616    try {
1617      var res = await fetch(API_BASE + '/v1/forms/fleet-interest', {
1618        method: 'POST',
1619        headers: { 'Content-Type': 'application/json' },
1620        body: JSON.stringify({ email: typed })
1621      });
1622      var data = await res.json().catch(function(){ return {}; });
1623      if (!res.ok || !data.ok) {
1624        // 422 = bad email shape, 429 = rate-limited, 5xx = backend issue.
1625        // For 422/429 we show the message inline; for 5xx we fall through
1626        // to mailto so the visitor still has a path forward.
1627        if (res.status >= 500) {
1628          fallbackMailto();
1629          return;
1630        }
1631        btnEarlyAccess.textContent = origLabel;
1632        btnEarlyAccess.disabled = false;
1633        alert(data.reason || data.detail || data.message ||
1634              ('Could not submit (HTTP ' + res.status + ')'));
1635        return;
1636      }
1637      btnEarlyAccess.textContent = '✓ Sent, we\'ll be in touch';
1638      // Lock the button on success so users don't double-submit.
1639    } catch (e) {
1640      // Network failure → mailto fallback. Don't swallow the lead.
1641      fallbackMailto();
1642    }
1643  });
1644
1645  // Growth tier inline signup, self-serve Stripe Checkout with
1646  // tier=growth in the request body. Same redirect-to-Stripe flow as
1647  // the hero paid signup, just discounted prices on the subscription.
1648  var emailGrowth = document.getElementById('signup-email-growth');
1649  var btnGrowth   = document.getElementById('signup-growth');
1650  async function submitGrowth() {
1651    var email = (emailGrowth.value || '').trim();
1652    if (!email || email.indexOf('@') < 1) {
1653      emailGrowth.focus();
1654      emailGrowth.style.borderColor = 'var(--amber)';
1655      setTimeout(function () { emailGrowth.style.borderColor = ''; }, 1500);
1656      return;
1657    }
1658    var original = btnGrowth.textContent;
1659    btnGrowth.disabled = true;
1660    btnGrowth.textContent = 'Redirecting…';
1661    try {
1662      var res = await fetch(API_BASE + '/v1/keys/checkout', {
1663        method: 'POST',
1664        headers: { 'Content-Type': 'application/json' },
1665        body: JSON.stringify({ email: email, tier: 'growth' })
1666      });
1667      var data = await res.json().catch(function () { return {}; });
1668      if (!res.ok || !data.checkout_url) {
1669        btnGrowth.textContent = original;
1670        btnGrowth.disabled = false;
1671        alert(data.reason || data.detail || ('Signup failed (HTTP ' + res.status + ')'));
1672        return;
1673      }
1674      window.location.href = data.checkout_url;
1675    } catch (e) {
1676      btnGrowth.textContent = original;
1677      btnGrowth.disabled = false;
1678      alert('Network error: ' + e.message);
1679    }
1680  }
1681  if (btnGrowth) btnGrowth.addEventListener('click', submitGrowth);
1682  if (emailGrowth) emailGrowth.addEventListener('keydown', function (e) {
1683    if (e.key === 'Enter') { e.preventDefault(); submitGrowth(); }
1684  });
1685
1686  function lockButtons(label, which) {
1687    btnFree.disabled = btnPaid.disabled = true;
1688    btnFree.dataset.original = btnFree.dataset.original || btnFree.textContent;
1689    btnPaid.dataset.original = btnPaid.dataset.original || btnPaid.textContent;
1690    if (which === 'free') btnFree.textContent = label;
1691    else                  btnPaid.textContent = label;
1692  }
1693  function unlockButtons() {
1694    btnFree.disabled = btnPaid.disabled = false;
1695    btnFree.textContent = btnFree.dataset.original || 'Get free API key →';
1696    btnPaid.textContent = btnPaid.dataset.original || 'Continue to Stripe →';
1697  }
1698
1699  async function submitSignup(tier) {
1700    clearError();
1701    // Each tier card has its own email input, read whichever the user typed in.
1702    var src = tier === 'free' ? emailFree : emailPaid;
1703    var email = (src.value || '').trim();
1704    // Fall back to the OTHER input if this card's was left empty (lets the
1705    // user type once and click either button).
1706    if (!email) {
1707      var alt = tier === 'free' ? emailPaid : emailFree;
1708      email = (alt.value || '').trim();
1709    }
1710    if (!email || email.indexOf('@') < 1) {
1711      showError('Please enter a valid email.');
1712      src.focus();
1713      return;
1714    }
1715    lockButtons(tier === 'free' ? 'Minting key…' : 'Redirecting…', tier);
1716    try {
1717      var res = await fetch(API_BASE + '/v1/keys/checkout', {
1718        method: 'POST',
1719        headers: { 'Content-Type': 'application/json' },
1720        body: JSON.stringify({ email: email, tier: tier })
1721      });
1722      var data = await res.json().catch(function () { return {}; });
1723      if (!res.ok) {
1724        showError(data.reason || data.detail || ('Signup failed (HTTP ' + res.status + ')'));
1725        unlockButtons();
1726        return;
1727      }
1728      if (tier === 'free') {
1729        // Free tier returns the api_key directly, show key step inline.
1730        if (!data.api_key) {
1731          showError(data.warning || 'Free tier already issued for this email, check your records.');
1732          unlockButtons();
1733          return;
1734        }
1735        keyBox.textContent = data.api_key;
1736        metaLine.textContent =
1737          'free tier · ' + (data.calls_remaining || 50) +
1738          ' calls remaining · ' + (data.email || email);
1739        showStep('key');
1740        return;
1741      }
1742      // Paid tier returns a checkout URL, redirect.
1743      if (!data.checkout_url) {
1744        showError(data.reason || data.detail || 'No checkout URL returned.');
1745        unlockButtons();
1746        return;
1747      }
1748      window.location.href = data.checkout_url;
1749    } catch (e) {
1750      showError('Network error: ' + e.message);
1751      unlockButtons();
1752    }
1753  }
1754  btnFree.addEventListener('click', function () { submitSignup('free'); });
1755  btnPaid.addEventListener('click', function () { submitSignup('paid'); });
1756  // Enter inside each input submits its own card.
1757  emailFree.addEventListener('keydown', function (e) {
1758    if (e.key === 'Enter') { e.preventDefault(); btnFree.click(); }
1759  });
1760  emailPaid.addEventListener('keydown', function (e) {
1761    if (e.key === 'Enter') { e.preventDefault(); btnPaid.click(); }
1762  });
1763
1764  copyBtn.addEventListener('click', async function () {
1765    var key = keyBox.textContent.trim();
1766    if (!key) return;
1767    try {
1768      await navigator.clipboard.writeText(key);
1769      copyBtn.textContent = 'Copied ✓';
1770      setTimeout(function () { copyBtn.textContent = 'Copy key'; }, 1800);
1771    } catch (e) {
1772      // Fallback: select the text so the user can ⌘C manually.
1773      var range = document.createRange();
1774      range.selectNodeContents(keyBox);
1775      var sel = window.getSelection();
1776      sel.removeAllRanges(); sel.addRange(range);
1777      copyBtn.textContent = 'Press ⌘C / Ctrl-C';
1778      setTimeout(function () { copyBtn.textContent = 'Copy key'; }, 2400);
1779    }
1780  });
1781
1782  // ── Activation handler ─────────────────────────────────────────
1783  // Stripe redirects back to ?session_id=cs_… on success. Pull it,
1784  // strip it from the URL (so refresh doesn't replay), and activate.
1785  async function maybeActivate() {
1786    var params = new URLSearchParams(window.location.search);
1787    var sessionId = params.get('session_id');
1788    if (!sessionId) return;
1789
1790    // Clean the URL immediately so a refresh doesn't re-POST. We
1791    // keep the hash so #docs etc. still work.
1792    var clean = window.location.pathname + window.location.hash;
1793    window.history.replaceState({}, document.title, clean);
1794
1795    openPanel();
1796    showStep('activate');
1797
1798    try {
1799      var res = await fetch(API_BASE + '/v1/keys/activate', {
1800        method: 'POST',
1801        headers: { 'Content-Type': 'application/json' },
1802        body: JSON.stringify({ session_id: sessionId })
1803      });
1804      var data = await res.json().catch(function () { return {}; });
1805      if (!res.ok || !data.api_key) {
1806        showStep('email');
1807        showError(data.reason || data.detail
1808                  || ('Activation failed (HTTP ' + res.status + ')'));
1809        return;
1810      }
1811      keyBox.textContent = data.api_key;
1812      metaLine.textContent =
1813        'customer ' + (data.customer_id || '?') +
1814        '  ·  ' + (data.email || 'no email on file');
1815      showStep('key');
1816    } catch (e) {
1817      showStep('email');
1818      showError('Network error during activation: ' + e.message);
1819    }
1820  }
1821  maybeActivate();
1822})();
1823</script>
1823
1824
1825<script>
1826// ── Chat widget controller ──────────────────────────────────────────
1827(function(){
1828  var API='https://forge.foundrynet.io';
1829  var fab=document.getElementById('fn-chat-fab'),
1830      panel=document.getElementById('fn-chat-panel'),
1831      closeBtn=document.getElementById('fn-chat-close'),
1832      log=document.getElementById('fn-chat-log'),
1833      form=document.getElementById('fn-chat-form'),
1834      input=document.getElementById('fn-chat-input'),
1835      send=document.getElementById('fn-chat-send'),
1836      tag=document.getElementById('fn-chat-tag'),
1837      upsell=document.getElementById('fn-chat-upsell'),
1838      authBtn=document.getElementById('fn-chat-auth'),
1839      signinPanel=document.getElementById('fn-chat-signin'),
1840      keyInput=document.getElementById('fn-chat-key'),
1841      verifyBtn=document.getElementById('fn-chat-verify'),
1842      cancelBtn=document.getElementById('fn-chat-cancel'),
1843      keyErr=document.getElementById('fn-chat-keyerr');
1844  var sessionId=localStorage.getItem('fn_chat_session')||null;
1845  var apiKey=localStorage.getItem('fn_api_key')||null;
1846
1847  function bubble(cls,role,text){
1848    var d=document.createElement('div'); d.className='m '+cls;
1849    if(role){ var r=document.createElement('div'); r.className='role'; r.textContent=role; d.appendChild(r); }
1850    d.appendChild(document.createTextNode(text));
1851    log.appendChild(d); log.scrollTop=log.scrollHeight; return d;
1852  }
1853  function tool(text){
1854    var d=document.createElement('div'); d.className='m t';
1855    d.textContent='› '+text; log.appendChild(d); log.scrollTop=log.scrollHeight;
1856  }
1857  function open(){ panel.classList.add('open'); setTimeout(function(){input.focus();},80); }
1858  function close(){ panel.classList.remove('open'); }
1859  function setSignedInUI(signedIn){
1860    tag.textContent=signedIn?'live':'demo';
1861    upsell.style.display=signedIn?'none':'block';
1862    authBtn.textContent=signedIn?'sign out':'sign in';
1863  }
1864  function showSignin(show){
1865    signinPanel.classList.toggle('open', show);
1866    keyErr.classList.remove('show'); keyErr.textContent='';
1867    if(show){ setTimeout(function(){keyInput.focus();},80); }
1868  }
1869
1870  // Restore prior auth UI state on load.
1871  if(apiKey) setSignedInUI(true);
1872
1873  fab.addEventListener('click',function(){ panel.classList.contains('open')?close():open(); });
1874  closeBtn.addEventListener('click',close);
1875  input.addEventListener('input',function(){input.style.height='auto';input.style.height=Math.min(input.scrollHeight,110)+'px';});
1876  input.addEventListener('keydown',function(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();form.requestSubmit();}});
1877
1878  // ── Sign in / sign out ───────────────────────────────────────────
1879  authBtn.addEventListener('click', function(){
1880    if(apiKey){
1881      // Sign out: clear key + bound demo session so a fresh demo starts.
1882      apiKey=null;
1883      localStorage.removeItem('fn_api_key');
1884      localStorage.removeItem('fn_chat_session');
1885      sessionId=null;
1886      setSignedInUI(false);
1887      bubble('b','FoundryNet','Signed out. You’re back in demo mode.');
1888    } else {
1889      showSignin(!signinPanel.classList.contains('open'));
1890    }
1891  });
1892  cancelBtn.addEventListener('click', function(){ showSignin(false); });
1893  keyInput.addEventListener('keydown', function(e){
1894    if(e.key==='Enter'){ e.preventDefault(); verifyBtn.click(); }
1895  });
1896  verifyBtn.addEventListener('click', async function(){
1897    keyErr.classList.remove('show'); keyErr.textContent='';
1898    var k=(keyInput.value||'').trim();
1899    if(!k.startsWith('fnet_')){
1900      keyErr.textContent='Keys start with fnet_'; keyErr.classList.add('show'); return;
1901    }
1902    verifyBtn.disabled=true; verifyBtn.textContent='Verifying…';
1903    try{
1904      var r=await fetch(API+'/v1/whoami',{ headers:{'Authorization':'Bearer '+k} });
1905      var data=await r.json().catch(function(){return {};});
1906      if(!r.ok){
1907        keyErr.textContent=data.detail||data.reason||'Key rejected';
1908        keyErr.classList.add('show');
1909        verifyBtn.disabled=false; verifyBtn.textContent='Verify & sign in'; return;
1910      }
1911      // Looks good, store + reset session so the next message goes through auth.
1912      apiKey=k; localStorage.setItem('fn_api_key', k);
1913      localStorage.removeItem('fn_chat_session'); sessionId=null;
1914      keyInput.value=''; showSignin(false); setSignedInUI(true);
1915      var meta=[]; if(data.has_subscription) meta.push('billing active');
1916      if(data.is_demo) meta.push('demo key, full mode disabled');
1917      bubble('b','FoundryNet','Signed in.'+(meta.length?' ('+meta.join(', ')+')':''));
1918    } catch(err){
1919      keyErr.textContent='Network error: '+err.message; keyErr.classList.add('show');
1920    }
1921    verifyBtn.disabled=false; verifyBtn.textContent='Verify & sign in';
1922  });
1923
1924  form.addEventListener('submit', async function(e){
1925    e.preventDefault();
1926    var text=input.value.trim(); if(!text) return;
1927    bubble('u','You',text); input.value=''; input.style.height='auto';
1928    send.disabled=true; var thinking=bubble('b','FoundryNet','…');
1929    try{
1930      var headers={'Content-Type':'application/json'};
1931      if(apiKey) headers['Authorization']='Bearer '+apiKey;
1932      var body={message:text}; if(sessionId) body.session_id=sessionId;
1933      var r=await fetch(API+'/v1/chat',{method:'POST',headers:headers,body:JSON.stringify(body)});
1934      var data=await r.json().catch(function(){return {};});
1935      thinking.remove();
1936      if(!r.ok){ bubble('b','FoundryNet',(data.detail||data.reason||'Error '+r.status)); send.disabled=false; return; }
1937      sessionId=data.session_id; localStorage.setItem('fn_chat_session',sessionId);
1938      setSignedInUI(!data.is_demo);
1939      (data.tool_calls||[]).forEach(function(tc){ tool(tc.tool+'('+JSON.stringify(tc.args)+')'); });
1940      bubble('b','FoundryNet',data.message||'(no response)');
1941    }catch(err){ thinking.remove(); bubble('b','FoundryNet','Network error: '+err.message); }
1942    send.disabled=false; input.focus();
1943  });
1944})();
1945</script>
vendor: 1 bytes, line 1945
1945
1946<script>
1947  (function(){
1948    var e = ['forge','foundrynet.io'].join('@');
1949    document.querySelectorAll('[data-email]').forEach(function(el){
1950      el.textContent = e; el.setAttribute('href', 'mailto:' + e);
1951    });
1952  })();
1953</script>
vendor: 1 bytes, line 1953
1953
1954<script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a18c417fc8cb23fd',t:'MTc4MzY1MTc3Mw=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script>
1954<!-- FN-COPY-BTN-OPEN -->
1955<style>
1956/* click-to-copy on code blocks and terminal demos, no dependencies */
1957.fn-cb { position: relative; display: block; max-width: 100%; }
1958/* reserve a gutter so a long single-line command never runs under the button */
1959.fn-cb > .fn-cb-block { padding-right: 86px; }
1960.fn-cb-btn {
1961  position: absolute; top: 8px; right: 8px; z-index: 3;
1962  font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
1963  font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase;
1964  line-height: 1; padding: 6px 9px; border-radius: 5px; cursor: pointer;
1965  color: #7c889c; background: #0d1219; border: 1px solid #1e2b3d;
1966  -webkit-appearance: none; appearance: none;
1967  /* opaque, never translucent: long commands scroll UNDER this button, and a
1968     see-through chip lets that text bleed through and reads as a glitch */
1969  box-shadow: -10px 0 14px -6px rgba(0,0,0,.95);
1970  transition: color .15s ease, border-color .15s ease, background .15s ease;
1971}
1972.fn-cb:hover .fn-cb-btn { color: #b6c0cf; }
1973.fn-cb-btn:hover { color: #d8dfeb; border-color: #1a9058; background: #0f1720; }
1974.fn-cb-btn:focus-visible { outline: 1px solid #2dbb78; outline-offset: 2px; color: #d8dfeb; }
1975.fn-cb-btn.is-copied { color: #2dbb78; border-color: #1a9058; }
1976.fn-cb-btn.is-failed { color: #f0a030; border-color: #f0a030; }
1977@media (max-width: 600px) {
1978  .fn-cb-btn { top: 6px; right: 6px; padding: 5px 7px; font-size: 10px; letter-spacing: .06em; }
1979  .fn-cb > .fn-cb-block { padding-right: 66px; }
1980}
1981@media print { .fn-cb-btn { display: none; } }
1982</style>
1983<script>
1984(function () {
1985  'use strict';
1986
1987  /* Terminal demos interleave commands (.you) with output (.ok/.feed) and
1988     blank spacers (.gap). Copy only the FIRST contiguous run of command lines
1989     so a click yields something pasteable, never a command plus its output. */
1990  function termText(el) {
1991    var kids = el.children, lines = [], started = false;
1992    for (var i = 0; i < kids.length; i++) {
1993      var isCmd = kids[i].classList.contains('you');
1994      if (isCmd) { lines.push(kids[i].textContent); started = true; }
1995      else if (started) { break; }
1996    }
1997    return lines.length ? lines.join('\n') : null;
1998  }
1999
2000  function clean(s) {
2001    /* &nbsp; indents render fine but break a shell paste */
2002    return s.replace(/\u00a0/g, ' ').replace(/[ \t]+$/gm, '').replace(/\s+$/, '');
2003  }
2004
2005  function payload(el) {
2006    var raw = el.classList.contains('term') ? termText(el) : el.textContent;
2007    if (!raw) return null;
2008    raw = clean(raw);
2009    return raw ? raw : null;
2010  }
2011
2012  function legacyCopy(text) {
2013    try {
2014      var ta = document.createElement('textarea');
2015      ta.value = text;
2016      ta.setAttribute('readonly', '');
2017      ta.style.cssText = 'position:fixed;top:0;left:-9999px;opacity:0';
2018      document.body.appendChild(ta);
2019      ta.select();
2020      ta.setSelectionRange(0, ta.value.length);
2021      var ok = document.execCommand('copy');
2022      document.body.removeChild(ta);
2023      return ok;
2024    } catch (e) { return false; }
2025  }
2026
2027  function flash(btn, ok) {
2028    btn.textContent = ok ? 'Copied!' : 'Press \u2318C';
2029    btn.classList.remove('is-copied', 'is-failed');
2030    btn.classList.add(ok ? 'is-copied' : 'is-failed');
2031    clearTimeout(btn._fnTimer);
2032    btn._fnTimer = setTimeout(function () {
2033      btn.textContent = 'Copy';
2034      btn.classList.remove('is-copied', 'is-failed');
2035    }, 2000);
2036  }
2037
2038  function copy(text, btn) {
2039    if (navigator.clipboard && navigator.clipboard.writeText && window.isSecureContext) {
2040      navigator.clipboard.writeText(text).then(
2041        function () { flash(btn, true); },
2042        function () { flash(btn, legacyCopy(text)); }
2043      );
2044    } else {
2045      flash(btn, legacyCopy(text));
2046    }
2047  }
2048
2049  function attach(el) {
2050    if (el.getAttribute('data-fn-cb')) return;
2051    var text = payload(el);
2052    if (!text) return;
2053    el.setAttribute('data-fn-cb', '1');
2054    el.classList.add('fn-cb-block');
2055
2056    /* Wrap rather than position inside: pre/.term are overflow-x:auto, so a
2057       button parented to them would scroll away with the content. */
2058    var wrap = document.createElement('div');
2059    wrap.className = 'fn-cb';
2060    el.parentNode.insertBefore(wrap, el);
2061    wrap.appendChild(el);
2062
2063    var btn = document.createElement('button');
2064    btn.type = 'button';
2065    btn.className = 'fn-cb-btn';
2066    btn.textContent = 'Copy';
2067    btn.setAttribute('aria-label', 'Copy code to clipboard');
2068    btn.addEventListener('click', function (e) {
2069      e.preventDefault();
2070      copy(text, btn);
2071    });
2072    wrap.appendChild(btn);
2073  }
2074
2075  function init() {
2076    var nodes = document.querySelectorAll('pre, .term');
2077    for (var i = 0; i < nodes.length; i++) attach(nodes[i]);
2078  }
2079
2080  if (document.readyState === 'loading') {
2081    document.addEventListener('DOMContentLoaded', init);
2082  } else {
2083    init();
2084  }
2085})();
2086</script>
2086
2087<!-- FN-COPY-BTN-CLOSE -->
2088<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a44ef1d3ddf47e38',t:'MTc5MTA2MTk0Mg=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script>
2088<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"7d6d2efd170341a38bb6c5e09af862b7","r":1,"spa":2}' crossorigin="anonymous"></script>
2088
2089</body>
2090</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.