tailprint 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +160 -2
- package/dist/config/env.d.ts +77 -0
- package/dist/config/env.js +38 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/plans.d.ts +15 -0
- package/dist/config/plans.js +104 -0
- package/dist/config/plans.js.map +1 -0
- package/dist/config/templates.d.ts +10 -0
- package/dist/config/templates.js +746 -0
- package/dist/config/templates.js.map +1 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.js +897 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.d.ts +2 -0
- package/dist/mcp/stdio.js +27 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/routes/dashboard.d.ts +2 -0
- package/dist/routes/dashboard.js +454 -0
- package/dist/routes/dashboard.js.map +1 -0
- package/dist/routes/mcp.d.ts +2 -0
- package/dist/routes/mcp.js +329 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/render.d.ts +2 -0
- package/dist/routes/render.js +444 -0
- package/dist/routes/render.js.map +1 -0
- package/dist/routes/scrape.d.ts +2 -0
- package/dist/routes/scrape.js +132 -0
- package/dist/routes/scrape.js.map +1 -0
- package/dist/routes/templates.d.ts +2 -0
- package/dist/routes/templates.js +2449 -0
- package/dist/routes/templates.js.map +1 -0
- package/dist/routes/usage.d.ts +31 -0
- package/dist/routes/usage.js +73 -0
- package/dist/routes/usage.js.map +1 -0
- package/dist/routes/webhooks.d.ts +2 -0
- package/dist/routes/webhooks.js +227 -0
- package/dist/routes/webhooks.js.map +1 -0
- package/dist/schemas/render.d.ts +422 -0
- package/dist/schemas/render.js +91 -0
- package/dist/schemas/render.js.map +1 -0
- package/dist/schemas/scrape.d.ts +65 -0
- package/dist/schemas/scrape.js +24 -0
- package/dist/schemas/scrape.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +206 -0
- package/dist/server.js.map +1 -0
- package/dist/services/browser.d.ts +21 -0
- package/dist/services/browser.js +299 -0
- package/dist/services/browser.js.map +1 -0
- package/dist/services/carousel.d.ts +43 -0
- package/dist/services/carousel.js +300 -0
- package/dist/services/carousel.js.map +1 -0
- package/dist/services/chart.d.ts +29 -0
- package/dist/services/chart.js +129 -0
- package/dist/services/chart.js.map +1 -0
- package/dist/services/eInvoice.d.ts +62 -0
- package/dist/services/eInvoice.js +413 -0
- package/dist/services/eInvoice.js.map +1 -0
- package/dist/services/email.d.ts +24 -0
- package/dist/services/email.js +177 -0
- package/dist/services/email.js.map +1 -0
- package/dist/services/mockup.d.ts +14 -0
- package/dist/services/mockup.js +130 -0
- package/dist/services/mockup.js.map +1 -0
- package/dist/services/pdfStamper.d.ts +16 -0
- package/dist/services/pdfStamper.js +47 -0
- package/dist/services/pdfStamper.js.map +1 -0
- package/dist/services/qr.d.ts +20 -0
- package/dist/services/qr.js +44 -0
- package/dist/services/qr.js.map +1 -0
- package/dist/services/redis.d.ts +65 -0
- package/dist/services/redis.js +318 -0
- package/dist/services/redis.js.map +1 -0
- package/dist/services/scraper.d.ts +16 -0
- package/dist/services/scraper.js +138 -0
- package/dist/services/scraper.js.map +1 -0
- package/dist/services/storage.d.ts +38 -0
- package/dist/services/storage.js +123 -0
- package/dist/services/storage.js.map +1 -0
- package/dist/services/stripe.d.ts +32 -0
- package/dist/services/stripe.js +229 -0
- package/dist/services/stripe.js.map +1 -0
- package/dist/services/visualDiff.d.ts +33 -0
- package/dist/services/visualDiff.js +218 -0
- package/dist/services/visualDiff.js.map +1 -0
- package/package.json +74 -4
|
@@ -0,0 +1,2449 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { TEMPLATES } from '../config/templates.js';
|
|
5
|
+
import { PLANS } from '../config/plans.js';
|
|
6
|
+
import { browserService } from '../services/browser.js';
|
|
7
|
+
import { stripeService } from '../services/stripe.js';
|
|
8
|
+
import { env } from '../config/env.js';
|
|
9
|
+
export const templateRoutes = async (fastify) => {
|
|
10
|
+
// GET /templates - Return JSON catalog of templates
|
|
11
|
+
fastify.get('/templates', {
|
|
12
|
+
schema: {
|
|
13
|
+
tags: ['Templates'],
|
|
14
|
+
summary: 'List Ready-to-Use Tailwind Templates',
|
|
15
|
+
description: 'Returns pre-designed Tailwind CSS templates for Invoices, Certificates, Executive Reports, Resumes, OG Banners, and Shipping Labels.',
|
|
16
|
+
},
|
|
17
|
+
}, async () => {
|
|
18
|
+
return {
|
|
19
|
+
templates: TEMPLATES.map((t) => ({
|
|
20
|
+
id: t.id,
|
|
21
|
+
title: t.title,
|
|
22
|
+
category: t.category,
|
|
23
|
+
description: t.description,
|
|
24
|
+
format: t.format,
|
|
25
|
+
landscape: t.landscape,
|
|
26
|
+
})),
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
// GET /templates/:id/download - Live render and download PDF
|
|
30
|
+
fastify.get('/templates/:id/download', {
|
|
31
|
+
schema: {
|
|
32
|
+
tags: ['Templates'],
|
|
33
|
+
summary: 'Download Rendered PDF for Template',
|
|
34
|
+
params: z.object({
|
|
35
|
+
id: z.string(),
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
}, async (request, reply) => {
|
|
39
|
+
const { id } = request.params;
|
|
40
|
+
const template = TEMPLATES.find((t) => t.id === id);
|
|
41
|
+
if (!template) {
|
|
42
|
+
return reply.code(404).send({
|
|
43
|
+
statusCode: 404,
|
|
44
|
+
error: 'Not Found',
|
|
45
|
+
message: `Template '${id}' not found. Available: ${TEMPLATES.map((t) => t.id).join(', ')}`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (template.id === 'og-card') {
|
|
49
|
+
// Special case: OG card renders as high-res PNG image
|
|
50
|
+
const imgBuffer = await browserService.renderImage({
|
|
51
|
+
html: template.html,
|
|
52
|
+
width: 1200,
|
|
53
|
+
height: 630,
|
|
54
|
+
type: 'png',
|
|
55
|
+
});
|
|
56
|
+
return reply
|
|
57
|
+
.code(200)
|
|
58
|
+
.header('Content-Type', 'image/png')
|
|
59
|
+
.header('Content-Disposition', `attachment; filename="${template.id}-preview.png"`)
|
|
60
|
+
.header('Content-Length', String(imgBuffer.length))
|
|
61
|
+
.send(imgBuffer);
|
|
62
|
+
}
|
|
63
|
+
const pdfBuffer = await browserService.renderPdf({
|
|
64
|
+
html: template.html,
|
|
65
|
+
format: template.format,
|
|
66
|
+
landscape: template.landscape,
|
|
67
|
+
print_background: true,
|
|
68
|
+
});
|
|
69
|
+
return reply
|
|
70
|
+
.code(200)
|
|
71
|
+
.header('Content-Type', 'application/pdf')
|
|
72
|
+
.header('Content-Disposition', `attachment; filename="${template.id}-preview.pdf"`)
|
|
73
|
+
.header('Content-Length', String(pdfBuffer.length))
|
|
74
|
+
.send(pdfBuffer);
|
|
75
|
+
});
|
|
76
|
+
// Internal checkout endpoints for frontend landing page pricing buttons
|
|
77
|
+
fastify.post('/v1/billing/checkout', { schema: { hide: true } }, async (request, reply) => {
|
|
78
|
+
const { planId, customerEmail } = request.body;
|
|
79
|
+
const session = await stripeService.createCheckoutSession({
|
|
80
|
+
planId,
|
|
81
|
+
customerEmail,
|
|
82
|
+
});
|
|
83
|
+
return reply.code(200).send({
|
|
84
|
+
success: true,
|
|
85
|
+
sessionId: session.sessionId,
|
|
86
|
+
checkoutUrl: session.url,
|
|
87
|
+
isMock: session.isMock,
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
fastify.post('/billing/create-checkout-session', { schema: { hide: true } }, async (request, reply) => {
|
|
91
|
+
const { planId, customerEmail } = request.body;
|
|
92
|
+
const session = await stripeService.createCheckoutSession({
|
|
93
|
+
planId,
|
|
94
|
+
customerEmail,
|
|
95
|
+
});
|
|
96
|
+
return reply.code(200).send({
|
|
97
|
+
success: true,
|
|
98
|
+
sessionId: session.sessionId,
|
|
99
|
+
checkoutUrl: session.url,
|
|
100
|
+
isMock: session.isMock,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
// GET /og-image.png - Open Graph preview image
|
|
104
|
+
fastify.get('/og-image.png', { schema: { hide: true } }, async (_request, reply) => {
|
|
105
|
+
const ogPath = path.join(process.cwd(), 'assets', 'og-image.png');
|
|
106
|
+
if (fs.existsSync(ogPath)) {
|
|
107
|
+
const buffer = fs.readFileSync(ogPath);
|
|
108
|
+
return reply
|
|
109
|
+
.header('Content-Type', 'image/png')
|
|
110
|
+
.header('Cache-Control', 'public, max-age=86400, stale-while-revalidate=604800')
|
|
111
|
+
.send(buffer);
|
|
112
|
+
}
|
|
113
|
+
return reply.code(404).send({ error: 'OG image not found' });
|
|
114
|
+
});
|
|
115
|
+
// GET /robots.txt - Search & AI Crawler Directives (SEO & GEO)
|
|
116
|
+
fastify.get('/robots.txt', { schema: { hide: true } }, async (_request, reply) => {
|
|
117
|
+
const robots = [
|
|
118
|
+
'User-agent: *',
|
|
119
|
+
'Allow: /',
|
|
120
|
+
'Disallow: /v1/billing/portal',
|
|
121
|
+
'',
|
|
122
|
+
'# AI Crawlers & Answer Engines (GEO - Generative Engine Optimization)',
|
|
123
|
+
'User-agent: GPTBot',
|
|
124
|
+
'Allow: /',
|
|
125
|
+
'',
|
|
126
|
+
'User-agent: ChatGPT-User',
|
|
127
|
+
'Allow: /',
|
|
128
|
+
'',
|
|
129
|
+
'User-agent: PerplexityBot',
|
|
130
|
+
'Allow: /',
|
|
131
|
+
'',
|
|
132
|
+
'User-agent: ClaudeBot',
|
|
133
|
+
'Allow: /',
|
|
134
|
+
'',
|
|
135
|
+
'User-agent: Anthropic-ai',
|
|
136
|
+
'Allow: /',
|
|
137
|
+
'',
|
|
138
|
+
'User-agent: Google-Extended',
|
|
139
|
+
'Allow: /',
|
|
140
|
+
'',
|
|
141
|
+
'User-agent: Cohere-ai',
|
|
142
|
+
'Allow: /',
|
|
143
|
+
'',
|
|
144
|
+
'Sitemap: https://tailprint.com/sitemap.xml',
|
|
145
|
+
''
|
|
146
|
+
].join('\n');
|
|
147
|
+
return reply
|
|
148
|
+
.header('Content-Type', 'text/plain; charset=utf-8')
|
|
149
|
+
.header('Cache-Control', 'public, max-age=3600')
|
|
150
|
+
.send(robots);
|
|
151
|
+
});
|
|
152
|
+
// GET /sitemap.xml - XML Sitemap for Search Engines
|
|
153
|
+
fastify.get('/sitemap.xml', { schema: { hide: true } }, async (_request, reply) => {
|
|
154
|
+
const today = new Date().toISOString().split('T')[0];
|
|
155
|
+
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
|
156
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
157
|
+
<url>
|
|
158
|
+
<loc>https://tailprint.com/</loc>
|
|
159
|
+
<lastmod>${today}</lastmod>
|
|
160
|
+
<changefreq>daily</changefreq>
|
|
161
|
+
<priority>1.0</priority>
|
|
162
|
+
</url>
|
|
163
|
+
<url>
|
|
164
|
+
<loc>https://tailprint.com/docs</loc>
|
|
165
|
+
<lastmod>${today}</lastmod>
|
|
166
|
+
<changefreq>weekly</changefreq>
|
|
167
|
+
<priority>0.9</priority>
|
|
168
|
+
</url>
|
|
169
|
+
<url>
|
|
170
|
+
<loc>https://tailprint.com/dashboard</loc>
|
|
171
|
+
<lastmod>${today}</lastmod>
|
|
172
|
+
<changefreq>weekly</changefreq>
|
|
173
|
+
<priority>0.7</priority>
|
|
174
|
+
</url>
|
|
175
|
+
<url>
|
|
176
|
+
<loc>https://tailprint.com/llms.txt</loc>
|
|
177
|
+
<lastmod>${today}</lastmod>
|
|
178
|
+
<changefreq>weekly</changefreq>
|
|
179
|
+
<priority>0.6</priority>
|
|
180
|
+
</url>
|
|
181
|
+
</urlset>`;
|
|
182
|
+
return reply
|
|
183
|
+
.header('Content-Type', 'application/xml; charset=utf-8')
|
|
184
|
+
.header('Cache-Control', 'public, max-age=3600')
|
|
185
|
+
.send(sitemap);
|
|
186
|
+
});
|
|
187
|
+
// GET /llms.txt - Generative Engine Optimization standard for AI (llmstxt.org)
|
|
188
|
+
fastify.get('/llms.txt', { schema: { hide: true } }, async (_request, reply) => {
|
|
189
|
+
const llms = `# Tailprint — Tailwind-Native Render & PDF Engine + AI Web Scraper
|
|
190
|
+
|
|
191
|
+
> High-performance developer API and Model Context Protocol (MCP) server for converting Tailwind CSS HTML into vector PDFs and cached Open Graph preview images in under 500ms.
|
|
192
|
+
|
|
193
|
+
## Core Capabilities
|
|
194
|
+
- **Tailwind-Native PDF Engine**: Submits raw HTML styled with arbitrary Tailwind CSS classes (including JIT syntax like \`w-[350px]\` and dynamic grids) and generates vector-sharp, print-ready PDF documents without build steps or headless browser maintenance.
|
|
195
|
+
- **Open Graph Card Generator**: Renders social preview cards (PNG/JPEG) with automatic SHA-256 caching and sub-400ms cached delivery.
|
|
196
|
+
- **LLM Web Scraper**: Scrapes public URLs, strips ads, scripts, and clutter with Mozilla Readability, and outputs token-efficient structured Markdown saving 90%+ context window tokens.
|
|
197
|
+
- **Model Context Protocol (MCP)**: Native Remote SSE and stdio CLI integration for Claude Desktop, Cursor, Windsurf, and autonomous AI agents.
|
|
198
|
+
|
|
199
|
+
## Quick API Reference
|
|
200
|
+
- Base URL: https://tailprint.com
|
|
201
|
+
- Documentation: https://tailprint.com/docs
|
|
202
|
+
- MCP Remote SSE Endpoint: https://tailprint.com/v1/mcp/sse
|
|
203
|
+
- Developer Free Tier: 100 free monthly renders with zero credit card required
|
|
204
|
+
|
|
205
|
+
### Key Endpoints
|
|
206
|
+
- POST /v1/render/pdf : Compiles raw HTML + Tailwind CSS to vector PDF
|
|
207
|
+
- POST /v1/render/image : Compiles raw HTML + Tailwind CSS to 1200x630 PNG/JPEG
|
|
208
|
+
- POST /v1/scrape/markdown : Scrapes public URL to clean LLM-ready Markdown
|
|
209
|
+
- POST /v1/auth/claim-free-key : Claim instant free developer API key
|
|
210
|
+
|
|
211
|
+
## Model Context Protocol (MCP) Setup
|
|
212
|
+
Claude Desktop (\`claude_desktop_config.json\`):
|
|
213
|
+
\`\`\`json
|
|
214
|
+
{
|
|
215
|
+
"mcpServers": {
|
|
216
|
+
"tailprint": {
|
|
217
|
+
"url": "https://tailprint.com/v1/mcp/sse",
|
|
218
|
+
"headers": {
|
|
219
|
+
"Authorization": "Bearer YOUR_API_KEY"
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
\`\`\`
|
|
225
|
+
|
|
226
|
+
Cursor IDE (\`Settings > Features > MCP Servers > Add\`):
|
|
227
|
+
- Name: tailprint
|
|
228
|
+
- Type: sse
|
|
229
|
+
- URL: https://tailprint.com/v1/mcp/sse?api_key=YOUR_API_KEY
|
|
230
|
+
`;
|
|
231
|
+
return reply
|
|
232
|
+
.header('Content-Type', 'text/markdown; charset=utf-8')
|
|
233
|
+
.header('Cache-Control', 'public, max-age=3600')
|
|
234
|
+
.send(llms);
|
|
235
|
+
});
|
|
236
|
+
// GET / - Showcase Landing Page & Template Gallery
|
|
237
|
+
fastify.get('/', {
|
|
238
|
+
schema: {
|
|
239
|
+
hide: true,
|
|
240
|
+
},
|
|
241
|
+
}, async (_request, reply) => {
|
|
242
|
+
const html = `<!DOCTYPE html>
|
|
243
|
+
<html lang="en" class="dark scroll-smooth">
|
|
244
|
+
<head>
|
|
245
|
+
<meta charset="utf-8" />
|
|
246
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
247
|
+
<title>Tailprint — Tailwind-Native Render & PDF Engine + AI Web Scraper</title>
|
|
248
|
+
<link rel="canonical" href="https://tailprint.com" />
|
|
249
|
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none'%3E%3Crect width='32' height='32' rx='8' fill='%23090d16'/%3E%3Cpath d='M11 9L6 16l5 7' stroke='%23818cf8' stroke-width='2.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M21 9l5 7-5 7' stroke='%23f472b6' stroke-width='2.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cline x1='18' y1='8' x2='14' y2='24' stroke='%2338bdf8' stroke-width='2.5' stroke-linecap='round'/%3E%3Ccircle cx='16' cy='16' r='1.5' fill='%23f43f5e'/%3E%3C/svg%3E" />
|
|
250
|
+
|
|
251
|
+
<!-- Primary Meta Tags (SEO) -->
|
|
252
|
+
<meta name="description" content="Tailprint is the high-performance Tailwind-Native Render & PDF Engine + Model Context Protocol (MCP) server. Generate print-ready vector PDFs and cached social cards in under 500ms." />
|
|
253
|
+
<meta name="keywords" content="Tailwind HTML to PDF, Tailwind CSS PDF API, PDF generation API, Model Context Protocol PDF, MCP server, vector PDF engine, Open Graph image generator, HTML to image API, web scraper for LLM, token efficient markdown" />
|
|
254
|
+
<meta name="author" content="Tailprint" />
|
|
255
|
+
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" />
|
|
256
|
+
<meta name="theme-color" content="#090d16" />
|
|
257
|
+
|
|
258
|
+
<!-- Open Graph / Facebook / LinkedIn / Discord -->
|
|
259
|
+
<meta property="og:type" content="website" />
|
|
260
|
+
<meta property="og:url" content="https://tailprint.com" />
|
|
261
|
+
<meta property="og:site_name" content="Tailprint" />
|
|
262
|
+
<meta property="og:locale" content="en_US" />
|
|
263
|
+
<meta property="og:title" content="Tailprint — Tailwind-Native Render & PDF Engine + AI Web Scraper" />
|
|
264
|
+
<meta property="og:description" content="Sub-500ms PDF & Open Graph image generation from raw Tailwind CSS, plus LLM-ready web scraping with 90%+ token savings. Model Context Protocol (MCP) ready." />
|
|
265
|
+
<meta property="og:image" content="https://tailprint.com/og-image.png" />
|
|
266
|
+
<meta property="og:image:width" content="1200" />
|
|
267
|
+
<meta property="og:image:height" content="630" />
|
|
268
|
+
<meta property="og:image:alt" content="Tailprint — Developer Render & PDF Engine" />
|
|
269
|
+
|
|
270
|
+
<!-- Twitter / X -->
|
|
271
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
272
|
+
<meta name="twitter:url" content="https://tailprint.com" />
|
|
273
|
+
<meta name="twitter:site" content="@tailprint" />
|
|
274
|
+
<meta name="twitter:creator" content="@tailprint" />
|
|
275
|
+
<meta name="twitter:title" content="Tailprint — Tailwind-Native Render & PDF Engine" />
|
|
276
|
+
<meta name="twitter:description" content="Turn Tailwind CSS HTML into high-fidelity vector PDFs and Open Graph preview cards in under 500ms. Official MCP server for Claude and Cursor." />
|
|
277
|
+
<meta name="twitter:image" content="https://tailprint.com/og-image.png" />
|
|
278
|
+
<meta name="twitter:image:alt" content="Tailprint — Developer Render & PDF Engine" />
|
|
279
|
+
|
|
280
|
+
<!-- Schema.org JSON-LD Structured Data for Search & Generative Engines (SEO & GEO) -->
|
|
281
|
+
<script type="application/ld+json">
|
|
282
|
+
{
|
|
283
|
+
"@context": "https://schema.org",
|
|
284
|
+
"@graph": [
|
|
285
|
+
{
|
|
286
|
+
"@type": "WebApplication",
|
|
287
|
+
"@id": "https://tailprint.com/#webapp",
|
|
288
|
+
"name": "Tailprint",
|
|
289
|
+
"url": "https://tailprint.com",
|
|
290
|
+
"applicationCategory": "DeveloperApplication",
|
|
291
|
+
"operatingSystem": "All (Cloud API & MCP Server)",
|
|
292
|
+
"description": "High-performance Tailwind-Native Render & PDF Engine + AI Web Scraper. Compiles raw Tailwind CSS HTML into vector PDFs and cached social preview images in under 500ms.",
|
|
293
|
+
"offers": [
|
|
294
|
+
{
|
|
295
|
+
"@type": "Offer",
|
|
296
|
+
"price": "0",
|
|
297
|
+
"priceCurrency": "USD",
|
|
298
|
+
"name": "Developer Free Tier",
|
|
299
|
+
"description": "100 free monthly renders, zero credit card required"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"@type": "Offer",
|
|
303
|
+
"price": "14",
|
|
304
|
+
"priceCurrency": "USD",
|
|
305
|
+
"name": "Starter Plan",
|
|
306
|
+
"description": "2,500 monthly renders"
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
"@type": "Offer",
|
|
310
|
+
"price": "39",
|
|
311
|
+
"priceCurrency": "USD",
|
|
312
|
+
"name": "Pro Plan",
|
|
313
|
+
"description": "20,000 monthly renders with prioritized Chromium pool"
|
|
314
|
+
}
|
|
315
|
+
],
|
|
316
|
+
"featureList": [
|
|
317
|
+
"Tailwind CSS JIT Rendering without build steps",
|
|
318
|
+
"Sub-500ms Vector PDF Generation",
|
|
319
|
+
"Open Graph Social Preview Cards (PNG/JPEG)",
|
|
320
|
+
"Model Context Protocol (MCP) Server for Claude Desktop & Cursor",
|
|
321
|
+
"LLM-Ready Web Scraper returning structured Markdown with 90%+ token savings",
|
|
322
|
+
"SHA-256 Deduplication Image Caching",
|
|
323
|
+
"Automated SSRF Sandboxing and Private IP Blocking"
|
|
324
|
+
]
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"@type": "Organization",
|
|
328
|
+
"@id": "https://tailprint.com/#organization",
|
|
329
|
+
"name": "Tailprint",
|
|
330
|
+
"url": "https://tailprint.com",
|
|
331
|
+
"logo": "https://tailprint.com/assets/tailprint-logo.svg",
|
|
332
|
+
"contactPoint": {
|
|
333
|
+
"@type": "ContactPoint",
|
|
334
|
+
"email": "support@tailprint.com",
|
|
335
|
+
"contactType": "customer support"
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"@type": "FAQPage",
|
|
340
|
+
"@id": "https://tailprint.com/#faq",
|
|
341
|
+
"mainEntity": [
|
|
342
|
+
{
|
|
343
|
+
"@type": "Question",
|
|
344
|
+
"name": "How does Tailprint convert Tailwind CSS HTML to vector PDF?",
|
|
345
|
+
"acceptedAnswer": {
|
|
346
|
+
"@type": "Answer",
|
|
347
|
+
"text": "Tailprint uses isolated, pre-warmed Chromium instances with native Tailwind CSS JIT compilation. You send raw HTML containing arbitrary utility classes (e.g. w-[350px], grid-cols-12, custom gradients), and Tailprint compiles print-ready vector PDFs in under 500ms with zero build steps or PostCSS configurations."
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"@type": "Question",
|
|
352
|
+
"name": "How do I connect Tailprint to Claude Desktop, Cursor, or AI coding agents via MCP?",
|
|
353
|
+
"acceptedAnswer": {
|
|
354
|
+
"@type": "Answer",
|
|
355
|
+
"text": "Tailprint implements the official Model Context Protocol (MCP) specification. You can connect it via Remote SSE at https://tailprint.com/v1/mcp/sse by adding Authorization: Bearer YOUR_API_KEY to your claude_desktop_config.json or Cursor MCP settings, or run it locally via stdio using npx @tailprint/mcp."
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
"@type": "Question",
|
|
360
|
+
"name": "What are the latency benchmarks and caching mechanisms?",
|
|
361
|
+
"acceptedAnswer": {
|
|
362
|
+
"@type": "Answer",
|
|
363
|
+
"text": "Fresh PDF renders execute in 350-500ms on pre-warmed Chromium instances. For images and social cards, Tailprint calculates a SHA-256 hash of the input markup, returning instant cache hits in under 50ms with X-Cache: HIT."
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
"@type": "Question",
|
|
368
|
+
"name": "What security and SSRF protections does Tailprint enforce?",
|
|
369
|
+
"acceptedAnswer": {
|
|
370
|
+
"@type": "Answer",
|
|
371
|
+
"text": "Tailprint features an enterprise-grade network security sandbox that automatically blocks all local, internal, and private IP ranges including 127.0.0.1, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, and cloud metadata services like 169.254.169.254 to prevent Server-Side Request Forgery (SSRF)."
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
"@type": "Question",
|
|
376
|
+
"name": "Does Tailprint offer a free tier?",
|
|
377
|
+
"acceptedAnswer": {
|
|
378
|
+
"@type": "Answer",
|
|
379
|
+
"text": "Yes, Tailprint provides a Developer Free Sandbox offering 100 free monthly renders with zero credit card required. It includes full access to the Tailwind rendering engine, PDF generator, and MCP server."
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
]
|
|
383
|
+
}
|
|
384
|
+
]
|
|
385
|
+
}
|
|
386
|
+
</script>
|
|
387
|
+
<script>
|
|
388
|
+
const _warn = console.warn;
|
|
389
|
+
console.warn = function(...args) {
|
|
390
|
+
if (args[0] && typeof args[0] === 'string' && args[0].includes('cdn.tailwindcss.com')) return;
|
|
391
|
+
_warn.apply(console, args);
|
|
392
|
+
};
|
|
393
|
+
</script>
|
|
394
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
395
|
+
<script>
|
|
396
|
+
tailwind.config = {
|
|
397
|
+
darkMode: 'class',
|
|
398
|
+
theme: {
|
|
399
|
+
extend: {
|
|
400
|
+
colors: {
|
|
401
|
+
brand: {
|
|
402
|
+
50: '#eef2ff',
|
|
403
|
+
500: '#6366f1',
|
|
404
|
+
600: '#4f46e5',
|
|
405
|
+
700: '#4338ca',
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
</script>
|
|
412
|
+
<style>
|
|
413
|
+
pre code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
|
|
414
|
+
@keyframes avatarFloat {
|
|
415
|
+
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
|
416
|
+
50% { transform: translateY(-8px) rotate(1deg); }
|
|
417
|
+
}
|
|
418
|
+
@keyframes cardFloatLeft {
|
|
419
|
+
0%, 100% { transform: translateY(0px) rotate(-2deg); }
|
|
420
|
+
50% { transform: translateY(-6px) rotate(-1deg); }
|
|
421
|
+
}
|
|
422
|
+
@keyframes cardFloatRight {
|
|
423
|
+
0%, 100% { transform: translateY(0px) rotate(2deg); }
|
|
424
|
+
50% { transform: translateY(-7px) rotate(3deg); }
|
|
425
|
+
}
|
|
426
|
+
@keyframes eyeBlink {
|
|
427
|
+
0%, 90%, 100% { transform: scaleY(1); }
|
|
428
|
+
95% { transform: scaleY(0.1); }
|
|
429
|
+
}
|
|
430
|
+
.animate-avatar {
|
|
431
|
+
animation: avatarFloat 4.2s ease-in-out infinite;
|
|
432
|
+
}
|
|
433
|
+
.animate-float-left {
|
|
434
|
+
animation: cardFloatLeft 5s ease-in-out infinite;
|
|
435
|
+
}
|
|
436
|
+
.animate-float-right {
|
|
437
|
+
animation: cardFloatRight 5.5s ease-in-out infinite 0.5s;
|
|
438
|
+
}
|
|
439
|
+
.avatar-eye-left, .avatar-eye-right {
|
|
440
|
+
transform-origin: center;
|
|
441
|
+
animation: eyeBlink 3.6s ease-in-out infinite;
|
|
442
|
+
}
|
|
443
|
+
</style>
|
|
444
|
+
</head>
|
|
445
|
+
<body class="bg-slate-950 text-slate-100 min-h-screen selection:bg-indigo-500 selection:text-white">
|
|
446
|
+
|
|
447
|
+
<!-- Navigation -->
|
|
448
|
+
<header class="sticky top-0 z-50 backdrop-blur-md bg-slate-950/85 border-b border-slate-800/80">
|
|
449
|
+
<div class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
|
|
450
|
+
|
|
451
|
+
<!-- Brand Logo & Main Navigation -->
|
|
452
|
+
<div class="flex items-center space-x-8">
|
|
453
|
+
<a href="/" class="flex items-center group transition hover:opacity-90">
|
|
454
|
+
<span class="font-extrabold text-2xl tracking-tight text-white flex items-center">
|
|
455
|
+
<span class="text-indigo-400 font-mono font-bold mr-1"><</span>tail<span class="bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent">print</span><span class="text-pink-400 font-mono font-bold ml-1">/></span>
|
|
456
|
+
</span>
|
|
457
|
+
</a>
|
|
458
|
+
|
|
459
|
+
<!-- Main Product Links (Desktop) -->
|
|
460
|
+
<nav class="hidden md:flex items-center space-x-1 text-sm font-medium">
|
|
461
|
+
<a href="#playground" class="px-3 py-1.5 rounded-lg text-slate-300 hover:text-white hover:bg-slate-900/80 transition flex items-center space-x-1.5">
|
|
462
|
+
<span class="w-1.5 h-1.5 rounded-full bg-indigo-400"></span>
|
|
463
|
+
<span>Playground</span>
|
|
464
|
+
</a>
|
|
465
|
+
<a href="#templates" class="px-3 py-1.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-900/80 transition flex items-center space-x-1.5">
|
|
466
|
+
<span>Templates</span>
|
|
467
|
+
<span class="text-[10px] px-1.5 py-0.5 rounded-full bg-slate-800 text-slate-400 font-mono leading-none">${TEMPLATES.length}</span>
|
|
468
|
+
</a>
|
|
469
|
+
<a href="#scraper" class="px-3 py-1.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-900/80 transition">
|
|
470
|
+
Scraper
|
|
471
|
+
</a>
|
|
472
|
+
<a href="#mcp" class="px-3 py-1.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-900/80 transition flex items-center space-x-1.5">
|
|
473
|
+
<span>MCP Server</span>
|
|
474
|
+
<span class="text-[9px] px-1.5 py-0.5 rounded-full bg-purple-500/10 text-purple-400 border border-purple-500/20 font-semibold uppercase tracking-wider leading-none">AI</span>
|
|
475
|
+
</a>
|
|
476
|
+
<a href="#pricing" class="px-3 py-1.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-900/80 transition">
|
|
477
|
+
Pricing
|
|
478
|
+
</a>
|
|
479
|
+
<a href="#faq" class="px-3 py-1.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-900/80 transition">
|
|
480
|
+
FAQ
|
|
481
|
+
</a>
|
|
482
|
+
<a href="/docs" target="_blank" class="px-3 py-1.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-900/80 transition flex items-center space-x-1">
|
|
483
|
+
<span>Docs</span>
|
|
484
|
+
<svg class="w-3 h-3 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg>
|
|
485
|
+
</a>
|
|
486
|
+
</nav>
|
|
487
|
+
</div>
|
|
488
|
+
|
|
489
|
+
<!-- User Actions & CTA -->
|
|
490
|
+
<div class="flex items-center space-x-3">
|
|
491
|
+
<a href="/dashboard" class="hidden sm:inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold text-slate-300 hover:text-white hover:bg-slate-900 border border-slate-800 transition">
|
|
492
|
+
Customer Portal
|
|
493
|
+
</a>
|
|
494
|
+
<button onclick="startCheckout('free')" class="px-3.5 py-1.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-semibold transition shadow-md shadow-emerald-600/20 flex items-center space-x-1.5">
|
|
495
|
+
<span class="text-xs">⚡</span>
|
|
496
|
+
<span>Claim Free Key</span>
|
|
497
|
+
</button>
|
|
498
|
+
|
|
499
|
+
<!-- Mobile Menu Toggle Button -->
|
|
500
|
+
<button onclick="document.getElementById('mobile-menu').classList.toggle('hidden')" class="md:hidden p-2 rounded-lg text-slate-400 hover:text-white hover:bg-slate-900 border border-slate-800" aria-label="Toggle navigation">
|
|
501
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
502
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
|
|
503
|
+
</svg>
|
|
504
|
+
</button>
|
|
505
|
+
</div>
|
|
506
|
+
|
|
507
|
+
</div>
|
|
508
|
+
|
|
509
|
+
<!-- Mobile Slide-down Navigation -->
|
|
510
|
+
<div id="mobile-menu" class="hidden md:hidden border-t border-slate-800/80 bg-slate-950/95 px-6 py-4 space-y-2">
|
|
511
|
+
<a href="#playground" onclick="document.getElementById('mobile-menu').classList.add('hidden')" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-200 hover:bg-slate-900">Live Playground</a>
|
|
512
|
+
<a href="#templates" onclick="document.getElementById('mobile-menu').classList.add('hidden')" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-300 hover:bg-slate-900">Templates (${TEMPLATES.length})</a>
|
|
513
|
+
<a href="#scraper" onclick="document.getElementById('mobile-menu').classList.add('hidden')" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-300 hover:bg-slate-900">URL Scraper</a>
|
|
514
|
+
<a href="#mcp" onclick="document.getElementById('mobile-menu').classList.add('hidden')" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-300 hover:bg-slate-900">MCP Server (AI)</a>
|
|
515
|
+
<a href="#pricing" onclick="document.getElementById('mobile-menu').classList.add('hidden')" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-300 hover:bg-slate-900">Pricing</a>
|
|
516
|
+
<a href="#faq" onclick="document.getElementById('mobile-menu').classList.add('hidden')" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-300 hover:bg-slate-900">FAQ</a>
|
|
517
|
+
<a href="/docs" target="_blank" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-300 hover:bg-slate-900">API Documentation</a>
|
|
518
|
+
<a href="/dashboard" class="block px-3 py-2 rounded-lg text-sm font-medium text-indigo-400 hover:bg-slate-900">Customer Dashboard</a>
|
|
519
|
+
</div>
|
|
520
|
+
</header>
|
|
521
|
+
|
|
522
|
+
<!-- Hero Section -->
|
|
523
|
+
<section class="relative pt-12 pb-20 overflow-hidden">
|
|
524
|
+
<!-- Ambient Gradient Backdrops -->
|
|
525
|
+
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-[700px] h-[350px] bg-gradient-to-tr from-indigo-600/20 via-purple-600/15 to-cyan-500/15 rounded-full blur-[110px] pointer-events-none"></div>
|
|
526
|
+
<div class="absolute inset-0 bg-[radial-gradient(#1e293b_1px,transparent_1px)] [background-size:24px_24px] opacity-25"></div>
|
|
527
|
+
|
|
528
|
+
<div class="max-w-6xl mx-auto px-6 relative z-10 text-center">
|
|
529
|
+
|
|
530
|
+
<!-- Top Banner Pill -->
|
|
531
|
+
<div class="inline-flex items-center space-x-2 px-3.5 py-1.5 rounded-full bg-indigo-950/70 border border-indigo-800/60 text-indigo-300 text-xs font-semibold mb-6 backdrop-blur-md shadow-lg shadow-indigo-950/50">
|
|
532
|
+
<span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span>
|
|
533
|
+
<span>Sub-500ms Chromium Engine • Tailwind v3 & v4 JIT</span>
|
|
534
|
+
</div>
|
|
535
|
+
|
|
536
|
+
<!-- Avatar & Floating Interactive Mascot Unit -->
|
|
537
|
+
<div class="relative max-w-xl mx-auto mb-8 flex items-center justify-center">
|
|
538
|
+
|
|
539
|
+
<!-- Left Floating Card (PDF Result) -->
|
|
540
|
+
<div class="hidden sm:flex items-center space-x-3 px-3.5 py-2.5 rounded-2xl bg-slate-900/90 border border-slate-700/70 backdrop-blur-xl shadow-xl shadow-black/40 animate-float-left absolute -left-8 lg:-left-16 top-4 z-20">
|
|
541
|
+
<div class="w-8 h-8 rounded-lg bg-indigo-500/20 text-indigo-400 flex items-center justify-center font-bold text-sm border border-indigo-500/30">
|
|
542
|
+
📄
|
|
543
|
+
</div>
|
|
544
|
+
<div class="text-left">
|
|
545
|
+
<div class="text-xs font-bold text-white flex items-center space-x-1.5">
|
|
546
|
+
<span>invoice.pdf</span>
|
|
547
|
+
<span class="text-[9px] px-1.5 py-0.5 rounded-full bg-emerald-500/20 text-emerald-400 font-mono leading-none">382ms</span>
|
|
548
|
+
</div>
|
|
549
|
+
<div class="text-[10px] text-slate-400">100% Vector • Tailwind styled</div>
|
|
550
|
+
</div>
|
|
551
|
+
</div>
|
|
552
|
+
|
|
553
|
+
<!-- Center: Interactive Avatar Character -->
|
|
554
|
+
<div class="relative flex flex-col items-center cursor-pointer group" onclick="cycleAvatarTip()">
|
|
555
|
+
<!-- Speech Bubble on hover / dynamic tips -->
|
|
556
|
+
<div id="speech-bubble" class="mb-3 px-3.5 py-1.5 rounded-xl bg-slate-900/90 border border-indigo-500/40 text-xs font-medium text-indigo-200 shadow-xl backdrop-blur-md flex items-center space-x-2 transition-all duration-300 group-hover:border-indigo-400 group-hover:scale-105">
|
|
557
|
+
<span class="w-2 h-2 rounded-full bg-emerald-400 animate-ping"></span>
|
|
558
|
+
<span id="speech-text" class="transition-opacity duration-150">Hey dev! Click me for quick tips ⚡</span>
|
|
559
|
+
</div>
|
|
560
|
+
|
|
561
|
+
<!-- Avatar Illustration -->
|
|
562
|
+
<div class="animate-avatar relative">
|
|
563
|
+
<div class="absolute -inset-3 bg-gradient-to-r from-indigo-500/30 to-cyan-500/30 rounded-full blur-xl pointer-events-none group-hover:opacity-100 opacity-60 transition duration-300"></div>
|
|
564
|
+
<svg class="w-28 h-28 sm:w-32 sm:h-32 filter drop-shadow-[0_10px_25px_rgba(99,102,241,0.35)] cursor-pointer select-none transition-transform duration-300 group-hover:scale-105" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg" id="hero-avatar">
|
|
565
|
+
<defs>
|
|
566
|
+
<linearGradient id="botBody" x1="20" y1="20" x2="100" y2="100" gradientUnits="userSpaceOnUse">
|
|
567
|
+
<stop offset="0%" stop-color="#1e1b4b"/>
|
|
568
|
+
<stop offset="50%" stop-color="#0f172a"/>
|
|
569
|
+
<stop offset="100%" stop-color="#020617"/>
|
|
570
|
+
</linearGradient>
|
|
571
|
+
<linearGradient id="antennaBeam" x1="60" y1="12" x2="60" y2="28" gradientUnits="userSpaceOnUse">
|
|
572
|
+
<stop offset="0%" stop-color="#38bdf8"/>
|
|
573
|
+
<stop offset="100%" stop-color="#6366f1"/>
|
|
574
|
+
</linearGradient>
|
|
575
|
+
<filter id="glowEyes" x="-20%" y="-20%" width="140%" height="140%">
|
|
576
|
+
<feDropShadow dx="0" dy="0" stdDeviation="3" flood-color="#38bdf8" flood-opacity="0.9"/>
|
|
577
|
+
</filter>
|
|
578
|
+
</defs>
|
|
579
|
+
|
|
580
|
+
<!-- Antenna with pinging radar -->
|
|
581
|
+
<line x1="60" y1="14" x2="60" y2="28" stroke="url(#antennaBeam)" stroke-width="3" stroke-linecap="round"/>
|
|
582
|
+
<circle cx="60" cy="12" r="5" fill="#38bdf8" class="animate-ping origin-center" opacity="0.75"/>
|
|
583
|
+
<circle cx="60" cy="12" r="4.5" fill="#38bdf8"/>
|
|
584
|
+
<circle cx="60" cy="12" r="2" fill="#ffffff"/>
|
|
585
|
+
|
|
586
|
+
<!-- Head Base with Metallic Bevel -->
|
|
587
|
+
<rect x="22" y="26" width="76" height="66" rx="24" fill="url(#botBody)" stroke="#334155" stroke-width="2"/>
|
|
588
|
+
<rect x="23" y="27" width="74" height="64" rx="23" stroke="#6366f1" stroke-width="1.2" stroke-opacity="0.4"/>
|
|
589
|
+
|
|
590
|
+
<!-- Cute Audio Ports / Ears -->
|
|
591
|
+
<rect x="14" y="46" width="8" height="26" rx="4" fill="#334155" stroke="#475569" stroke-width="1.5"/>
|
|
592
|
+
<circle cx="18" cy="59" r="2" fill="#38bdf8"/>
|
|
593
|
+
<rect x="98" y="46" width="8" height="26" rx="4" fill="#334155" stroke="#475569" stroke-width="1.5"/>
|
|
594
|
+
<circle cx="102" cy="59" r="2" fill="#c084fc"/>
|
|
595
|
+
|
|
596
|
+
<!-- Visor Screen -->
|
|
597
|
+
<rect x="28" y="38" width="64" height="42" rx="15" fill="#030712" stroke="#1e293b" stroke-width="1.5"/>
|
|
598
|
+
<path d="M30 46 C45 42, 75 42, 90 46" stroke="rgba(255,255,255,0.15)" stroke-width="1.5" stroke-linecap="round"/>
|
|
599
|
+
|
|
600
|
+
<!-- Glowing Cyan Eyes (Blinks with CSS animation) -->
|
|
601
|
+
<g filter="url(#glowEyes)">
|
|
602
|
+
<ellipse cx="46" cy="58" rx="6" ry="8" fill="#38bdf8" class="avatar-eye-left"/>
|
|
603
|
+
<circle cx="48" cy="56" r="2.2" fill="#ffffff"/>
|
|
604
|
+
<ellipse cx="74" cy="58" rx="6" ry="8" fill="#38bdf8" class="avatar-eye-right"/>
|
|
605
|
+
<circle cx="76" cy="56" r="2.2" fill="#ffffff"/>
|
|
606
|
+
</g>
|
|
607
|
+
|
|
608
|
+
<!-- Friendly Smile -->
|
|
609
|
+
<path d="M52 70 Q60 74 68 70" stroke="#818cf8" stroke-width="2" stroke-linecap="round"/>
|
|
610
|
+
|
|
611
|
+
<!-- Cheeks Blush -->
|
|
612
|
+
<circle cx="36" cy="65" r="3" fill="#f43f5e" opacity="0.4"/>
|
|
613
|
+
<circle cx="84" cy="65" r="3" fill="#f43f5e" opacity="0.4"/>
|
|
614
|
+
|
|
615
|
+
<!-- Floating Paper / PDF sheet being held/printed -->
|
|
616
|
+
<rect x="42" y="86" width="36" height="24" rx="4" fill="#1e293b" stroke="#38bdf8" stroke-width="1.5"/>
|
|
617
|
+
<line x1="48" y1="92" x2="72" y2="92" stroke="#94a3b8" stroke-width="1.5" stroke-linecap="round"/>
|
|
618
|
+
<line x1="48" y1="97" x2="68" y2="97" stroke="#94a3b8" stroke-width="1.5" stroke-linecap="round"/>
|
|
619
|
+
<line x1="48" y1="102" x2="62" y2="102" stroke="#38bdf8" stroke-width="1.5" stroke-linecap="round"/>
|
|
620
|
+
<circle cx="71" cy="102" r="1.5" fill="#f43f5e"/>
|
|
621
|
+
</svg>
|
|
622
|
+
</div>
|
|
623
|
+
</div>
|
|
624
|
+
|
|
625
|
+
<!-- Right Floating Card (MCP & AI Agent) -->
|
|
626
|
+
<div class="hidden sm:flex items-center space-x-3 px-3.5 py-2.5 rounded-2xl bg-slate-900/90 border border-slate-700/70 backdrop-blur-xl shadow-xl shadow-black/40 animate-float-right absolute -right-8 lg:-right-16 top-6 z-20">
|
|
627
|
+
<div class="w-8 h-8 rounded-lg bg-purple-500/20 text-purple-400 flex items-center justify-center font-bold text-sm border border-purple-500/30">
|
|
628
|
+
🤖
|
|
629
|
+
</div>
|
|
630
|
+
<div class="text-left">
|
|
631
|
+
<div class="text-xs font-bold text-white flex items-center space-x-1.5">
|
|
632
|
+
<span>MCP Protocol</span>
|
|
633
|
+
<span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span>
|
|
634
|
+
</div>
|
|
635
|
+
<div class="text-[10px] text-slate-400">Claude Desktop & Cursor</div>
|
|
636
|
+
</div>
|
|
637
|
+
</div>
|
|
638
|
+
|
|
639
|
+
</div>
|
|
640
|
+
|
|
641
|
+
<h1 class="text-5xl sm:text-6xl font-black tracking-tight text-white leading-tight">
|
|
642
|
+
Send <span class="bg-gradient-to-r from-indigo-400 via-sky-400 to-cyan-300 bg-clip-text text-transparent">Tailwind HTML</span>.<br>
|
|
643
|
+
Receive Pixel-Perfect <span class="bg-gradient-to-r from-emerald-400 via-teal-300 to-cyan-400 bg-clip-text text-transparent">PDFs & OG Images</span>.
|
|
644
|
+
</h1>
|
|
645
|
+
|
|
646
|
+
<p class="mt-6 text-lg text-slate-400 max-w-2xl mx-auto leading-relaxed">
|
|
647
|
+
Eliminate complex browser infrastructure and build steps. Submit raw HTML with arbitrary utility classes and generate print-ready PDFs or cached social preview banners in under 500ms.
|
|
648
|
+
</p>
|
|
649
|
+
|
|
650
|
+
<div class="mt-8 flex flex-col sm:flex-row justify-center items-center gap-3.5">
|
|
651
|
+
<button onclick="startCheckout('free')" class="w-full sm:w-auto px-7 py-3.5 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-bold transition shadow-lg shadow-emerald-600/25 flex items-center justify-center space-x-2.5 group">
|
|
652
|
+
<span>⚡ Claim 100 Free Renders</span>
|
|
653
|
+
<span class="text-[10px] px-2 py-0.5 rounded-full bg-emerald-950/60 border border-emerald-400/30 text-emerald-200 font-bold uppercase tracking-wider">No CC</span>
|
|
654
|
+
</button>
|
|
655
|
+
<a href="#playground" class="w-full sm:w-auto px-6 py-3.5 rounded-xl bg-slate-900 hover:bg-slate-800 border border-slate-700/80 hover:border-slate-600 text-slate-200 font-semibold transition flex items-center justify-center space-x-2">
|
|
656
|
+
<span>Try Live Sandbox</span>
|
|
657
|
+
<span class="text-slate-400">→</span>
|
|
658
|
+
</a>
|
|
659
|
+
</div>
|
|
660
|
+
<p class="mt-3 text-xs text-slate-500">Instant API key • Zero configuration • 100% serverless</p>
|
|
661
|
+
|
|
662
|
+
<!-- System Stats -->
|
|
663
|
+
<div class="mt-14 grid grid-cols-2 sm:grid-cols-4 gap-4 max-w-3xl mx-auto text-left">
|
|
664
|
+
<div class="p-4 rounded-xl bg-slate-900/60 border border-slate-800">
|
|
665
|
+
<p class="text-xs text-slate-400">Response Latency</p>
|
|
666
|
+
<p class="text-2xl font-black text-emerald-400 mt-1">< 500 ms</p>
|
|
667
|
+
</div>
|
|
668
|
+
<div class="p-4 rounded-xl bg-slate-900/60 border border-slate-800">
|
|
669
|
+
<p class="text-xs text-slate-400">Tailwind Engine</p>
|
|
670
|
+
<p class="text-2xl font-black text-indigo-400 mt-1">v3 & v4</p>
|
|
671
|
+
</div>
|
|
672
|
+
<div class="p-4 rounded-xl bg-slate-900/60 border border-slate-800">
|
|
673
|
+
<p class="text-xs text-slate-400">Sandboxing</p>
|
|
674
|
+
<p class="text-2xl font-black text-purple-400 mt-1">SSRF Blocked</p>
|
|
675
|
+
</div>
|
|
676
|
+
<div class="p-4 rounded-xl bg-slate-900/60 border border-slate-800">
|
|
677
|
+
<p class="text-xs text-slate-400">Cache Layer</p>
|
|
678
|
+
<p class="text-2xl font-black text-cyan-400 mt-1">SHA-256</p>
|
|
679
|
+
</div>
|
|
680
|
+
</div>
|
|
681
|
+
</div>
|
|
682
|
+
</section>
|
|
683
|
+
|
|
684
|
+
<!-- Live Interactive PDF Playground Section -->
|
|
685
|
+
<section id="playground" class="py-16 bg-slate-900/40 border-t border-slate-800/80">
|
|
686
|
+
<div class="max-w-7xl mx-auto px-6">
|
|
687
|
+
<div class="text-center max-w-2xl mx-auto mb-10">
|
|
688
|
+
<div class="inline-flex items-center space-x-2 px-3 py-1 rounded-full bg-indigo-950/60 border border-indigo-800/50 text-indigo-400 text-xs font-semibold mb-3">
|
|
689
|
+
<span class="w-2 h-2 rounded-full bg-indigo-400 animate-pulse"></span>
|
|
690
|
+
<span>Interactive Browser Sandbox</span>
|
|
691
|
+
</div>
|
|
692
|
+
<h2 class="text-3xl sm:text-4xl font-extrabold text-white">Live Tailwind HTML to PDF Playground</h2>
|
|
693
|
+
<p class="text-slate-400 mt-2">Edit Tailwind CSS markup in real-time. Render a pixel-perfect, vector PDF in under 500ms using our Chromium engine.</p>
|
|
694
|
+
</div>
|
|
695
|
+
|
|
696
|
+
<!-- Playground Grid -->
|
|
697
|
+
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6 items-stretch">
|
|
698
|
+
|
|
699
|
+
<!-- Left: Code Editor (6 cols) -->
|
|
700
|
+
<div class="lg:col-span-6 flex flex-col rounded-2xl bg-slate-900/90 border border-slate-800 p-5 shadow-2xl">
|
|
701
|
+
<!-- Mode Tabs & Presets Header -->
|
|
702
|
+
<div class="flex flex-col sm:flex-row sm:items-center justify-between pb-3 border-b border-slate-800 mb-3 gap-2">
|
|
703
|
+
<!-- Mode Switcher -->
|
|
704
|
+
<div class="flex items-center space-x-1.5 bg-slate-950 p-1 rounded-xl border border-slate-800 text-xs">
|
|
705
|
+
<button onclick="setPlaygroundMode('editor')" id="mode-tab-editor" class="px-2.5 py-1 rounded-lg font-bold bg-indigo-600 text-white transition">HTML Editor</button>
|
|
706
|
+
<button onclick="setPlaygroundMode('url')" id="mode-tab-url" class="px-2.5 py-1 rounded-lg font-semibold text-slate-400 hover:text-white transition">URL to PDF</button>
|
|
707
|
+
<button onclick="setPlaygroundMode('snippets')" id="mode-tab-snippets" class="px-2.5 py-1 rounded-lg font-semibold text-slate-400 hover:text-white transition">SDK Code</button>
|
|
708
|
+
</div>
|
|
709
|
+
|
|
710
|
+
<!-- Preset switcher (Editor mode only) -->
|
|
711
|
+
<div id="playground-presets-row" class="flex flex-wrap gap-1">
|
|
712
|
+
<button onclick="loadPlaygroundPreset('invoice')" id="btn-pre-invoice" class="px-2 py-1 rounded-lg text-[11px] font-semibold bg-indigo-600 text-white">Invoice</button>
|
|
713
|
+
<button onclick="loadPlaygroundPreset('ticket')" id="btn-pre-ticket" class="px-2 py-1 rounded-lg text-[11px] font-semibold bg-slate-800 text-slate-400 hover:text-white">Ticket</button>
|
|
714
|
+
<button onclick="loadPlaygroundPreset('receipt')" id="btn-pre-receipt" class="px-2 py-1 rounded-lg text-[11px] font-semibold bg-slate-800 text-slate-400 hover:text-white">Receipt</button>
|
|
715
|
+
<button onclick="loadPlaygroundPreset('certificate')" id="btn-pre-certificate" class="px-2 py-1 rounded-lg text-[11px] font-semibold bg-slate-800 text-slate-400 hover:text-white">Certificate</button>
|
|
716
|
+
<button onclick="loadPlaygroundPreset('badge')" id="btn-pre-badge" class="px-2 py-1 rounded-lg text-[11px] font-semibold bg-slate-800 text-slate-400 hover:text-white">ID Badge</button>
|
|
717
|
+
</div>
|
|
718
|
+
</div>
|
|
719
|
+
|
|
720
|
+
<!-- Mode 1: HTML Textarea -->
|
|
721
|
+
<div id="container-mode-editor" class="flex-1 flex flex-col">
|
|
722
|
+
<textarea
|
|
723
|
+
id="playground-editor"
|
|
724
|
+
rows="16"
|
|
725
|
+
class="flex-1 w-full bg-slate-950 font-mono text-xs text-indigo-200 p-4 rounded-xl border border-slate-800/80 focus:outline-none focus:border-indigo-500 leading-relaxed resize-none shadow-inner"
|
|
726
|
+
spellcheck="false"
|
|
727
|
+
></textarea>
|
|
728
|
+
</div>
|
|
729
|
+
|
|
730
|
+
<!-- Mode 2: Direct URL Input -->
|
|
731
|
+
<div id="container-mode-url" class="hidden flex-1 flex flex-col justify-center space-y-4 py-8 px-2">
|
|
732
|
+
<div class="space-y-2">
|
|
733
|
+
<label class="block text-xs font-bold text-slate-300">Target Web URL to Render</label>
|
|
734
|
+
<div class="flex items-center space-x-2">
|
|
735
|
+
<input
|
|
736
|
+
id="playground-url-input"
|
|
737
|
+
type="url"
|
|
738
|
+
value="https://example.com"
|
|
739
|
+
placeholder="https://yourdomain.com/dashboard/invoice"
|
|
740
|
+
class="flex-1 bg-slate-950 font-mono text-xs text-indigo-200 p-3.5 rounded-xl border border-slate-800 focus:outline-none focus:border-indigo-500 shadow-inner"
|
|
741
|
+
/>
|
|
742
|
+
</div>
|
|
743
|
+
<p class="text-[11px] text-slate-400 leading-relaxed">
|
|
744
|
+
Chromium navigates to the target URL, executes JavaScript, waits for custom fonts and networkidle, and captures a pixel-perfect vector PDF.
|
|
745
|
+
</p>
|
|
746
|
+
</div>
|
|
747
|
+
|
|
748
|
+
<div class="p-3.5 rounded-xl bg-slate-950/70 border border-slate-800 text-[11px] text-slate-400 space-y-1.5">
|
|
749
|
+
<div class="flex items-center space-x-1.5 text-emerald-400 font-semibold">
|
|
750
|
+
<span>🛡️ Enterprise SSRF Protected:</span>
|
|
751
|
+
</div>
|
|
752
|
+
<p>Loopback, internal IP ranges (127.0.0.1, 10.0.0.0/8, 192.168.0.0/16) and cloud instance metadata (169.254.169.254) are automatically blocked.</p>
|
|
753
|
+
</div>
|
|
754
|
+
</div>
|
|
755
|
+
|
|
756
|
+
<!-- Mode 3: Multi-Language Code Snippets -->
|
|
757
|
+
<div id="container-mode-snippets" class="hidden flex-1 flex flex-col space-y-3">
|
|
758
|
+
<div class="flex items-center justify-between">
|
|
759
|
+
<div class="flex space-x-1.5 text-xs">
|
|
760
|
+
<button onclick="setSnippetLang('curl')" id="btn-lang-curl" class="px-2.5 py-1 rounded-lg font-bold bg-indigo-600 text-white">cURL</button>
|
|
761
|
+
<button onclick="setSnippetLang('node')" id="btn-lang-node" class="px-2.5 py-1 rounded-lg font-semibold bg-slate-800 text-slate-400 hover:text-white">Node / TS</button>
|
|
762
|
+
<button onclick="setSnippetLang('python')" id="btn-lang-python" class="px-2.5 py-1 rounded-lg font-semibold bg-slate-800 text-slate-400 hover:text-white">Python</button>
|
|
763
|
+
<button onclick="setSnippetLang('php')" id="btn-lang-php" class="px-2.5 py-1 rounded-lg font-semibold bg-slate-800 text-slate-400 hover:text-white">PHP</button>
|
|
764
|
+
</div>
|
|
765
|
+
<button onclick="copyCurrentSnippet()" id="btn-copy-snippet" class="px-3 py-1 rounded-lg text-xs font-bold bg-slate-800 hover:bg-slate-700 text-indigo-300 transition flex items-center space-x-1">
|
|
766
|
+
<span>📋 Copy Snippet</span>
|
|
767
|
+
</button>
|
|
768
|
+
</div>
|
|
769
|
+
<pre id="playground-snippet-code" class="flex-1 bg-slate-950 p-4 rounded-xl border border-slate-800 font-mono text-[11px] text-indigo-200 overflow-x-auto whitespace-pre leading-relaxed max-h-72"></pre>
|
|
770
|
+
</div>
|
|
771
|
+
|
|
772
|
+
<div class="mt-4 flex flex-wrap items-center justify-between gap-3 pt-3 border-t border-slate-800">
|
|
773
|
+
<div class="flex flex-wrap items-center gap-3 text-xs text-slate-400">
|
|
774
|
+
<div class="flex items-center space-x-1.5">
|
|
775
|
+
<span>Paper:</span>
|
|
776
|
+
<select id="playground-format" class="bg-slate-950 text-slate-200 border border-slate-800 rounded-lg px-2 py-1 text-xs focus:outline-none">
|
|
777
|
+
<option value="A4">A4 Portrait</option>
|
|
778
|
+
<option value="Letter">Letter Portrait</option>
|
|
779
|
+
<option value="A4-landscape">A4 Landscape</option>
|
|
780
|
+
</select>
|
|
781
|
+
</div>
|
|
782
|
+
|
|
783
|
+
<!-- Header / Footer Toggle -->
|
|
784
|
+
<label class="flex items-center space-x-1 cursor-pointer text-slate-300 hover:text-white select-none">
|
|
785
|
+
<input id="playground-header-footer" type="checkbox" class="rounded bg-slate-950 border-slate-700 text-indigo-600 focus:ring-0 text-xs">
|
|
786
|
+
<span class="text-[11px]">Header & Page #</span>
|
|
787
|
+
</label>
|
|
788
|
+
|
|
789
|
+
<!-- Password Protection Option -->
|
|
790
|
+
<div class="flex items-center space-x-1">
|
|
791
|
+
<span class="text-[11px] text-slate-400">🔒</span>
|
|
792
|
+
<input
|
|
793
|
+
id="playground-password"
|
|
794
|
+
type="text"
|
|
795
|
+
placeholder="Password (AES-256)"
|
|
796
|
+
class="bg-slate-950 text-slate-200 border border-slate-800 rounded-lg px-2 py-1 text-[11px] focus:outline-none focus:border-indigo-500 w-32 placeholder:text-slate-600"
|
|
797
|
+
/>
|
|
798
|
+
</div>
|
|
799
|
+
</div>
|
|
800
|
+
|
|
801
|
+
<button
|
|
802
|
+
id="playground-render-btn"
|
|
803
|
+
onclick="renderPlaygroundPdf()"
|
|
804
|
+
class="px-5 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-bold text-xs transition shadow-lg shadow-indigo-600/30 flex items-center space-x-2"
|
|
805
|
+
>
|
|
806
|
+
<span>⚡ Render PDF</span>
|
|
807
|
+
</button>
|
|
808
|
+
</div>
|
|
809
|
+
</div>
|
|
810
|
+
|
|
811
|
+
<!-- Right: Live PDF Preview / Viewer (6 cols) -->
|
|
812
|
+
<div class="lg:col-span-6 flex flex-col rounded-2xl bg-slate-900/90 border border-slate-800 p-5 shadow-2xl">
|
|
813
|
+
<div class="flex items-center justify-between pb-3 border-b border-slate-800 mb-3">
|
|
814
|
+
<div class="flex items-center space-x-2">
|
|
815
|
+
<span class="text-xs font-bold text-slate-300">Live PDF Output</span>
|
|
816
|
+
<span id="playground-status-badge" class="text-[10px] px-2 py-0.5 rounded-full bg-emerald-950 text-emerald-400 border border-emerald-500/30 font-bold">
|
|
817
|
+
Ready
|
|
818
|
+
</span>
|
|
819
|
+
</div>
|
|
820
|
+
<div class="flex items-center space-x-2">
|
|
821
|
+
<span id="playground-time" class="text-[11px] font-mono text-slate-400">~380ms</span>
|
|
822
|
+
<a
|
|
823
|
+
id="playground-download-btn"
|
|
824
|
+
href="#"
|
|
825
|
+
download="tailprint-rendered.pdf"
|
|
826
|
+
class="hidden px-3 py-1 rounded-lg text-[11px] font-bold bg-slate-800 hover:bg-slate-700 text-white border border-slate-700 transition"
|
|
827
|
+
>
|
|
828
|
+
⬇️ Download PDF
|
|
829
|
+
</a>
|
|
830
|
+
</div>
|
|
831
|
+
</div>
|
|
832
|
+
|
|
833
|
+
<!-- Preview Area -->
|
|
834
|
+
<div class="flex-1 w-full min-h-[380px] bg-slate-950 rounded-xl border border-slate-800/80 overflow-hidden relative flex items-center justify-center">
|
|
835
|
+
|
|
836
|
+
<!-- Loading Indicator -->
|
|
837
|
+
<div id="playground-loading" class="hidden absolute inset-0 bg-slate-950/80 backdrop-blur-sm flex flex-col items-center justify-center space-y-3 z-10">
|
|
838
|
+
<div class="w-8 h-8 rounded-full border-2 border-indigo-500 border-t-transparent animate-spin"></div>
|
|
839
|
+
<p class="text-xs text-indigo-300 font-medium">Compiling Tailwind & Chromium...</p>
|
|
840
|
+
</div>
|
|
841
|
+
|
|
842
|
+
<!-- PDF Viewer Iframe -->
|
|
843
|
+
<iframe
|
|
844
|
+
id="playground-iframe"
|
|
845
|
+
class="w-full h-full min-h-[460px] border-0 rounded-xl"
|
|
846
|
+
title="Tailprint Live PDF Preview"
|
|
847
|
+
></iframe>
|
|
848
|
+
</div>
|
|
849
|
+
</div>
|
|
850
|
+
|
|
851
|
+
</div>
|
|
852
|
+
</div>
|
|
853
|
+
</section>
|
|
854
|
+
|
|
855
|
+
<!-- LLM Scraper Live Demo Section -->
|
|
856
|
+
<section id="scraper" class="py-16 bg-slate-900/60 border-t border-slate-800/80">
|
|
857
|
+
<div class="max-w-6xl mx-auto px-6">
|
|
858
|
+
<div class="text-center max-w-2xl mx-auto mb-10">
|
|
859
|
+
<div class="inline-flex items-center space-x-2 px-3 py-1 rounded-full bg-emerald-950/60 border border-emerald-800/50 text-emerald-400 text-xs font-semibold mb-3">
|
|
860
|
+
<span>NEW: LLM-Ready Web Scraper & Markdown Cleaner</span>
|
|
861
|
+
</div>
|
|
862
|
+
<h2 class="text-3xl font-extrabold text-white">Turn Any Web Page into Clean Markdown</h2>
|
|
863
|
+
<p class="text-slate-400 mt-2">Mozilla Readability strips 95%+ boilerplate noise (scripts, cookie banners, navbars). Drastically cuts LLM token costs for RAG & AI agents.</p>
|
|
864
|
+
</div>
|
|
865
|
+
|
|
866
|
+
<!-- Live Interactive Scraper Box -->
|
|
867
|
+
<div class="bg-slate-950 border border-slate-800 rounded-2xl p-6 shadow-2xl">
|
|
868
|
+
<div class="flex flex-col sm:flex-row gap-3">
|
|
869
|
+
<input id="scraper-input" type="url" value="https://example.com" placeholder="Enter any article or documentation URL..." class="flex-1 bg-slate-900 border border-slate-700/80 rounded-xl px-4 py-3 text-sm text-white focus:outline-none focus:border-indigo-500 font-mono" />
|
|
870
|
+
<button onclick="runLiveScrape()" id="scraper-btn" class="px-6 py-3 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-bold text-sm transition shadow-md shadow-indigo-600/30 flex items-center justify-center space-x-2">
|
|
871
|
+
<span>⚡ Scrape to Markdown</span>
|
|
872
|
+
</button>
|
|
873
|
+
</div>
|
|
874
|
+
|
|
875
|
+
<!-- Metrics & Output -->
|
|
876
|
+
<div id="scraper-results" class="mt-6 border-t border-slate-800/80 pt-6">
|
|
877
|
+
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-4">
|
|
878
|
+
<div class="p-3 bg-slate-900/80 rounded-xl border border-slate-800">
|
|
879
|
+
<span class="text-[10px] uppercase font-bold text-slate-400 block">Token Savings</span>
|
|
880
|
+
<span id="metric-savings" class="text-xl font-black text-emerald-400 mt-0.5 block">94.2%</span>
|
|
881
|
+
</div>
|
|
882
|
+
<div class="p-3 bg-slate-900/80 rounded-xl border border-slate-800">
|
|
883
|
+
<span class="text-[10px] uppercase font-bold text-slate-400 block">Estimated Tokens</span>
|
|
884
|
+
<span id="metric-tokens" class="text-xl font-black text-indigo-400 mt-0.5 block">181</span>
|
|
885
|
+
</div>
|
|
886
|
+
<div class="p-3 bg-slate-900/80 rounded-xl border border-slate-800">
|
|
887
|
+
<span class="text-[10px] uppercase font-bold text-slate-400 block">Word Count</span>
|
|
888
|
+
<span id="metric-words" class="text-xl font-black text-slate-200 mt-0.5 block">136</span>
|
|
889
|
+
</div>
|
|
890
|
+
<div class="p-3 bg-slate-900/80 rounded-xl border border-slate-800">
|
|
891
|
+
<span class="text-[10px] uppercase font-bold text-slate-400 block">Cache Status</span>
|
|
892
|
+
<span id="metric-cache" class="text-xl font-black text-cyan-400 mt-0.5 block">HIT (Redis)</span>
|
|
893
|
+
</div>
|
|
894
|
+
</div>
|
|
895
|
+
|
|
896
|
+
<div class="relative">
|
|
897
|
+
<div class="flex justify-between items-center mb-2">
|
|
898
|
+
<span class="text-xs font-semibold text-slate-400 uppercase tracking-wider font-mono">Cleaned Markdown Output</span>
|
|
899
|
+
<button onclick="copyScrapedMarkdown()" class="text-xs text-indigo-400 hover:text-indigo-300 font-semibold flex items-center space-x-1">
|
|
900
|
+
<span>📋 Copy Clean Markdown</span>
|
|
901
|
+
</button>
|
|
902
|
+
</div>
|
|
903
|
+
<pre id="scraper-output" class="p-4 bg-slate-900 rounded-xl text-slate-300 text-xs font-mono max-h-64 overflow-y-auto whitespace-pre-wrap border border-slate-800"># Example Domain
|
|
904
|
+
|
|
905
|
+
This domain is for use in documentation examples without needing permission. This is not a service; avoid relying on it for testing and monitoring purposes.
|
|
906
|
+
|
|
907
|
+
[Learn more](https://iana.org/help/example-domains)</pre>
|
|
908
|
+
</div>
|
|
909
|
+
</div>
|
|
910
|
+
</div>
|
|
911
|
+
</div>
|
|
912
|
+
</section>
|
|
913
|
+
|
|
914
|
+
<!-- Templates Section -->
|
|
915
|
+
<section id="templates" class="py-20 bg-slate-900/40 border-t border-b border-slate-800/80">
|
|
916
|
+
<div class="max-w-7xl mx-auto px-6">
|
|
917
|
+
<div class="text-center max-w-2xl mx-auto mb-10">
|
|
918
|
+
<div class="inline-flex items-center space-x-2 px-3 py-1 rounded-full bg-indigo-950/60 border border-indigo-800/50 text-indigo-400 text-xs font-semibold mb-3">
|
|
919
|
+
<span>Ready to Copy & Render</span>
|
|
920
|
+
</div>
|
|
921
|
+
<h2 class="text-3xl font-extrabold text-white">8 Production Tailwind Templates</h2>
|
|
922
|
+
<p class="text-slate-400 mt-2">One-click copy for clean Tailwind HTML or cURL terminal commands. Test immediate rendering on any template.</p>
|
|
923
|
+
</div>
|
|
924
|
+
|
|
925
|
+
<!-- Tab Buttons Grid -->
|
|
926
|
+
<div class="flex flex-wrap justify-center mb-8 gap-2.5 max-w-5xl mx-auto">
|
|
927
|
+
${TEMPLATES.map((t, index) => `
|
|
928
|
+
<button onclick="switchTab('${t.id}')" id="btn-${t.id}" class="px-4 py-2 rounded-xl font-bold text-xs ${index === 0
|
|
929
|
+
? 'bg-indigo-600 text-white shadow-md shadow-indigo-600/30'
|
|
930
|
+
: 'bg-slate-900/90 border border-slate-800 text-slate-400 hover:text-white'} transition flex items-center space-x-2">
|
|
931
|
+
<span>${t.id === 'invoice'
|
|
932
|
+
? '🧾'
|
|
933
|
+
: t.id === 'certificate'
|
|
934
|
+
? '🎓'
|
|
935
|
+
: t.id === 'report'
|
|
936
|
+
? '📊'
|
|
937
|
+
: t.id === 'resume'
|
|
938
|
+
? '👤'
|
|
939
|
+
: t.id === 'ticket'
|
|
940
|
+
? '🎟️'
|
|
941
|
+
: t.id === 'receipt'
|
|
942
|
+
? '🏷️'
|
|
943
|
+
: t.id === 'og-card'
|
|
944
|
+
? '🖼️'
|
|
945
|
+
: '📦'}</span>
|
|
946
|
+
<span>${t.title}</span>
|
|
947
|
+
</button>
|
|
948
|
+
`).join('')}
|
|
949
|
+
</div>
|
|
950
|
+
|
|
951
|
+
<!-- Template Panels -->
|
|
952
|
+
${TEMPLATES.map((t, index) => `
|
|
953
|
+
<div id="tab-${t.id}" class="template-panel ${index === 0 ? '' : 'hidden'} grid grid-cols-1 lg:grid-cols-12 gap-8 items-start">
|
|
954
|
+
<div class="lg:col-span-7 bg-white text-slate-900 rounded-2xl p-6 shadow-2xl border border-slate-700/50 max-h-[640px] overflow-y-auto">
|
|
955
|
+
${t.html}
|
|
956
|
+
</div>
|
|
957
|
+
<div class="lg:col-span-5 space-y-4">
|
|
958
|
+
<div class="p-6 rounded-2xl bg-slate-900 border border-slate-800">
|
|
959
|
+
<div class="flex justify-between items-start">
|
|
960
|
+
<span class="text-[10px] font-bold uppercase tracking-wider text-indigo-400 bg-indigo-950/80 px-2.5 py-1 rounded-md border border-indigo-800/40">${t.category}</span>
|
|
961
|
+
<span class="text-xs text-slate-400 font-mono">${t.format} ${t.landscape ? 'Landscape' : 'Portrait'}</span>
|
|
962
|
+
</div>
|
|
963
|
+
<h3 class="text-xl font-bold text-white mt-3">${t.title}</h3>
|
|
964
|
+
<p class="text-slate-400 text-xs mt-1.5 leading-relaxed">${t.description}</p>
|
|
965
|
+
|
|
966
|
+
<div class="mt-6 flex flex-wrap gap-2.5">
|
|
967
|
+
<button onclick="copyHtml('${t.id}')" class="px-3.5 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-white font-semibold text-xs transition flex items-center space-x-1.5 shadow-md shadow-indigo-600/30">
|
|
968
|
+
<span>📋 Copy HTML</span>
|
|
969
|
+
</button>
|
|
970
|
+
<button onclick="copyCurl('${t.id}')" class="px-3.5 py-2 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-200 font-semibold text-xs transition flex items-center space-x-1.5">
|
|
971
|
+
<span>💻 Copy cURL</span>
|
|
972
|
+
</button>
|
|
973
|
+
<a href="/templates/${t.id}/download" class="px-3.5 py-2 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-xs transition flex items-center space-x-1.5 shadow-md shadow-emerald-600/30">
|
|
974
|
+
<span>⬇️ Download ${t.id === 'og-card' ? 'PNG' : 'PDF'}</span>
|
|
975
|
+
</a>
|
|
976
|
+
</div>
|
|
977
|
+
</div>
|
|
978
|
+
|
|
979
|
+
<div class="p-4 rounded-xl bg-slate-900/80 border border-slate-800/80 text-[11px]">
|
|
980
|
+
<p class="font-bold text-slate-300 uppercase tracking-wider mb-1.5">cURL API Request</p>
|
|
981
|
+
<pre class="bg-slate-950 p-3 rounded-lg text-slate-300 overflow-x-auto leading-relaxed border border-slate-800 font-mono text-[10px]">curl -X POST ${env.APP_URL}${t.id === 'og-card' ? '/v1/render/image' : '/v1/render/pdf'} \\
|
|
982
|
+
-H "Authorization: Bearer sec_live_..." \\
|
|
983
|
+
-H "Content-Type: application/json" \\
|
|
984
|
+
-d '{"html":"..."}' \\
|
|
985
|
+
--output ${t.id}.${t.id === 'og-card' ? 'png' : 'pdf'}</pre>
|
|
986
|
+
</div>
|
|
987
|
+
</div>
|
|
988
|
+
</div>
|
|
989
|
+
`).join('')}
|
|
990
|
+
|
|
991
|
+
</div>
|
|
992
|
+
</section>
|
|
993
|
+
|
|
994
|
+
<!-- Model Context Protocol (MCP) Section -->
|
|
995
|
+
<section id="mcp" class="py-24 border-b border-slate-800/80 bg-gradient-to-b from-slate-950 via-indigo-950/20 to-slate-950">
|
|
996
|
+
<div class="max-w-7xl mx-auto px-6 space-y-16">
|
|
997
|
+
|
|
998
|
+
<!-- Section Header -->
|
|
999
|
+
<div class="text-center max-w-3xl mx-auto">
|
|
1000
|
+
<div class="inline-flex items-center space-x-2 px-3 py-1 rounded-full bg-purple-950/60 border border-purple-800/50 text-purple-400 text-xs font-semibold mb-4">
|
|
1001
|
+
<span class="w-2 h-2 rounded-full bg-purple-400 animate-pulse"></span>
|
|
1002
|
+
<span>Official Model Context Protocol (MCP) Server</span>
|
|
1003
|
+
</div>
|
|
1004
|
+
<h2 class="text-3xl sm:text-4xl font-extrabold text-white tracking-tight">
|
|
1005
|
+
Give Your AI Assistants Hands to <span class="bg-gradient-to-r from-purple-400 to-indigo-400 bg-clip-text text-transparent">Scrape & Print</span>
|
|
1006
|
+
</h2>
|
|
1007
|
+
<p class="text-slate-400 mt-3 text-sm sm:text-base leading-relaxed">
|
|
1008
|
+
Connect Tailprint to Claude Desktop, Cursor, Windsurf, or autonomous LLM agents in 30 seconds. Turn conversational prompts into pixel-perfect PDF briefs, dynamic invoices, and inline preview images.
|
|
1009
|
+
</p>
|
|
1010
|
+
</div>
|
|
1011
|
+
|
|
1012
|
+
<!-- 4 Concrete Real-World Use Cases & Business Value -->
|
|
1013
|
+
<div>
|
|
1014
|
+
<div class="flex items-center justify-between mb-6">
|
|
1015
|
+
<div>
|
|
1016
|
+
<h3 class="text-xl font-bold text-white">Concrete Real-World Use Cases</h3>
|
|
1017
|
+
<p class="text-xs text-slate-400 mt-1">What you can ask your AI assistant to build right now with Tailprint MCP.</p>
|
|
1018
|
+
</div>
|
|
1019
|
+
<span class="hidden sm:inline-block text-xs font-semibold text-purple-400 px-3 py-1 rounded-lg bg-purple-950/60 border border-purple-800/40">
|
|
1020
|
+
Tools • Resources • Prompts
|
|
1021
|
+
</span>
|
|
1022
|
+
</div>
|
|
1023
|
+
|
|
1024
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
1025
|
+
|
|
1026
|
+
<!-- Use Case 1: Executive Briefs from URL -->
|
|
1027
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 flex flex-col justify-between hover:border-indigo-500/50 transition shadow-lg group">
|
|
1028
|
+
<div class="space-y-4">
|
|
1029
|
+
<div class="flex items-center justify-between">
|
|
1030
|
+
<div class="flex items-center space-x-2.5">
|
|
1031
|
+
<span class="p-2 rounded-xl bg-indigo-500/10 text-indigo-400 text-lg">📑</span>
|
|
1032
|
+
<h4 class="text-sm font-bold text-white group-hover:text-indigo-400 transition">1. Web Article to 1-Page Executive PDF</h4>
|
|
1033
|
+
</div>
|
|
1034
|
+
<span class="text-[10px] font-bold px-2 py-0.5 rounded bg-indigo-950/80 text-indigo-300 border border-indigo-800/40">scrape + render</span>
|
|
1035
|
+
</div>
|
|
1036
|
+
|
|
1037
|
+
<!-- Prompt Snippet -->
|
|
1038
|
+
<div class="p-3.5 rounded-xl bg-slate-950/90 border border-slate-800/90 font-mono text-xs text-slate-300">
|
|
1039
|
+
<span class="text-indigo-400 font-bold">You to Claude:</span> "Read this 15-page article at <span class="text-slate-500">https://example.com/report</span>, extract the key data, and render a 1-page executive summary PDF with benchmark bars."
|
|
1040
|
+
</div>
|
|
1041
|
+
|
|
1042
|
+
<!-- How Tailprint Works & Business Value -->
|
|
1043
|
+
<div class="space-y-2 text-xs text-slate-400">
|
|
1044
|
+
<p class="flex items-start space-x-2">
|
|
1045
|
+
<span class="text-emerald-400 font-bold">✓</span>
|
|
1046
|
+
<span><strong class="text-slate-200">What happens:</strong> Tailprint strips cookie walls & ads (saving 85% LLM tokens), structures key findings, and compiles a vector PDF.</span>
|
|
1047
|
+
</p>
|
|
1048
|
+
<p class="flex items-start space-x-2">
|
|
1049
|
+
<span class="text-purple-400 font-bold">💎</span>
|
|
1050
|
+
<span><strong class="text-purple-300">Business Value:</strong> Saves 2+ hours of manual report formatting and copy-pasting into Word/Canva.</span>
|
|
1051
|
+
</p>
|
|
1052
|
+
</div>
|
|
1053
|
+
</div>
|
|
1054
|
+
</div>
|
|
1055
|
+
|
|
1056
|
+
<!-- Use Case 2: On-Demand Invoicing & Billing -->
|
|
1057
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 flex flex-col justify-between hover:border-emerald-500/50 transition shadow-lg group">
|
|
1058
|
+
<div class="space-y-4">
|
|
1059
|
+
<div class="flex items-center justify-between">
|
|
1060
|
+
<div class="flex items-center space-x-2.5">
|
|
1061
|
+
<span class="p-2 rounded-xl bg-emerald-500/10 text-emerald-400 text-lg">🧾</span>
|
|
1062
|
+
<h4 class="text-sm font-bold text-white group-hover:text-emerald-400 transition">2. Instant Client Invoicing & Billing</h4>
|
|
1063
|
+
</div>
|
|
1064
|
+
<span class="text-[10px] font-bold px-2 py-0.5 rounded bg-emerald-950/80 text-emerald-300 border border-emerald-800/40">render_template</span>
|
|
1065
|
+
</div>
|
|
1066
|
+
|
|
1067
|
+
<!-- Prompt Snippet -->
|
|
1068
|
+
<div class="p-3.5 rounded-xl bg-slate-950/90 border border-slate-800/90 font-mono text-xs text-slate-300">
|
|
1069
|
+
<span class="text-emerald-400 font-bold">You to Claude:</span> "Create a PDF invoice for Acme Corp: 15h engineering at $150/hr, invoice #INV-2026-042, due in 14 days."
|
|
1070
|
+
</div>
|
|
1071
|
+
|
|
1072
|
+
<!-- How Tailprint Works & Business Value -->
|
|
1073
|
+
<div class="space-y-2 text-xs text-slate-400">
|
|
1074
|
+
<p class="flex items-start space-x-2">
|
|
1075
|
+
<span class="text-emerald-400 font-bold">✓</span>
|
|
1076
|
+
<span><strong class="text-slate-200">What happens:</strong> Calls <code class="text-slate-300">render_template('invoice', variables)</code>. The engine injects line items into our battle-tested Tailwind layout.</span>
|
|
1077
|
+
</p>
|
|
1078
|
+
<p class="flex items-start space-x-2">
|
|
1079
|
+
<span class="text-purple-400 font-bold">💎</span>
|
|
1080
|
+
<span><strong class="text-purple-300">Business Value:</strong> Zero CSS hallucinations. LLMs don't break page margins or table alignment, yielding instant, clean invoices.</span>
|
|
1081
|
+
</p>
|
|
1082
|
+
</div>
|
|
1083
|
+
</div>
|
|
1084
|
+
</div>
|
|
1085
|
+
|
|
1086
|
+
<!-- Use Case 3: Inline Social Preview Cards -->
|
|
1087
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 flex flex-col justify-between hover:border-cyan-500/50 transition shadow-lg group">
|
|
1088
|
+
<div class="space-y-4">
|
|
1089
|
+
<div class="flex items-center justify-between">
|
|
1090
|
+
<div class="flex items-center space-x-2.5">
|
|
1091
|
+
<span class="p-2 rounded-xl bg-cyan-500/10 text-cyan-400 text-lg">🖼️</span>
|
|
1092
|
+
<h4 class="text-sm font-bold text-white group-hover:text-cyan-400 transition">3. Inline Social Share Images (Open Graph)</h4>
|
|
1093
|
+
</div>
|
|
1094
|
+
<span class="text-[10px] font-bold px-2 py-0.5 rounded bg-cyan-950/80 text-cyan-300 border border-cyan-800/40">inline image block</span>
|
|
1095
|
+
</div>
|
|
1096
|
+
|
|
1097
|
+
<!-- Prompt Snippet -->
|
|
1098
|
+
<div class="p-3.5 rounded-xl bg-slate-950/90 border border-slate-800/90 font-mono text-xs text-slate-300">
|
|
1099
|
+
<span class="text-cyan-400 font-bold">You to Cursor:</span> "Generate a modern dark 1200x630 Open Graph banner for my blog post 'Shipping Fast with Autonomous Agents'."
|
|
1100
|
+
</div>
|
|
1101
|
+
|
|
1102
|
+
<!-- How Tailprint Works & Business Value -->
|
|
1103
|
+
<div class="space-y-2 text-xs text-slate-400">
|
|
1104
|
+
<p class="flex items-start space-x-2">
|
|
1105
|
+
<span class="text-emerald-400 font-bold">✓</span>
|
|
1106
|
+
<span><strong class="text-slate-200">What happens:</strong> Renders HTML with Tailwind CSS and returns a native MCP <code class="text-slate-300">image</code> object.</span>
|
|
1107
|
+
</p>
|
|
1108
|
+
<p class="flex items-start space-x-2">
|
|
1109
|
+
<span class="text-purple-400 font-bold">💎</span>
|
|
1110
|
+
<span><strong class="text-purple-300">Business Value:</strong> Displays directly inside your AI chat window for visual verification before publishing to production.</span>
|
|
1111
|
+
</p>
|
|
1112
|
+
</div>
|
|
1113
|
+
</div>
|
|
1114
|
+
</div>
|
|
1115
|
+
|
|
1116
|
+
<!-- Use Case 4: Backoffice Automation -->
|
|
1117
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 flex flex-col justify-between hover:border-amber-500/50 transition shadow-lg group">
|
|
1118
|
+
<div class="space-y-4">
|
|
1119
|
+
<div class="flex items-center justify-between">
|
|
1120
|
+
<div class="flex items-center space-x-2.5">
|
|
1121
|
+
<span class="p-2 rounded-xl bg-amber-500/10 text-amber-400 text-lg">🎟️</span>
|
|
1122
|
+
<h4 class="text-sm font-bold text-white group-hover:text-amber-400 transition">4. Fulfillment: Tickets, Receipts & Shipping Labels</h4>
|
|
1123
|
+
</div>
|
|
1124
|
+
<span class="text-[10px] font-bold px-2 py-0.5 rounded bg-amber-950/80 text-amber-300 border border-amber-800/40">autonomous agents</span>
|
|
1125
|
+
</div>
|
|
1126
|
+
|
|
1127
|
+
<!-- Prompt Snippet -->
|
|
1128
|
+
<div class="p-3.5 rounded-xl bg-slate-950/90 border border-slate-800/90 font-mono text-xs text-slate-300">
|
|
1129
|
+
<span class="text-amber-400 font-bold">Your Agent:</span> "Order #8492 placed. Issue a POS receipt, generate shipping label for DHL, and email customer."
|
|
1130
|
+
</div>
|
|
1131
|
+
|
|
1132
|
+
<!-- How Tailprint Works & Business Value -->
|
|
1133
|
+
<div class="space-y-2 text-xs text-slate-400">
|
|
1134
|
+
<p class="flex items-start space-x-2">
|
|
1135
|
+
<span class="text-emerald-400 font-bold">✓</span>
|
|
1136
|
+
<span><strong class="text-slate-200">What happens:</strong> Agenter kalder <code class="text-slate-300">render_template('ticket' | 'receipt' | 'shipping-label')</code> on-demand via JSON-RPC.</span>
|
|
1137
|
+
</p>
|
|
1138
|
+
<p class="flex items-start space-x-2">
|
|
1139
|
+
<span class="text-purple-400 font-bold">💎</span>
|
|
1140
|
+
<span><strong class="text-purple-300">Business Value:</strong> Eliminates headless Chrome dependencies and Puppeteer crashes in your application backend.</span>
|
|
1141
|
+
</p>
|
|
1142
|
+
</div>
|
|
1143
|
+
</div>
|
|
1144
|
+
</div>
|
|
1145
|
+
|
|
1146
|
+
<!-- Use Case 5: Native Data Visualizations & Charts -->
|
|
1147
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 flex flex-col justify-between hover:border-violet-500/50 transition shadow-lg group">
|
|
1148
|
+
<div class="space-y-4">
|
|
1149
|
+
<div class="flex items-center justify-between">
|
|
1150
|
+
<div class="flex items-center space-x-2.5">
|
|
1151
|
+
<span class="p-2 rounded-xl bg-violet-500/10 text-violet-400 text-lg">📊</span>
|
|
1152
|
+
<h4 class="text-sm font-bold text-white group-hover:text-violet-400 transition">5. High-DPI Charts & Data Visuals</h4>
|
|
1153
|
+
</div>
|
|
1154
|
+
<span class="text-[10px] font-bold px-2 py-0.5 rounded bg-violet-950/80 text-violet-300 border border-violet-800/40">render_chart</span>
|
|
1155
|
+
</div>
|
|
1156
|
+
|
|
1157
|
+
<!-- Prompt Snippet -->
|
|
1158
|
+
<div class="p-3.5 rounded-xl bg-slate-950/90 border border-slate-800/90 font-mono text-xs text-slate-300">
|
|
1159
|
+
<span class="text-violet-400 font-bold">You to Claude:</span> "Plot our monthly MRR growth vs burn rate for the last 6 months as a dark-mode dual bar chart."
|
|
1160
|
+
</div>
|
|
1161
|
+
|
|
1162
|
+
<!-- How Tailprint Works & Business Value -->
|
|
1163
|
+
<div class="space-y-2 text-xs text-slate-400">
|
|
1164
|
+
<p class="flex items-start space-x-2">
|
|
1165
|
+
<span class="text-emerald-400 font-bold">✓</span>
|
|
1166
|
+
<span><strong class="text-slate-200">What happens:</strong> Compiles Chart.js options into a high-DPI image and streams it back inline in chat.</span>
|
|
1167
|
+
</p>
|
|
1168
|
+
<p class="flex items-start space-x-2">
|
|
1169
|
+
<span class="text-purple-400 font-bold">💎</span>
|
|
1170
|
+
<span><strong class="text-purple-300">Business Value:</strong> No text approximations or ugly ASCII art. Real, presentation-ready charts right inside your LLM workflow.</span>
|
|
1171
|
+
</p>
|
|
1172
|
+
</div>
|
|
1173
|
+
</div>
|
|
1174
|
+
</div>
|
|
1175
|
+
|
|
1176
|
+
<!-- Use Case 6: Scannable QR Codes & Barcodes -->
|
|
1177
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 flex flex-col justify-between hover:border-emerald-500/50 transition shadow-lg group">
|
|
1178
|
+
<div class="space-y-4">
|
|
1179
|
+
<div class="flex items-center justify-between">
|
|
1180
|
+
<div class="flex items-center space-x-2.5">
|
|
1181
|
+
<span class="p-2 rounded-xl bg-emerald-500/10 text-emerald-400 text-lg">📱</span>
|
|
1182
|
+
<h4 class="text-sm font-bold text-white group-hover:text-emerald-400 transition">6. Instant Verified QR Codes & Barcodes</h4>
|
|
1183
|
+
</div>
|
|
1184
|
+
<span class="text-[10px] font-bold px-2 py-0.5 rounded bg-emerald-950/80 text-emerald-300 border border-emerald-800/40">generate_qr</span>
|
|
1185
|
+
</div>
|
|
1186
|
+
|
|
1187
|
+
<!-- Prompt Snippet -->
|
|
1188
|
+
<div class="p-3.5 rounded-xl bg-slate-950/90 border border-slate-800/90 font-mono text-xs text-slate-300">
|
|
1189
|
+
<span class="text-emerald-400 font-bold">You to Cursor:</span> "Generate a scannable high-resolution QR code pointing to https://tailprint.com/dashboard with dark theme."
|
|
1190
|
+
</div>
|
|
1191
|
+
|
|
1192
|
+
<!-- How Tailprint Works & Business Value -->
|
|
1193
|
+
<div class="space-y-2 text-xs text-slate-400">
|
|
1194
|
+
<p class="flex items-start space-x-2">
|
|
1195
|
+
<span class="text-emerald-400 font-bold">✓</span>
|
|
1196
|
+
<span><strong class="text-slate-200">What happens:</strong> Generates mathematically verified PNG or SVG QR matrices with custom error correction (L/M/Q/H).</span>
|
|
1197
|
+
</p>
|
|
1198
|
+
<p class="flex items-start space-x-2">
|
|
1199
|
+
<span class="text-purple-400 font-bold">💎</span>
|
|
1200
|
+
<span><strong class="text-purple-300">Business Value:</strong> Chatbots can't draw scannable QR matrices on their own. Tailprint delivers scan-ready codes instantly.</span>
|
|
1201
|
+
</p>
|
|
1202
|
+
</div>
|
|
1203
|
+
</div>
|
|
1204
|
+
</div>
|
|
1205
|
+
|
|
1206
|
+
<!-- Use Case 7: Multi-Device Studio Mockups -->
|
|
1207
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 flex flex-col justify-between hover:border-blue-500/50 transition shadow-lg group">
|
|
1208
|
+
<div class="space-y-4">
|
|
1209
|
+
<div class="flex items-center justify-between">
|
|
1210
|
+
<div class="flex items-center space-x-2.5">
|
|
1211
|
+
<span class="p-2 rounded-xl bg-blue-500/10 text-blue-400 text-lg">💻</span>
|
|
1212
|
+
<h4 class="text-sm font-bold text-white group-hover:text-blue-400 transition">7. Responsive Studio Device Mockups</h4>
|
|
1213
|
+
</div>
|
|
1214
|
+
<span class="text-[10px] font-bold px-2 py-0.5 rounded bg-blue-950/80 text-blue-300 border border-blue-800/40">render_device_mockup</span>
|
|
1215
|
+
</div>
|
|
1216
|
+
|
|
1217
|
+
<!-- Prompt Snippet -->
|
|
1218
|
+
<div class="p-3.5 rounded-xl bg-slate-950/90 border border-slate-800/90 font-mono text-xs text-slate-300">
|
|
1219
|
+
<span class="text-blue-400 font-bold">You to Claude:</span> "Create a showcase device mockup of https://tailprint.com on a MacBook Pro and floating iPhone 16."
|
|
1220
|
+
</div>
|
|
1221
|
+
|
|
1222
|
+
<!-- How Tailprint Works & Business Value -->
|
|
1223
|
+
<div class="space-y-2 text-xs text-slate-400">
|
|
1224
|
+
<p class="flex items-start space-x-2">
|
|
1225
|
+
<span class="text-emerald-400 font-bold">✓</span>
|
|
1226
|
+
<span><strong class="text-slate-200">What happens:</strong> Captures desktop and mobile viewports in parallel and composites them into a 1600x1000 studio scene.</span>
|
|
1227
|
+
</p>
|
|
1228
|
+
<p class="flex items-start space-x-2">
|
|
1229
|
+
<span class="text-purple-400 font-bold">💎</span>
|
|
1230
|
+
<span><strong class="text-purple-300">Business Value:</strong> Perfect for landing page hero assets, pitch decks, and social media announcements in 1 click.</span>
|
|
1231
|
+
</p>
|
|
1232
|
+
</div>
|
|
1233
|
+
</div>
|
|
1234
|
+
</div>
|
|
1235
|
+
|
|
1236
|
+
<!-- Use Case 8: Watermarking & Document Stamping -->
|
|
1237
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 flex flex-col justify-between hover:border-rose-500/50 transition shadow-lg group">
|
|
1238
|
+
<div class="space-y-4">
|
|
1239
|
+
<div class="flex items-center justify-between">
|
|
1240
|
+
<div class="flex items-center space-x-2.5">
|
|
1241
|
+
<span class="p-2 rounded-xl bg-rose-500/10 text-rose-400 text-lg">🔒</span>
|
|
1242
|
+
<h4 class="text-sm font-bold text-white group-hover:text-rose-400 transition">8. PDF Watermarking & Compliance Stamping</h4>
|
|
1243
|
+
</div>
|
|
1244
|
+
<span class="text-[10px] font-bold px-2 py-0.5 rounded bg-rose-950/80 text-rose-300 border border-rose-800/40">stamp_pdf</span>
|
|
1245
|
+
</div>
|
|
1246
|
+
|
|
1247
|
+
<!-- Prompt Snippet -->
|
|
1248
|
+
<div class="p-3.5 rounded-xl bg-slate-950/90 border border-slate-800/90 font-mono text-xs text-slate-300">
|
|
1249
|
+
<span class="text-rose-400 font-bold">You to Claude:</span> "Stamp this PDF contract with a diagonal semi-transparent 'FORTROLIGT / CONFIDENTIAL' across all pages."
|
|
1250
|
+
</div>
|
|
1251
|
+
|
|
1252
|
+
<!-- How Tailprint Works & Business Value -->
|
|
1253
|
+
<div class="space-y-2 text-xs text-slate-400">
|
|
1254
|
+
<p class="flex items-start space-x-2">
|
|
1255
|
+
<span class="text-emerald-400 font-bold">✓</span>
|
|
1256
|
+
<span><strong class="text-slate-200">What happens:</strong> Streams the PDF through pdf-lib, injecting vector watermark layers with custom rotation, opacity, and color.</span>
|
|
1257
|
+
</p>
|
|
1258
|
+
<p class="flex items-start space-x-2">
|
|
1259
|
+
<span class="text-purple-400 font-bold">💎</span>
|
|
1260
|
+
<span><strong class="text-purple-300">Business Value:</strong> Ensures enterprise compliance and NDA adherence before documents leave your AI workflow.</span>
|
|
1261
|
+
</p>
|
|
1262
|
+
</div>
|
|
1263
|
+
</div>
|
|
1264
|
+
</div>
|
|
1265
|
+
|
|
1266
|
+
<!-- Use Case 9: Visual Regression Diff Heatmap -->
|
|
1267
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 flex flex-col justify-between hover:border-amber-500/50 transition shadow-lg group">
|
|
1268
|
+
<div class="space-y-4">
|
|
1269
|
+
<div class="flex items-center justify-between">
|
|
1270
|
+
<div class="flex items-center space-x-2.5">
|
|
1271
|
+
<span class="p-2 rounded-xl bg-amber-500/10 text-amber-400 text-lg">🔍</span>
|
|
1272
|
+
<h4 class="text-sm font-bold text-white group-hover:text-amber-400 transition">9. Visual Regression & Layout Diff Heatmap</h4>
|
|
1273
|
+
</div>
|
|
1274
|
+
<span class="text-[10px] font-bold px-2 py-0.5 rounded bg-amber-950/80 text-amber-300 border border-amber-800/40">compare_snapshots</span>
|
|
1275
|
+
</div>
|
|
1276
|
+
|
|
1277
|
+
<!-- Prompt Snippet -->
|
|
1278
|
+
<div class="p-3.5 rounded-xl bg-slate-950/90 border border-slate-800/90 font-mono text-xs text-slate-300">
|
|
1279
|
+
<span class="text-amber-400 font-bold">You to Cursor:</span> "Compare our staging homepage against production. Highlight any CSS regressions or unintended pixel shifts."
|
|
1280
|
+
</div>
|
|
1281
|
+
|
|
1282
|
+
<!-- How Tailprint Works & Business Value -->
|
|
1283
|
+
<div class="space-y-2 text-xs text-slate-400">
|
|
1284
|
+
<p class="flex items-start space-x-2">
|
|
1285
|
+
<span class="text-emerald-400 font-bold">✓</span>
|
|
1286
|
+
<span><strong class="text-slate-200">What happens:</strong> Compares screenshots pixel-by-pixel with pixelmatch and streams a 3-panel Before / Heatmap / After studio image inline.</span>
|
|
1287
|
+
</p>
|
|
1288
|
+
<p class="flex items-start space-x-2">
|
|
1289
|
+
<span class="text-purple-400 font-bold">💎</span>
|
|
1290
|
+
<span><strong class="text-purple-300">Business Value:</strong> Catch breaking CSS bugs and layout shifts before deploying to production without paying $500/mo for Percy.</span>
|
|
1291
|
+
</p>
|
|
1292
|
+
</div>
|
|
1293
|
+
</div>
|
|
1294
|
+
</div>
|
|
1295
|
+
|
|
1296
|
+
<!-- Use Case 10: Multi-Slide Social Carousels -->
|
|
1297
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 flex flex-col justify-between hover:border-indigo-500/50 transition shadow-lg group">
|
|
1298
|
+
<div class="space-y-4">
|
|
1299
|
+
<div class="flex items-center justify-between">
|
|
1300
|
+
<div class="flex items-center space-x-2.5">
|
|
1301
|
+
<span class="p-2 rounded-xl bg-indigo-500/10 text-indigo-400 text-lg">📱</span>
|
|
1302
|
+
<h4 class="text-sm font-bold text-white group-hover:text-indigo-400 transition">10. Viral LinkedIn & Instagram Carousels</h4>
|
|
1303
|
+
</div>
|
|
1304
|
+
<span class="text-[10px] font-bold px-2 py-0.5 rounded bg-indigo-950/80 text-indigo-300 border border-indigo-800/40">render_carousel</span>
|
|
1305
|
+
</div>
|
|
1306
|
+
|
|
1307
|
+
<!-- Prompt Snippet -->
|
|
1308
|
+
<div class="p-3.5 rounded-xl bg-slate-950/90 border border-slate-800/90 font-mono text-xs text-slate-300">
|
|
1309
|
+
<span class="text-indigo-400 font-bold">You to Claude:</span> "Turn my blog post into a 5-slide 1080x1350 portrait carousel PDF for LinkedIn with dark theme and swipe arrows."
|
|
1310
|
+
</div>
|
|
1311
|
+
|
|
1312
|
+
<!-- How Tailprint Works & Business Value -->
|
|
1313
|
+
<div class="space-y-2 text-xs text-slate-400">
|
|
1314
|
+
<p class="flex items-start space-x-2">
|
|
1315
|
+
<span class="text-emerald-400 font-bold">✓</span>
|
|
1316
|
+
<span><strong class="text-slate-200">What happens:</strong> Generates a multi-page vector PDF (LinkedIn carousel standard) + PNG cover preview hosted on CDN.</span>
|
|
1317
|
+
</p>
|
|
1318
|
+
<p class="flex items-start space-x-2">
|
|
1319
|
+
<span class="text-purple-400 font-bold">💎</span>
|
|
1320
|
+
<span><strong class="text-purple-300">Business Value:</strong> Solopreneurs and marketers turn thoughts into swipeable high-converting social assets in 5 seconds.</span>
|
|
1321
|
+
</p>
|
|
1322
|
+
</div>
|
|
1323
|
+
</div>
|
|
1324
|
+
</div>
|
|
1325
|
+
|
|
1326
|
+
<!-- Use Case 11: EU Factur-X / PEPPOL E-Invoicing -->
|
|
1327
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 flex flex-col justify-between hover:border-emerald-500/50 transition shadow-lg group">
|
|
1328
|
+
<div class="space-y-4">
|
|
1329
|
+
<div class="flex items-center justify-between">
|
|
1330
|
+
<div class="flex items-center space-x-2.5">
|
|
1331
|
+
<span class="p-2 rounded-xl bg-emerald-500/10 text-emerald-400 text-lg">🇪🇺</span>
|
|
1332
|
+
<h4 class="text-sm font-bold text-white group-hover:text-emerald-400 transition">11. EU Factur-X & PEPPOL E-Invoicing</h4>
|
|
1333
|
+
</div>
|
|
1334
|
+
<span class="text-[10px] font-bold px-2 py-0.5 rounded bg-emerald-950/80 text-emerald-300 border border-emerald-800/40">generate_einvoice</span>
|
|
1335
|
+
</div>
|
|
1336
|
+
|
|
1337
|
+
<!-- Prompt Snippet -->
|
|
1338
|
+
<div class="p-3.5 rounded-xl bg-slate-950/90 border border-slate-800/90 font-mono text-xs text-slate-300">
|
|
1339
|
+
<span class="text-indigo-400 font-bold">You to Claude:</span> "Bill BMW AG in Munich for 40 hours consulting at 150 EUR/hr. Make it an official EU Factur-X hybrid invoice with my Danish IBAN."
|
|
1340
|
+
</div>
|
|
1341
|
+
|
|
1342
|
+
<!-- How Tailprint Works & Business Value -->
|
|
1343
|
+
<div class="space-y-2 text-xs text-slate-400">
|
|
1344
|
+
<p class="flex items-start space-x-2">
|
|
1345
|
+
<span class="text-emerald-400 font-bold">✓</span>
|
|
1346
|
+
<span><strong class="text-slate-200">What happens:</strong> Generates a Tailwind A4 PDF with valid <strong class="text-slate-200">factur-x.xml</strong> (EN 16931) embedded directly in the PDF catalog.</span>
|
|
1347
|
+
</p>
|
|
1348
|
+
<p class="flex items-start space-x-2">
|
|
1349
|
+
<span class="text-purple-400 font-bold">💎</span>
|
|
1350
|
+
<span><strong class="text-purple-300">Business Value:</strong> Ingestible by SAP, DATEV, Dinero & Chorus Pro. Complies with upcoming EU mandatory B2B e-invoicing laws.</span>
|
|
1351
|
+
</p>
|
|
1352
|
+
</div>
|
|
1353
|
+
</div>
|
|
1354
|
+
</div>
|
|
1355
|
+
|
|
1356
|
+
</div>
|
|
1357
|
+
</div>
|
|
1358
|
+
|
|
1359
|
+
<!-- Quickstart Config Box & Protocol Breakdown -->
|
|
1360
|
+
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 items-start pt-8 border-t border-slate-800/80">
|
|
1361
|
+
|
|
1362
|
+
<!-- Left: Protocol Capabilities Breakdown -->
|
|
1363
|
+
<div class="lg:col-span-5 space-y-4">
|
|
1364
|
+
<div class="space-y-2">
|
|
1365
|
+
<span class="text-xs font-bold text-indigo-400 uppercase tracking-wider">Protocol Architecture</span>
|
|
1366
|
+
<h3 class="text-xl font-bold text-white">Full MCP Protocol Compliance</h3>
|
|
1367
|
+
<p class="text-xs text-slate-400 leading-relaxed">
|
|
1368
|
+
Tailprint implements all three official Model Context Protocol primitives for complete interoperability across all leading AI runtimes.
|
|
1369
|
+
</p>
|
|
1370
|
+
</div>
|
|
1371
|
+
|
|
1372
|
+
<div class="space-y-3 pt-2">
|
|
1373
|
+
<div class="p-3.5 rounded-xl bg-slate-900/60 border border-slate-800 flex items-start space-x-3">
|
|
1374
|
+
<span class="text-base">🛠️</span>
|
|
1375
|
+
<div>
|
|
1376
|
+
<h4 class="text-xs font-bold text-white">13 Native Tools</h4>
|
|
1377
|
+
<p class="text-[11px] text-slate-400 mt-0.5">render_pdf, render_image, render_chart, generate_qr, render_device_mockup, stamp_pdf, compare_snapshots, render_carousel, generate_einvoice, scrape_markdown, render_template, list_templates, get_template_source.</p>
|
|
1378
|
+
</div>
|
|
1379
|
+
</div>
|
|
1380
|
+
|
|
1381
|
+
<div class="p-3.5 rounded-xl bg-slate-900/60 border border-slate-800 flex items-start space-x-3">
|
|
1382
|
+
<span class="text-base">📚</span>
|
|
1383
|
+
<div>
|
|
1384
|
+
<h4 class="text-xs font-bold text-white">10 Resources (Context URI)</h4>
|
|
1385
|
+
<p class="text-[11px] text-slate-400 mt-0.5">tailprint://templates/catalog, tailprint://docs/tailwind-guide, and static HTML templates directly readable into context.</p>
|
|
1386
|
+
</div>
|
|
1387
|
+
</div>
|
|
1388
|
+
|
|
1389
|
+
<div class="p-3.5 rounded-xl bg-slate-900/60 border border-slate-800 flex items-start space-x-3">
|
|
1390
|
+
<span class="text-base">💬</span>
|
|
1391
|
+
<div>
|
|
1392
|
+
<h4 class="text-xs font-bold text-white">3 Prompts (1-Click Workflows)</h4>
|
|
1393
|
+
<p class="text-[11px] text-slate-400 mt-0.5">generate_invoice, summarize_url_to_pdf, create_social_card prompts pre-configured for Claude.</p>
|
|
1394
|
+
</div>
|
|
1395
|
+
</div>
|
|
1396
|
+
</div>
|
|
1397
|
+
</div>
|
|
1398
|
+
|
|
1399
|
+
<!-- Right: Config Snippets & Code Box -->
|
|
1400
|
+
<div class="lg:col-span-7 bg-slate-900/90 rounded-2xl border border-slate-800 p-6 shadow-2xl space-y-4">
|
|
1401
|
+
<div class="flex flex-wrap items-center justify-between gap-3 pb-3 border-b border-slate-800">
|
|
1402
|
+
<div class="flex flex-wrap gap-1.5">
|
|
1403
|
+
<button onclick="switchMcpTab('claude')" id="btn-mcp-claude" class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-indigo-600 text-white">Claude Desktop</button>
|
|
1404
|
+
<button onclick="switchMcpTab('cursor')" id="btn-mcp-cursor" class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-slate-800 text-slate-400 hover:text-white">Cursor</button>
|
|
1405
|
+
<button onclick="switchMcpTab('sse')" id="btn-mcp-sse" class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-slate-800 text-slate-400 hover:text-white">Remote Cloud SSE</button>
|
|
1406
|
+
</div>
|
|
1407
|
+
<div class="flex items-center space-x-2">
|
|
1408
|
+
<a id="btn-cursor-deeplink" href="#" class="hidden px-3 py-1.5 rounded-lg text-xs font-bold bg-cyan-600 hover:bg-cyan-500 text-white transition flex items-center space-x-1.5 shadow-md shadow-cyan-600/20">
|
|
1409
|
+
<span>⚡ 1-Click Install in Cursor</span>
|
|
1410
|
+
</a>
|
|
1411
|
+
<button onclick="copyMcpSnippet()" id="mcp-copy-btn" class="px-3 py-1.5 rounded-lg text-xs bg-slate-800 hover:bg-slate-700 text-indigo-300 font-semibold flex items-center space-x-1 transition">
|
|
1412
|
+
<span>📋 Copy Config</span>
|
|
1413
|
+
</button>
|
|
1414
|
+
</div>
|
|
1415
|
+
</div>
|
|
1416
|
+
|
|
1417
|
+
<!-- Dynamic Key Status Banner -->
|
|
1418
|
+
<div id="mcp-key-banner" class="flex items-center justify-between px-3.5 py-2 rounded-xl bg-slate-950/60 border border-slate-800 text-xs">
|
|
1419
|
+
<div class="flex items-center space-x-2">
|
|
1420
|
+
<span id="mcp-key-dot" class="w-2 h-2 rounded-full bg-amber-400"></span>
|
|
1421
|
+
<span id="mcp-key-text" class="text-slate-400">Using placeholder key</span>
|
|
1422
|
+
</div>
|
|
1423
|
+
<button onclick="openFreeKeyModal()" id="mcp-key-action-btn" class="text-indigo-400 hover:text-indigo-300 font-bold transition">
|
|
1424
|
+
Get 100 free renders ⚡
|
|
1425
|
+
</button>
|
|
1426
|
+
</div>
|
|
1427
|
+
|
|
1428
|
+
<pre id="mcp-code-block" class="p-4 bg-slate-950 rounded-xl text-xs font-mono text-slate-300 overflow-x-auto leading-relaxed border border-slate-800/80 max-h-80 whitespace-pre"></pre>
|
|
1429
|
+
<p id="mcp-hint" class="text-[11px] text-slate-400 leading-relaxed"></p>
|
|
1430
|
+
</div>
|
|
1431
|
+
|
|
1432
|
+
</div>
|
|
1433
|
+
|
|
1434
|
+
</div>
|
|
1435
|
+
</section>
|
|
1436
|
+
|
|
1437
|
+
<!-- Pricing Section -->
|
|
1438
|
+
<section id="pricing" class="py-24">
|
|
1439
|
+
<div class="max-w-7xl mx-auto px-6">
|
|
1440
|
+
<div class="text-center max-w-2xl mx-auto mb-16">
|
|
1441
|
+
<div class="inline-flex items-center space-x-2 px-3 py-1 rounded-full bg-emerald-950/60 border border-emerald-800/50 text-emerald-400 text-xs font-semibold mb-4">
|
|
1442
|
+
<span>High-Margin, Conversion-Optimized Tiers</span>
|
|
1443
|
+
</div>
|
|
1444
|
+
<h2 class="text-3xl sm:text-4xl font-extrabold text-white">Simple, High-Value Pricing</h2>
|
|
1445
|
+
<p class="text-slate-400 mt-3">Underbidding competitors while delivering instant sub-500ms Chromium execution. Automatic API key provisioning via Stripe Checkout.</p>
|
|
1446
|
+
</div>
|
|
1447
|
+
|
|
1448
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 items-stretch">
|
|
1449
|
+
<!-- Free / Developer Sandbox Plan -->
|
|
1450
|
+
<div class="p-6 rounded-2xl bg-slate-900/70 border border-emerald-500/30 flex flex-col justify-between hover:border-emerald-500/50 transition relative">
|
|
1451
|
+
<div class="absolute -top-3.5 right-6 px-3 py-0.5 rounded-full bg-emerald-600/90 text-white text-[10px] font-black tracking-wider uppercase">
|
|
1452
|
+
NO CREDIT CARD
|
|
1453
|
+
</div>
|
|
1454
|
+
<div>
|
|
1455
|
+
<div class="flex justify-between items-center">
|
|
1456
|
+
<h3 class="text-lg font-bold text-white">${PLANS.free.name}</h3>
|
|
1457
|
+
</div>
|
|
1458
|
+
<p class="text-xs text-emerald-400 font-medium mt-1">${PLANS.free.targetAudience}</p>
|
|
1459
|
+
<div class="mt-5 flex items-baseline">
|
|
1460
|
+
<span class="text-4xl font-extrabold text-white">$0</span>
|
|
1461
|
+
<span class="text-slate-400 ml-1.5 text-sm">/ forever</span>
|
|
1462
|
+
</div>
|
|
1463
|
+
<p class="text-xs text-emerald-400 font-bold mt-1">100 renders / month</p>
|
|
1464
|
+
<p class="text-xs text-slate-400 mt-3 leading-relaxed">${PLANS.free.description}</p>
|
|
1465
|
+
|
|
1466
|
+
<ul class="mt-6 space-y-2.5 text-xs text-slate-300">
|
|
1467
|
+
${PLANS.free.features.map((f) => `<li class="flex items-start space-x-2"><span class="text-emerald-400 font-bold mt-0.5">✓</span><span>${f}</span></li>`).join('')}
|
|
1468
|
+
</ul>
|
|
1469
|
+
</div>
|
|
1470
|
+
<button onclick="startCheckout('free')" class="mt-6 w-full py-2.5 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-bold transition text-xs shadow-lg shadow-emerald-600/20">
|
|
1471
|
+
Start Free via Stripe (No Card) ⚡
|
|
1472
|
+
</button>
|
|
1473
|
+
</div>
|
|
1474
|
+
|
|
1475
|
+
<!-- Starter Plan -->
|
|
1476
|
+
<div class="p-6 rounded-2xl bg-slate-900/70 border border-slate-800 flex flex-col justify-between hover:border-slate-700 transition">
|
|
1477
|
+
<div>
|
|
1478
|
+
<div class="flex justify-between items-center">
|
|
1479
|
+
<h3 class="text-lg font-bold text-white">${PLANS.starter.name}</h3>
|
|
1480
|
+
</div>
|
|
1481
|
+
<p class="text-xs text-indigo-400 font-medium mt-1">${PLANS.starter.targetAudience}</p>
|
|
1482
|
+
<div class="mt-5 flex items-baseline">
|
|
1483
|
+
<span class="text-4xl font-extrabold text-white">$${PLANS.starter.priceUsd}</span>
|
|
1484
|
+
<span class="text-slate-400 ml-1.5 text-sm">/ mo</span>
|
|
1485
|
+
</div>
|
|
1486
|
+
<p class="text-xs text-emerald-400 font-bold mt-1">2,500 renders / month</p>
|
|
1487
|
+
<p class="text-xs text-slate-400 mt-3 leading-relaxed">${PLANS.starter.description}</p>
|
|
1488
|
+
|
|
1489
|
+
<ul class="mt-6 space-y-2.5 text-xs text-slate-300">
|
|
1490
|
+
${PLANS.starter.features.map((f) => `<li class="flex items-start space-x-2"><span class="text-emerald-400 font-bold mt-0.5">✓</span><span>${f}</span></li>`).join('')}
|
|
1491
|
+
</ul>
|
|
1492
|
+
</div>
|
|
1493
|
+
<button onclick="startCheckout('starter')" class="mt-6 w-full py-2.5 rounded-xl bg-slate-800 hover:bg-slate-700 text-white font-bold transition text-xs">
|
|
1494
|
+
Start with Starter ($14/mo)
|
|
1495
|
+
</button>
|
|
1496
|
+
</div>
|
|
1497
|
+
|
|
1498
|
+
<!-- Pro Plan (Most Popular) -->
|
|
1499
|
+
<div class="p-6 rounded-2xl bg-gradient-to-b from-indigo-950/70 via-slate-900 to-slate-900 border-2 border-indigo-500 flex flex-col justify-between relative shadow-xl shadow-indigo-500/10 lg:-translate-y-2">
|
|
1500
|
+
<div class="absolute -top-3.5 right-6 px-3 py-0.5 rounded-full bg-indigo-500 text-white text-[10px] font-black tracking-wider uppercase shadow-md">
|
|
1501
|
+
MOST POPULAR
|
|
1502
|
+
</div>
|
|
1503
|
+
<div>
|
|
1504
|
+
<div class="flex justify-between items-center">
|
|
1505
|
+
<h3 class="text-lg font-bold text-white">${PLANS.pro.name}</h3>
|
|
1506
|
+
</div>
|
|
1507
|
+
<p class="text-xs text-indigo-300 font-medium mt-1">${PLANS.pro.targetAudience}</p>
|
|
1508
|
+
<div class="mt-5 flex items-baseline">
|
|
1509
|
+
<span class="text-4xl font-extrabold text-white">$${PLANS.pro.priceUsd}</span>
|
|
1510
|
+
<span class="text-slate-400 ml-1.5 text-sm">/ mo</span>
|
|
1511
|
+
</div>
|
|
1512
|
+
<p class="text-xs text-emerald-400 font-bold mt-1">20,000 renders / month</p>
|
|
1513
|
+
<p class="text-xs text-slate-300 mt-3 leading-relaxed">${PLANS.pro.description}</p>
|
|
1514
|
+
|
|
1515
|
+
<ul class="mt-6 space-y-2.5 text-xs text-slate-200">
|
|
1516
|
+
${PLANS.pro.features.map((f) => `<li class="flex items-start space-x-2"><span class="text-emerald-400 font-bold mt-0.5">✓</span><span>${f}</span></li>`).join('')}
|
|
1517
|
+
</ul>
|
|
1518
|
+
</div>
|
|
1519
|
+
<button onclick="startCheckout('pro')" class="mt-6 w-full py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-bold transition text-xs shadow-lg shadow-indigo-600/30">
|
|
1520
|
+
Get Pro (20k Renders)
|
|
1521
|
+
</button>
|
|
1522
|
+
</div>
|
|
1523
|
+
|
|
1524
|
+
<!-- Business / Scale Plan -->
|
|
1525
|
+
<div class="p-6 rounded-2xl bg-slate-900/70 border border-slate-800 flex flex-col justify-between hover:border-slate-700 transition relative">
|
|
1526
|
+
<div class="absolute -top-3.5 right-6 px-3 py-0.5 rounded-full bg-indigo-600/90 text-white text-[10px] font-black tracking-wider uppercase">
|
|
1527
|
+
SCALE VALUE
|
|
1528
|
+
</div>
|
|
1529
|
+
<div>
|
|
1530
|
+
<div class="flex justify-between items-center">
|
|
1531
|
+
<h3 class="text-lg font-bold text-white">${PLANS.business.name}</h3>
|
|
1532
|
+
</div>
|
|
1533
|
+
<p class="text-xs text-emerald-400 font-medium mt-1">${PLANS.business.targetAudience}</p>
|
|
1534
|
+
<div class="mt-5 flex items-baseline">
|
|
1535
|
+
<span class="text-4xl font-extrabold text-white">$${PLANS.business.priceUsd}</span>
|
|
1536
|
+
<span class="text-slate-400 ml-1.5 text-sm">/ mo</span>
|
|
1537
|
+
</div>
|
|
1538
|
+
<p class="text-xs text-emerald-400 font-bold mt-1">75,000 renders / month</p>
|
|
1539
|
+
<p class="text-xs text-slate-400 mt-3 leading-relaxed">${PLANS.business.description}</p>
|
|
1540
|
+
|
|
1541
|
+
<ul class="mt-6 space-y-2.5 text-xs text-slate-300">
|
|
1542
|
+
${PLANS.business.features.map((f) => `<li class="flex items-start space-x-2"><span class="text-emerald-400 font-bold mt-0.5">✓</span><span>${f}</span></li>`).join('')}
|
|
1543
|
+
</ul>
|
|
1544
|
+
</div>
|
|
1545
|
+
<button onclick="startCheckout('business')" class="mt-6 w-full py-2.5 rounded-xl bg-slate-800 hover:bg-slate-700 text-white font-bold transition text-xs">
|
|
1546
|
+
Scale with Business ($99/mo)
|
|
1547
|
+
</button>
|
|
1548
|
+
</div>
|
|
1549
|
+
</div>
|
|
1550
|
+
|
|
1551
|
+
<!-- Enterprise Callout Banner -->
|
|
1552
|
+
<div class="mt-10 p-6 rounded-2xl bg-slate-900/50 border border-slate-800 flex flex-col sm:flex-row items-center justify-between gap-6">
|
|
1553
|
+
<div>
|
|
1554
|
+
<div class="inline-flex items-center space-x-2 px-2.5 py-0.5 rounded-full bg-purple-500/10 text-purple-400 border border-purple-500/20 text-[10px] font-bold uppercase tracking-wider mb-1.5">
|
|
1555
|
+
<span>Enterprise & Custom Pools</span>
|
|
1556
|
+
</div>
|
|
1557
|
+
<h4 class="text-white font-bold text-base">Need 100,000+ monthly renders, dedicated browser workers or strict 99.9% uptime SLA?</h4>
|
|
1558
|
+
<p class="text-xs text-slate-400 mt-1">Enterprise plans start at $199/mo with dedicated Chromium instances and private Slack/Discord technical channel.</p>
|
|
1559
|
+
</div>
|
|
1560
|
+
<button onclick="startCheckout('enterprise')" class="px-5 py-2.5 rounded-xl bg-slate-800 hover:bg-slate-700 text-white font-bold text-xs transition whitespace-nowrap border border-slate-700">
|
|
1561
|
+
Deploy Enterprise ($199/mo)
|
|
1562
|
+
</button>
|
|
1563
|
+
</div>
|
|
1564
|
+
</div>
|
|
1565
|
+
</section>
|
|
1566
|
+
|
|
1567
|
+
<!-- Frequently Asked Questions Section (SEO & GEO Knowledge Engine) -->
|
|
1568
|
+
<section id="faq" class="py-20 bg-slate-900/40 border-t border-slate-800/80">
|
|
1569
|
+
<div class="max-w-4xl mx-auto px-6">
|
|
1570
|
+
<div class="text-center mb-12">
|
|
1571
|
+
<div class="inline-flex items-center space-x-2 px-3 py-1 rounded-full bg-indigo-950/60 border border-indigo-800/50 text-indigo-400 text-xs font-semibold mb-3">
|
|
1572
|
+
<span>Architecture & Knowledge Base</span>
|
|
1573
|
+
</div>
|
|
1574
|
+
<h2 class="text-3xl sm:text-4xl font-extrabold text-white">Frequently Asked Questions</h2>
|
|
1575
|
+
<p class="text-slate-400 mt-2 text-sm">Everything you need to know about Tailwind rendering, the MCP server, performance, and security.</p>
|
|
1576
|
+
</div>
|
|
1577
|
+
|
|
1578
|
+
<div class="space-y-4">
|
|
1579
|
+
<!-- Q1 -->
|
|
1580
|
+
<details class="group rounded-2xl bg-slate-900/70 border border-slate-800 p-6 transition duration-200 open:border-indigo-500/50 open:bg-slate-900/90" open>
|
|
1581
|
+
<summary class="flex items-center justify-between font-bold text-white cursor-pointer list-none text-base">
|
|
1582
|
+
<span>How does Tailprint convert Tailwind CSS HTML to vector PDF?</span>
|
|
1583
|
+
<span class="text-indigo-400 text-lg group-open:rotate-180 transition-transform duration-200">↓</span>
|
|
1584
|
+
</summary>
|
|
1585
|
+
<p class="text-slate-300 text-sm mt-3 leading-relaxed">
|
|
1586
|
+
Tailprint uses isolated, pre-warmed Chromium instances with native Tailwind CSS JIT compilation. You submit raw HTML containing arbitrary utility classes (e.g. <code class="text-indigo-300 bg-slate-950 px-1.5 py-0.5 rounded text-xs font-mono">w-[350px]</code>, <code class="text-indigo-300 bg-slate-950 px-1.5 py-0.5 rounded text-xs font-mono">grid-cols-12</code>, custom gradients), and Tailprint compiles print-ready vector PDFs in under 500ms without build steps, PostCSS configs, or local browser dependencies.
|
|
1587
|
+
</p>
|
|
1588
|
+
</details>
|
|
1589
|
+
|
|
1590
|
+
<!-- Q2 -->
|
|
1591
|
+
<details class="group rounded-2xl bg-slate-900/70 border border-slate-800 p-6 transition duration-200 open:border-indigo-500/50 open:bg-slate-900/90">
|
|
1592
|
+
<summary class="flex items-center justify-between font-bold text-white cursor-pointer list-none text-base">
|
|
1593
|
+
<span>How do I connect Tailprint to Claude Desktop, Cursor, or AI coding agents via MCP?</span>
|
|
1594
|
+
<span class="text-indigo-400 text-lg group-open:rotate-180 transition-transform duration-200">↓</span>
|
|
1595
|
+
</summary>
|
|
1596
|
+
<p class="text-slate-300 text-sm mt-3 leading-relaxed">
|
|
1597
|
+
Tailprint implements the official <a href="#mcp" class="text-indigo-400 underline font-semibold">Model Context Protocol (MCP)</a> specification. Connect via Remote SSE at <code class="text-indigo-300 bg-slate-950 px-1.5 py-0.5 rounded text-xs font-mono">https://tailprint.com/v1/mcp/sse</code> by passing your API key in the headers of your Claude Desktop configuration (<code class="text-slate-400 text-xs font-mono">claude_desktop_config.json</code>) or Cursor IDE settings. Autonomous agents can then call tools like <code class="text-purple-300 bg-slate-950 px-1.5 py-0.5 rounded text-xs font-mono">render_pdf</code>, <code class="text-purple-300 bg-slate-950 px-1.5 py-0.5 rounded text-xs font-mono">render_image</code>, and <code class="text-purple-300 bg-slate-950 px-1.5 py-0.5 rounded text-xs font-mono">scrape_markdown</code> directly.
|
|
1598
|
+
</p>
|
|
1599
|
+
</details>
|
|
1600
|
+
|
|
1601
|
+
<!-- Q3 -->
|
|
1602
|
+
<details class="group rounded-2xl bg-slate-900/70 border border-slate-800 p-6 transition duration-200 open:border-indigo-500/50 open:bg-slate-900/90">
|
|
1603
|
+
<summary class="flex items-center justify-between font-bold text-white cursor-pointer list-none text-base">
|
|
1604
|
+
<span>What are the latency benchmarks and caching mechanisms?</span>
|
|
1605
|
+
<span class="text-indigo-400 text-lg group-open:rotate-180 transition-transform duration-200">↓</span>
|
|
1606
|
+
</summary>
|
|
1607
|
+
<p class="text-slate-300 text-sm mt-3 leading-relaxed">
|
|
1608
|
+
Fresh PDF renders execute in 350ms to 500ms on pre-warmed Chromium instances with memory pooling and strict concurrency throttling (<code class="text-slate-400 text-xs font-mono">p-limit</code>). For social preview cards and images, Tailprint automatically computes a SHA-256 hash of the input markup, returning instant cache hits in under 50ms with an <code class="text-emerald-400 bg-slate-950 px-1.5 py-0.5 rounded text-xs font-mono">X-Cache: HIT</code> header.
|
|
1609
|
+
</p>
|
|
1610
|
+
</details>
|
|
1611
|
+
|
|
1612
|
+
<!-- Q4 -->
|
|
1613
|
+
<details class="group rounded-2xl bg-slate-900/70 border border-slate-800 p-6 transition duration-200 open:border-indigo-500/50 open:bg-slate-900/90">
|
|
1614
|
+
<summary class="flex items-center justify-between font-bold text-white cursor-pointer list-none text-base">
|
|
1615
|
+
<span>What security and Server-Side Request Forgery (SSRF) protections are in place?</span>
|
|
1616
|
+
<span class="text-indigo-400 text-lg group-open:rotate-180 transition-transform duration-200">↓</span>
|
|
1617
|
+
</summary>
|
|
1618
|
+
<p class="text-slate-300 text-sm mt-3 leading-relaxed">
|
|
1619
|
+
Tailprint provides an enterprise-hardened network sandbox. All outbound network requests are scrutinized, and requests targeting local addresses or private IP subnets (<code class="text-slate-400 text-xs font-mono">127.0.0.1</code>, <code class="text-slate-400 text-xs font-mono">10.0.0.0/8</code>, <code class="text-slate-400 text-xs font-mono">172.16.0.0/12</code>, <code class="text-slate-400 text-xs font-mono">192.168.0.0/16</code>, and cloud metadata services like <code class="text-slate-400 text-xs font-mono">169.254.169.254</code>) are blocked instantly before socket connection.
|
|
1620
|
+
</p>
|
|
1621
|
+
</details>
|
|
1622
|
+
|
|
1623
|
+
<!-- Q5 -->
|
|
1624
|
+
<details class="group rounded-2xl bg-slate-900/70 border border-slate-800 p-6 transition duration-200 open:border-indigo-500/50 open:bg-slate-900/90">
|
|
1625
|
+
<summary class="flex items-center justify-between font-bold text-white cursor-pointer list-none text-base">
|
|
1626
|
+
<span>Is there a free tier available for developers?</span>
|
|
1627
|
+
<span class="text-indigo-400 text-lg group-open:rotate-180 transition-transform duration-200">↓</span>
|
|
1628
|
+
</summary>
|
|
1629
|
+
<p class="text-slate-300 text-sm mt-3 leading-relaxed">
|
|
1630
|
+
Yes. Tailprint offers a <strong>Developer Free Sandbox</strong> granting 100 free monthly renders with zero credit card required. You can claim an instant API key directly from the homepage and start using it with cURL, Node.js, Python, or MCP immediately.
|
|
1631
|
+
</p>
|
|
1632
|
+
</details>
|
|
1633
|
+
</div>
|
|
1634
|
+
</div>
|
|
1635
|
+
</section>
|
|
1636
|
+
|
|
1637
|
+
<!-- Stripe Checkout Success Modal -->
|
|
1638
|
+
<div id="success-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-950/80 backdrop-blur-md">
|
|
1639
|
+
<div class="relative w-full max-w-xl rounded-2xl bg-slate-900 border border-emerald-500/40 p-8 shadow-2xl shadow-emerald-500/10 text-center">
|
|
1640
|
+
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-emerald-500/10 border border-emerald-500/30 flex items-center justify-center text-3xl">
|
|
1641
|
+
🎉
|
|
1642
|
+
</div>
|
|
1643
|
+
<h3 class="text-2xl font-black text-white">Payment Successful!</h3>
|
|
1644
|
+
<p class="text-slate-400 text-sm mt-1">Welcome to Tailprint. Your live API key is ready below:</p>
|
|
1645
|
+
|
|
1646
|
+
<div class="mt-6 p-4 rounded-xl bg-slate-950 border border-slate-800 text-left">
|
|
1647
|
+
<div class="flex items-center justify-between mb-2">
|
|
1648
|
+
<span class="text-xs font-bold text-slate-400 uppercase tracking-wider">Your Live API Key</span>
|
|
1649
|
+
<button onclick="copyRevealedKey()" id="copy-revealed-key-btn" class="text-xs text-emerald-400 hover:text-emerald-300 font-bold flex items-center space-x-1">
|
|
1650
|
+
<span>📋 Copy Key</span>
|
|
1651
|
+
</button>
|
|
1652
|
+
</div>
|
|
1653
|
+
<div id="revealed-api-key" class="font-mono text-sm text-emerald-300 break-all bg-slate-900/80 p-3 rounded-lg border border-slate-800">
|
|
1654
|
+
Provisioning your API key...
|
|
1655
|
+
</div>
|
|
1656
|
+
</div>
|
|
1657
|
+
|
|
1658
|
+
<div class="mt-4 text-left">
|
|
1659
|
+
<span class="text-xs font-semibold text-slate-400">Quickstart cURL:</span>
|
|
1660
|
+
<pre id="revealed-curl" class="mt-1.5 p-3 rounded-xl bg-slate-950 text-xs font-mono text-slate-300 overflow-x-auto border border-slate-800">curl -X POST https://tailprint.com/v1/render/pdf \
|
|
1661
|
+
-H "Authorization: Bearer YOUR_KEY" \
|
|
1662
|
+
-H "Content-Type: application/json" \
|
|
1663
|
+
-d '{"html":"<h1 class=\"text-2xl font-bold\">Hello Tailprint</h1>"}' \
|
|
1664
|
+
--output test.pdf</pre>
|
|
1665
|
+
</div>
|
|
1666
|
+
|
|
1667
|
+
<div class="mt-6 flex justify-end">
|
|
1668
|
+
<button onclick="closeSuccessModal()" class="px-6 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-bold text-xs transition shadow-lg shadow-indigo-600/30">
|
|
1669
|
+
Start Building 🚀
|
|
1670
|
+
</button>
|
|
1671
|
+
</div>
|
|
1672
|
+
</div>
|
|
1673
|
+
</div>
|
|
1674
|
+
|
|
1675
|
+
<!-- Claim Free API Key Modal -->
|
|
1676
|
+
<div id="free-key-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-950/80 backdrop-blur-md">
|
|
1677
|
+
<div class="relative w-full max-w-md rounded-2xl bg-slate-900 border border-emerald-500/40 p-6 sm:p-8 shadow-2xl shadow-emerald-500/10">
|
|
1678
|
+
<button onclick="closeFreeKeyModal()" class="absolute top-4 right-4 text-slate-400 hover:text-white text-lg">✕</button>
|
|
1679
|
+
|
|
1680
|
+
<div id="free-key-form-view">
|
|
1681
|
+
<div class="w-12 h-12 rounded-2xl bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 flex items-center justify-center text-2xl mb-4">
|
|
1682
|
+
⚡
|
|
1683
|
+
</div>
|
|
1684
|
+
<h3 class="text-xl font-bold text-white">Claim 100 Free Renders / mo</h3>
|
|
1685
|
+
<p class="text-xs text-slate-400 mt-1">Instant developer sandbox key. No credit card required. Works with cURL, Node.js, Python, and MCP.</p>
|
|
1686
|
+
|
|
1687
|
+
<form onsubmit="handleClaimFreeKey(event)" class="mt-6 space-y-4">
|
|
1688
|
+
<div>
|
|
1689
|
+
<label class="block text-xs font-semibold text-slate-300 mb-1">Developer Email</label>
|
|
1690
|
+
<input id="free-key-email" type="email" required placeholder="alex@company.com" class="w-full px-3.5 py-2.5 rounded-xl bg-slate-950 border border-slate-800 text-white text-xs focus:outline-none focus:border-emerald-500" />
|
|
1691
|
+
</div>
|
|
1692
|
+
<div id="free-key-error" class="hidden text-xs text-rose-400 bg-rose-950/40 border border-rose-900/50 p-2.5 rounded-lg"></div>
|
|
1693
|
+
<button id="free-key-submit-btn" type="submit" class="w-full py-2.5 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs transition shadow-lg shadow-emerald-600/30 flex items-center justify-center space-x-2">
|
|
1694
|
+
<span>Claim 100 Free Renders ⚡</span>
|
|
1695
|
+
</button>
|
|
1696
|
+
</form>
|
|
1697
|
+
</div>
|
|
1698
|
+
|
|
1699
|
+
<div id="free-key-success-view" class="hidden text-left">
|
|
1700
|
+
<div class="w-12 h-12 rounded-2xl bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 flex items-center justify-center text-2xl mb-4">
|
|
1701
|
+
✓
|
|
1702
|
+
</div>
|
|
1703
|
+
<h3 class="text-xl font-bold text-white">Your Free API Key is Active!</h3>
|
|
1704
|
+
<p class="text-xs text-slate-400 mt-1">100 monthly renders unlocked. Use it immediately in your code or terminal:</p>
|
|
1705
|
+
|
|
1706
|
+
<div class="mt-4 p-3 rounded-xl bg-slate-950 border border-slate-800 font-mono text-xs text-emerald-400 break-all flex items-center justify-between">
|
|
1707
|
+
<span id="free-key-display"></span>
|
|
1708
|
+
<button onclick="copyFreeKey()" id="copy-free-key-btn" class="ml-2 px-2.5 py-1 bg-slate-800 hover:bg-slate-700 text-slate-200 text-[10px] rounded font-sans font-semibold">Copy</button>
|
|
1709
|
+
</div>
|
|
1710
|
+
|
|
1711
|
+
<div class="mt-4">
|
|
1712
|
+
<span class="text-xs font-semibold text-slate-400">cURL Quickstart:</span>
|
|
1713
|
+
<pre id="free-key-curl" class="mt-1.5 p-3 rounded-xl bg-slate-950 text-[10px] font-mono text-slate-300 overflow-x-auto border border-slate-800"></pre>
|
|
1714
|
+
</div>
|
|
1715
|
+
|
|
1716
|
+
<div class="mt-6 flex space-x-3">
|
|
1717
|
+
<a href="/dashboard" class="flex-1 py-2.5 rounded-xl bg-slate-800 hover:bg-slate-700 text-white text-xs font-bold text-center transition">
|
|
1718
|
+
Open Dashboard →
|
|
1719
|
+
</a>
|
|
1720
|
+
<button onclick="closeFreeKeyModal()" class="px-5 py-2.5 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-bold transition">
|
|
1721
|
+
Done
|
|
1722
|
+
</button>
|
|
1723
|
+
</div>
|
|
1724
|
+
</div>
|
|
1725
|
+
</div>
|
|
1726
|
+
</div>
|
|
1727
|
+
|
|
1728
|
+
<!-- Footer -->
|
|
1729
|
+
<footer class="py-12 border-t border-slate-900 bg-slate-950 text-center text-xs text-slate-500">
|
|
1730
|
+
<p>© 2026 Tailprint (tailprint.com). Built with Fastify, Playwright Chromium, Upstash Redis & Stripe.</p>
|
|
1731
|
+
</footer>
|
|
1732
|
+
|
|
1733
|
+
<!-- Script for Interactivity -->
|
|
1734
|
+
<script>
|
|
1735
|
+
// Interactive Avatar Mascot Easter Eggs & Tips
|
|
1736
|
+
const botTips = [
|
|
1737
|
+
"⚡ Ultra-fast rendering: Sub-500ms execution from Tailwind HTML to vector PDF!",
|
|
1738
|
+
"🤖 MCP Server Ready: Connect Claude Desktop, Cursor, or Windsurf directly via /v1/mcp/sse.",
|
|
1739
|
+
"🎨 100% Tailwind Native: Arbitrary utility classes like w-[340px] and grid-cols-12 supported out of the box.",
|
|
1740
|
+
"🔒 Enterprise Security: Automatic SSRF blocking and pre-warmed isolated Chromium sandboxes.",
|
|
1741
|
+
"🚀 Zero headless browser setup: Just send a POST request or prompt your AI agent.",
|
|
1742
|
+
"📄 Try the interactive live playground right below to test your markup ⬇️"
|
|
1743
|
+
];
|
|
1744
|
+
let tipIdx = 0;
|
|
1745
|
+
window.cycleAvatarTip = function() {
|
|
1746
|
+
tipIdx = (tipIdx + 1) % botTips.length;
|
|
1747
|
+
const el = document.getElementById('speech-text');
|
|
1748
|
+
const avatar = document.getElementById('hero-avatar');
|
|
1749
|
+
if (el) {
|
|
1750
|
+
el.style.opacity = '0';
|
|
1751
|
+
setTimeout(function() {
|
|
1752
|
+
el.innerText = botTips[tipIdx];
|
|
1753
|
+
el.style.opacity = '1';
|
|
1754
|
+
}, 150);
|
|
1755
|
+
}
|
|
1756
|
+
if (avatar) {
|
|
1757
|
+
avatar.classList.add('rotate-6', 'scale-110');
|
|
1758
|
+
setTimeout(function() {
|
|
1759
|
+
avatar.classList.remove('rotate-6', 'scale-110');
|
|
1760
|
+
}, 300);
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1763
|
+
|
|
1764
|
+
const templatesData = {
|
|
1765
|
+
${TEMPLATES.map((t) => `'${t.id}': ${JSON.stringify(t.html)}`).join(',\n ')}
|
|
1766
|
+
};
|
|
1767
|
+
|
|
1768
|
+
const templateMeta = {
|
|
1769
|
+
${TEMPLATES.map((t) => `'${t.id}': { landscape: ${t.landscape}, isImage: ${t.id === 'og-card'} }`).join(',\n ')}
|
|
1770
|
+
};
|
|
1771
|
+
|
|
1772
|
+
window.playgroundPresets = {
|
|
1773
|
+
invoice: templatesData['invoice'] || '',
|
|
1774
|
+
ticket: templatesData['ticket'] || '',
|
|
1775
|
+
receipt: templatesData['receipt'] || '',
|
|
1776
|
+
certificate: templatesData['certificate'] || '',
|
|
1777
|
+
badge: '<div class="max-w-xs mx-auto p-6 bg-gradient-to-br from-indigo-900 to-purple-950 text-white rounded-3xl shadow-2xl border border-indigo-500/30 text-center font-sans"><div class="w-16 h-16 mx-auto rounded-2xl bg-indigo-500 flex items-center justify-center text-3xl shadow-lg shadow-indigo-500/50">⚡</div><h3 class="text-xl font-black mt-4">Alex Morgan</h3><p class="text-xs text-indigo-300 font-semibold mt-0.5">Staff Platform Architect</p><div class="mt-6 p-2 rounded-xl bg-slate-950/60 font-mono text-[11px] text-slate-400 border border-slate-800">ACCESS TIER: VIP DEVELOPER</div></div>'
|
|
1778
|
+
};
|
|
1779
|
+
|
|
1780
|
+
window.currentPlaygroundMode = 'editor';
|
|
1781
|
+
window.currentSnippetLang = 'curl';
|
|
1782
|
+
|
|
1783
|
+
window.setPlaygroundMode = function(mode) {
|
|
1784
|
+
window.currentPlaygroundMode = mode;
|
|
1785
|
+
['editor', 'url', 'snippets'].forEach(function(m) {
|
|
1786
|
+
const btn = document.getElementById('mode-tab-' + m);
|
|
1787
|
+
const container = document.getElementById('container-mode-' + m);
|
|
1788
|
+
if (btn && container) {
|
|
1789
|
+
if (m === mode) {
|
|
1790
|
+
btn.className = 'px-2.5 py-1 rounded-lg font-bold bg-indigo-600 text-white transition';
|
|
1791
|
+
container.classList.remove('hidden');
|
|
1792
|
+
} else {
|
|
1793
|
+
btn.className = 'px-2.5 py-1 rounded-lg font-semibold text-slate-400 hover:text-white transition';
|
|
1794
|
+
container.classList.add('hidden');
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
});
|
|
1798
|
+
|
|
1799
|
+
const presetsRow = document.getElementById('playground-presets-row');
|
|
1800
|
+
if (presetsRow) {
|
|
1801
|
+
if (mode === 'editor') {
|
|
1802
|
+
presetsRow.classList.remove('hidden');
|
|
1803
|
+
} else {
|
|
1804
|
+
presetsRow.classList.add('hidden');
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
if (mode === 'snippets') {
|
|
1809
|
+
window.updateSnippetCode();
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
|
|
1813
|
+
window.setSnippetLang = function(lang) {
|
|
1814
|
+
window.currentSnippetLang = lang;
|
|
1815
|
+
['curl', 'node', 'python', 'php'].forEach(function(l) {
|
|
1816
|
+
const btn = document.getElementById('btn-lang-' + l);
|
|
1817
|
+
if (btn) {
|
|
1818
|
+
if (l === lang) {
|
|
1819
|
+
btn.className = 'px-2.5 py-1 rounded-lg font-bold bg-indigo-600 text-white';
|
|
1820
|
+
} else {
|
|
1821
|
+
btn.className = 'px-2.5 py-1 rounded-lg font-semibold bg-slate-800 text-slate-400 hover:text-white';
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
});
|
|
1825
|
+
window.updateSnippetCode();
|
|
1826
|
+
};
|
|
1827
|
+
|
|
1828
|
+
window.updateSnippetCode = function() {
|
|
1829
|
+
const pre = document.getElementById('playground-snippet-code');
|
|
1830
|
+
if (!pre) return;
|
|
1831
|
+
|
|
1832
|
+
const editor = document.getElementById('playground-editor');
|
|
1833
|
+
const urlInput = document.getElementById('playground-url-input');
|
|
1834
|
+
const formatSelect = document.getElementById('playground-format');
|
|
1835
|
+
const passwordInput = document.getElementById('playground-password');
|
|
1836
|
+
|
|
1837
|
+
const isUrl = window.currentPlaygroundMode === 'url';
|
|
1838
|
+
const format = formatSelect ? formatSelect.value : 'A4';
|
|
1839
|
+
const isLandscape = format.includes('landscape');
|
|
1840
|
+
const paperFormat = isLandscape ? 'A4' : format;
|
|
1841
|
+
const pass = passwordInput && passwordInput.value.trim() ? passwordInput.value.trim() : null;
|
|
1842
|
+
|
|
1843
|
+
const payloadObj = {
|
|
1844
|
+
format: paperFormat,
|
|
1845
|
+
landscape: isLandscape
|
|
1846
|
+
};
|
|
1847
|
+
|
|
1848
|
+
if (isUrl) {
|
|
1849
|
+
payloadObj.url = urlInput && urlInput.value ? urlInput.value.trim() : 'https://example.com';
|
|
1850
|
+
} else {
|
|
1851
|
+
payloadObj.html = editor && editor.value ? editor.value.slice(0, 80) + '...' : '<h1>Hello</h1>';
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
if (pass) {
|
|
1855
|
+
payloadObj.password = pass;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
const jsonStr = JSON.stringify(payloadObj, null, 2);
|
|
1859
|
+
const sq = String.fromCharCode(39);
|
|
1860
|
+
const bslash = String.fromCharCode(92);
|
|
1861
|
+
const nl = String.fromCharCode(10);
|
|
1862
|
+
let code = '';
|
|
1863
|
+
|
|
1864
|
+
if (window.currentSnippetLang === 'curl') {
|
|
1865
|
+
code = [
|
|
1866
|
+
'curl -X POST https://api.tailprint.com/v1/render/pdf ' + bslash,
|
|
1867
|
+
' -H "Authorization: Bearer sec_live_YOUR_API_KEY" ' + bslash,
|
|
1868
|
+
' -H "Content-Type: application/json" ' + bslash,
|
|
1869
|
+
' -d ' + sq + jsonStr + sq + ' ' + bslash,
|
|
1870
|
+
' --output output.pdf'
|
|
1871
|
+
].join(nl);
|
|
1872
|
+
} else if (window.currentSnippetLang === 'node') {
|
|
1873
|
+
code = [
|
|
1874
|
+
'import fs from "node:fs/promises";',
|
|
1875
|
+
'',
|
|
1876
|
+
'const response = await fetch("https://api.tailprint.com/v1/render/pdf", {',
|
|
1877
|
+
' method: "POST",',
|
|
1878
|
+
' headers: {',
|
|
1879
|
+
' "Authorization": "Bearer " + (process.env.TAILPRINT_API_KEY || "sec_live_YOUR_KEY"),',
|
|
1880
|
+
' "Content-Type": "application/json",',
|
|
1881
|
+
' },',
|
|
1882
|
+
' body: JSON.stringify(' + jsonStr + ', null, 2),',
|
|
1883
|
+
'});',
|
|
1884
|
+
'',
|
|
1885
|
+
'const buffer = Buffer.from(await response.arrayBuffer());',
|
|
1886
|
+
'await fs.writeFile("document.pdf", buffer);'
|
|
1887
|
+
].join(nl);
|
|
1888
|
+
} else if (window.currentSnippetLang === 'python') {
|
|
1889
|
+
code = [
|
|
1890
|
+
'import requests',
|
|
1891
|
+
'import os',
|
|
1892
|
+
'',
|
|
1893
|
+
'api_key = os.environ.get("TAILPRINT_API_KEY", "sec_live_YOUR_KEY")',
|
|
1894
|
+
'payload = ' + jsonStr.replace(/: true/g, ': True').replace(/: false/g, ': False'),
|
|
1895
|
+
'',
|
|
1896
|
+
'res = requests.post(',
|
|
1897
|
+
' "https://api.tailprint.com/v1/render/pdf",',
|
|
1898
|
+
' headers={',
|
|
1899
|
+
' "Authorization": f"Bearer {api_key}",',
|
|
1900
|
+
' "Content-Type": "application/json",',
|
|
1901
|
+
' },',
|
|
1902
|
+
' json=payload,',
|
|
1903
|
+
')',
|
|
1904
|
+
'',
|
|
1905
|
+
'with open("document.pdf", "wb") as f:',
|
|
1906
|
+
' f.write(res.content)'
|
|
1907
|
+
].join(nl);
|
|
1908
|
+
} else if (window.currentSnippetLang === 'php') {
|
|
1909
|
+
code = [
|
|
1910
|
+
'<?php',
|
|
1911
|
+
'$ch = curl_init("https://api.tailprint.com/v1/render/pdf");',
|
|
1912
|
+
'curl_setopt($ch, CURLOPT_HTTPHEADER, [',
|
|
1913
|
+
' "Authorization: Bearer " . getenv("TAILPRINT_API_KEY"),',
|
|
1914
|
+
' "Content-Type: application/json"',
|
|
1915
|
+
']);',
|
|
1916
|
+
'curl_setopt($ch, CURLOPT_POSTFIELDS, ' + sq + jsonStr + sq + ');',
|
|
1917
|
+
'curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);',
|
|
1918
|
+
'$pdf = curl_exec($ch);',
|
|
1919
|
+
'file_put_contents("document.pdf", $pdf);'
|
|
1920
|
+
].join(nl);
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
pre.innerText = code;
|
|
1924
|
+
};
|
|
1925
|
+
|
|
1926
|
+
window.copyCurrentSnippet = function() {
|
|
1927
|
+
const pre = document.getElementById('playground-snippet-code');
|
|
1928
|
+
const btn = document.getElementById('btn-copy-snippet');
|
|
1929
|
+
if (pre && btn) {
|
|
1930
|
+
navigator.clipboard.writeText(pre.innerText);
|
|
1931
|
+
const originalText = btn.innerHTML;
|
|
1932
|
+
btn.innerHTML = '<span>✅ Copied!</span>';
|
|
1933
|
+
setTimeout(function() {
|
|
1934
|
+
btn.innerHTML = originalText;
|
|
1935
|
+
}, 1800);
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
|
|
1939
|
+
window.loadPlaygroundPreset = function(presetName) {
|
|
1940
|
+
window.setPlaygroundMode('editor');
|
|
1941
|
+
const editor = document.getElementById('playground-editor');
|
|
1942
|
+
const formatSelect = document.getElementById('playground-format');
|
|
1943
|
+
if (editor && window.playgroundPresets[presetName]) {
|
|
1944
|
+
editor.value = window.playgroundPresets[presetName];
|
|
1945
|
+
}
|
|
1946
|
+
if (formatSelect) {
|
|
1947
|
+
if (presetName === 'ticket') {
|
|
1948
|
+
formatSelect.value = 'A4-landscape';
|
|
1949
|
+
} else {
|
|
1950
|
+
formatSelect.value = 'A4';
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
['invoice', 'ticket', 'receipt', 'certificate', 'badge'].forEach(function(p) {
|
|
1954
|
+
const btn = document.getElementById('btn-pre-' + p);
|
|
1955
|
+
if (btn) {
|
|
1956
|
+
if (p === presetName) {
|
|
1957
|
+
btn.className = 'px-2 py-1 rounded-lg text-[11px] font-semibold bg-indigo-600 text-white';
|
|
1958
|
+
} else {
|
|
1959
|
+
btn.className = 'px-2 py-1 rounded-lg text-[11px] font-semibold bg-slate-800 text-slate-400 hover:text-white';
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
});
|
|
1963
|
+
window.renderPlaygroundPdf();
|
|
1964
|
+
};
|
|
1965
|
+
|
|
1966
|
+
window.renderPlaygroundPdf = async function() {
|
|
1967
|
+
const editor = document.getElementById('playground-editor');
|
|
1968
|
+
const urlInput = document.getElementById('playground-url-input');
|
|
1969
|
+
const formatSelect = document.getElementById('playground-format');
|
|
1970
|
+
const headerFooterCb = document.getElementById('playground-header-footer');
|
|
1971
|
+
const passwordInput = document.getElementById('playground-password');
|
|
1972
|
+
const btn = document.getElementById('playground-render-btn');
|
|
1973
|
+
const loader = document.getElementById('playground-loading');
|
|
1974
|
+
const iframe = document.getElementById('playground-iframe');
|
|
1975
|
+
const timeBadge = document.getElementById('playground-time');
|
|
1976
|
+
const downloadBtn = document.getElementById('playground-download-btn');
|
|
1977
|
+
const statusBadge = document.getElementById('playground-status-badge');
|
|
1978
|
+
|
|
1979
|
+
const isUrlMode = window.currentPlaygroundMode === 'url';
|
|
1980
|
+
if (isUrlMode) {
|
|
1981
|
+
if (!urlInput || !urlInput.value.trim()) {
|
|
1982
|
+
alert('Please enter a target URL');
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
} else {
|
|
1986
|
+
if (!editor || !editor.value) return;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
const formatVal = formatSelect ? formatSelect.value : 'A4';
|
|
1990
|
+
const isLandscape = formatVal.includes('landscape');
|
|
1991
|
+
const paperFormat = isLandscape ? 'A4' : formatVal;
|
|
1992
|
+
|
|
1993
|
+
btn.disabled = true;
|
|
1994
|
+
btn.innerHTML = '<span>⏳ Rendering...</span>';
|
|
1995
|
+
loader.classList.remove('hidden');
|
|
1996
|
+
statusBadge.innerText = 'Rendering...';
|
|
1997
|
+
statusBadge.className = 'text-[10px] px-2 py-0.5 rounded-full bg-indigo-950 text-indigo-400 border border-indigo-500/30 font-bold';
|
|
1998
|
+
|
|
1999
|
+
const startTime = performance.now();
|
|
2000
|
+
|
|
2001
|
+
const payload = {
|
|
2002
|
+
format: paperFormat,
|
|
2003
|
+
landscape: isLandscape
|
|
2004
|
+
};
|
|
2005
|
+
|
|
2006
|
+
if (isUrlMode) {
|
|
2007
|
+
payload.url = urlInput.value.trim();
|
|
2008
|
+
} else {
|
|
2009
|
+
payload.html = editor.value;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
if (headerFooterCb && headerFooterCb.checked) {
|
|
2013
|
+
payload.header_html = '<div style="font-size: 8px; color: #64748b; width: 100%; display: flex; justify-content: space-between; padding: 0 15mm; border-bottom: 1px solid #e2e8f0; padding-bottom: 3px;"><span>Tailprint PDF Engine</span><span class="date"></span></div>';
|
|
2014
|
+
payload.footer_html = '<div style="font-size: 8px; color: #64748b; width: 100%; display: flex; justify-content: space-between; padding: 0 15mm; border-top: 1px solid #e2e8f0; padding-top: 3px;"><span>Confidential Document</span><span>Page <span class="pageNumber"></span> of <span class="totalPages"></span></span></div>';
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
if (passwordInput && passwordInput.value.trim()) {
|
|
2018
|
+
payload.password = passwordInput.value.trim();
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
try {
|
|
2022
|
+
const res = await fetch('/v1/render/playground', {
|
|
2023
|
+
method: 'POST',
|
|
2024
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2025
|
+
body: JSON.stringify(payload)
|
|
2026
|
+
});
|
|
2027
|
+
|
|
2028
|
+
if (!res.ok) {
|
|
2029
|
+
const errData = await res.json().catch(function() { return {}; });
|
|
2030
|
+
alert('Render error: ' + (errData.message || 'Failed to render PDF'));
|
|
2031
|
+
statusBadge.innerText = 'Error';
|
|
2032
|
+
statusBadge.className = 'text-[10px] px-2 py-0.5 rounded-full bg-rose-950 text-rose-400 border border-rose-500/30 font-bold';
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
const duration = Math.round(performance.now() - startTime);
|
|
2037
|
+
timeBadge.innerText = duration + 'ms';
|
|
2038
|
+
statusBadge.innerText = 'Rendered (' + duration + 'ms)' + (payload.password ? ' 🔒' : '');
|
|
2039
|
+
statusBadge.className = 'text-[10px] px-2 py-0.5 rounded-full bg-emerald-950 text-emerald-400 border border-emerald-500/30 font-bold';
|
|
2040
|
+
|
|
2041
|
+
const pdfBlob = await res.blob();
|
|
2042
|
+
const blobUrl = URL.createObjectURL(pdfBlob);
|
|
2043
|
+
iframe.src = blobUrl;
|
|
2044
|
+
downloadBtn.href = blobUrl;
|
|
2045
|
+
downloadBtn.classList.remove('hidden');
|
|
2046
|
+
} catch (err) {
|
|
2047
|
+
alert('Network error: ' + err.message);
|
|
2048
|
+
} finally {
|
|
2049
|
+
loader.classList.add('hidden');
|
|
2050
|
+
btn.disabled = false;
|
|
2051
|
+
btn.innerHTML = '<span>⚡ Render PDF</span>';
|
|
2052
|
+
}
|
|
2053
|
+
};
|
|
2054
|
+
|
|
2055
|
+
// Auto-initialize playground editor on page load
|
|
2056
|
+
setTimeout(function() {
|
|
2057
|
+
const editor = document.getElementById('playground-editor');
|
|
2058
|
+
if (editor && !editor.value) {
|
|
2059
|
+
editor.value = window.playgroundPresets.invoice;
|
|
2060
|
+
window.renderPlaygroundPdf();
|
|
2061
|
+
}
|
|
2062
|
+
}, 100);
|
|
2063
|
+
|
|
2064
|
+
window.runLiveScrape = async function() {
|
|
2065
|
+
const input = document.getElementById('scraper-input');
|
|
2066
|
+
const btn = document.getElementById('scraper-btn');
|
|
2067
|
+
const output = document.getElementById('scraper-output');
|
|
2068
|
+
const savings = document.getElementById('metric-savings');
|
|
2069
|
+
const tokens = document.getElementById('metric-tokens');
|
|
2070
|
+
const words = document.getElementById('metric-words');
|
|
2071
|
+
const cache = document.getElementById('metric-cache');
|
|
2072
|
+
|
|
2073
|
+
if (!input || !input.value) {
|
|
2074
|
+
alert('Please enter a valid URL');
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
btn.disabled = true;
|
|
2079
|
+
btn.innerText = 'Extracting...';
|
|
2080
|
+
|
|
2081
|
+
try {
|
|
2082
|
+
const res = await fetch('/v1/scrape/markdown', {
|
|
2083
|
+
method: 'POST',
|
|
2084
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2085
|
+
body: JSON.stringify({ url: input.value, strip_images: true })
|
|
2086
|
+
});
|
|
2087
|
+
const data = await res.json();
|
|
2088
|
+
if (data.markdown) {
|
|
2089
|
+
output.innerText = data.markdown;
|
|
2090
|
+
savings.innerText = data.token_savings_percent;
|
|
2091
|
+
tokens.innerText = data.estimated_tokens;
|
|
2092
|
+
words.innerText = data.word_count;
|
|
2093
|
+
cache.innerText = data.cached ? 'HIT (Redis)' : 'MISS (Fresh)';
|
|
2094
|
+
} else {
|
|
2095
|
+
alert('Error: ' + (data.message || 'Failed to scrape page'));
|
|
2096
|
+
}
|
|
2097
|
+
} catch (err) {
|
|
2098
|
+
alert('Request failed: ' + err.message);
|
|
2099
|
+
} finally {
|
|
2100
|
+
btn.disabled = false;
|
|
2101
|
+
btn.innerText = '⚡ Scrape to Markdown';
|
|
2102
|
+
}
|
|
2103
|
+
};
|
|
2104
|
+
|
|
2105
|
+
window.copyScrapedMarkdown = function() {
|
|
2106
|
+
const output = document.getElementById('scraper-output');
|
|
2107
|
+
if (output && output.innerText) {
|
|
2108
|
+
navigator.clipboard.writeText(output.innerText).then(() => {
|
|
2109
|
+
alert('Cleaned Markdown copied to clipboard!');
|
|
2110
|
+
});
|
|
2111
|
+
}
|
|
2112
|
+
};
|
|
2113
|
+
|
|
2114
|
+
window.switchTab = function(tabName) {
|
|
2115
|
+
document.querySelectorAll('.template-panel').forEach(function(p) {
|
|
2116
|
+
p.classList.add('hidden');
|
|
2117
|
+
});
|
|
2118
|
+
const activePanel = document.getElementById('tab-' + tabName);
|
|
2119
|
+
if (activePanel) {
|
|
2120
|
+
activePanel.classList.remove('hidden');
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
${JSON.stringify(TEMPLATES.map((t) => t.id))}.forEach(function(id) {
|
|
2124
|
+
const btn = document.getElementById('btn-' + id);
|
|
2125
|
+
if (btn) {
|
|
2126
|
+
if (id === tabName) {
|
|
2127
|
+
btn.className = 'px-4 py-2 rounded-xl font-bold text-xs bg-indigo-600 text-white shadow-md shadow-indigo-600/30 transition flex items-center space-x-2';
|
|
2128
|
+
} else {
|
|
2129
|
+
btn.className = 'px-4 py-2 rounded-xl font-bold text-xs bg-slate-900/90 border border-slate-800 text-slate-400 hover:text-white transition flex items-center space-x-2';
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
});
|
|
2133
|
+
};
|
|
2134
|
+
|
|
2135
|
+
window.copyHtml = function(key) {
|
|
2136
|
+
const html = templatesData[key];
|
|
2137
|
+
if (!html) return;
|
|
2138
|
+
navigator.clipboard.writeText(html).then(function() {
|
|
2139
|
+
alert('Tailwind HTML copied to clipboard!');
|
|
2140
|
+
}).catch(function(err) {
|
|
2141
|
+
console.error('Clipboard copy failed:', err);
|
|
2142
|
+
});
|
|
2143
|
+
};
|
|
2144
|
+
|
|
2145
|
+
window.copyCurl = function(key) {
|
|
2146
|
+
const html = templatesData[key];
|
|
2147
|
+
if (!html) return;
|
|
2148
|
+
const meta = templateMeta[key] || { landscape: false, isImage: false };
|
|
2149
|
+
const endpoint = meta.isImage ? '/v1/render/image' : '/v1/render/pdf';
|
|
2150
|
+
const outputExt = meta.isImage ? 'png' : 'pdf';
|
|
2151
|
+
|
|
2152
|
+
const payloadObj = meta.isImage
|
|
2153
|
+
? { width: 1200, height: 630, type: 'png', html: html }
|
|
2154
|
+
: { format: 'A4', landscape: meta.landscape, html: html };
|
|
2155
|
+
|
|
2156
|
+
const jsonStr = JSON.stringify(payloadObj);
|
|
2157
|
+
const cmd = 'curl -X POST ' + window.location.origin + endpoint + ' ' +
|
|
2158
|
+
'-H "Authorization: Bearer sec_live_dev_test_key_12345" ' +
|
|
2159
|
+
'-H "Content-Type: application/json" ' +
|
|
2160
|
+
'--data-raw ' + JSON.stringify(jsonStr) + ' ' +
|
|
2161
|
+
'--output ' + key + '.' + outputExt;
|
|
2162
|
+
|
|
2163
|
+
navigator.clipboard.writeText(cmd).then(function() {
|
|
2164
|
+
alert('cURL command copied to clipboard!');
|
|
2165
|
+
}).catch(function(err) {
|
|
2166
|
+
console.error('Clipboard copy failed:', err);
|
|
2167
|
+
});
|
|
2168
|
+
};
|
|
2169
|
+
|
|
2170
|
+
function getStoredApiKey() {
|
|
2171
|
+
return localStorage.getItem('tailprint_api_key') || 'sec_live_your_key_here';
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
function updateMcpKeyBanner() {
|
|
2175
|
+
const key = localStorage.getItem('tailprint_api_key');
|
|
2176
|
+
const dot = document.getElementById('mcp-key-dot');
|
|
2177
|
+
const text = document.getElementById('mcp-key-text');
|
|
2178
|
+
const action = document.getElementById('mcp-key-action-btn');
|
|
2179
|
+
if (!dot || !text || !action) return;
|
|
2180
|
+
|
|
2181
|
+
if (key && key.startsWith('sec_live_')) {
|
|
2182
|
+
dot.className = 'w-2 h-2 rounded-full bg-emerald-400';
|
|
2183
|
+
text.innerHTML = 'Using your active API key: <code class="text-emerald-300 font-mono">' + key.slice(0, 16) + '...</code>';
|
|
2184
|
+
action.outerHTML = '<a href="/dashboard" id="mcp-key-action-btn" class="text-indigo-400 hover:text-indigo-300 font-bold transition">View Dashboard →</a>';
|
|
2185
|
+
} else {
|
|
2186
|
+
dot.className = 'w-2 h-2 rounded-full bg-amber-400';
|
|
2187
|
+
text.innerText = 'Using placeholder key';
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
window.getMcpSnippets = function() {
|
|
2192
|
+
const apiKey = getStoredApiKey();
|
|
2193
|
+
return {
|
|
2194
|
+
claude: JSON.stringify({
|
|
2195
|
+
mcpServers: {
|
|
2196
|
+
tailprint: {
|
|
2197
|
+
command: "npx",
|
|
2198
|
+
args: ["-y", "tailprint"],
|
|
2199
|
+
env: {
|
|
2200
|
+
TAILPRINT_API_KEY: apiKey
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
}, null, 2),
|
|
2205
|
+
cursor: JSON.stringify({
|
|
2206
|
+
mcpServers: {
|
|
2207
|
+
tailprint: {
|
|
2208
|
+
url: window.location.origin + "/v1/mcp/sse?api_key=" + apiKey
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
}, null, 2),
|
|
2212
|
+
sse: "# Remote MCP via Server-Sent Events (SSE)\n" +
|
|
2213
|
+
"URL: " + window.location.origin + "/v1/mcp/sse?api_key=" + apiKey + "\n" +
|
|
2214
|
+
"POST Messages: " + window.location.origin + "/v1/mcp/messages\n\n" +
|
|
2215
|
+
"# Header Auth Option:\n" +
|
|
2216
|
+
"Authorization: Bearer " + apiKey
|
|
2217
|
+
};
|
|
2218
|
+
};
|
|
2219
|
+
|
|
2220
|
+
window.switchMcpTab = function(type) {
|
|
2221
|
+
const block = document.getElementById('mcp-code-block');
|
|
2222
|
+
const hint = document.getElementById('mcp-hint');
|
|
2223
|
+
const cursorBtn = document.getElementById('btn-cursor-deeplink');
|
|
2224
|
+
const tabs = ['claude', 'cursor', 'sse'];
|
|
2225
|
+
const snippets = window.getMcpSnippets();
|
|
2226
|
+
const apiKey = getStoredApiKey();
|
|
2227
|
+
|
|
2228
|
+
updateMcpKeyBanner();
|
|
2229
|
+
|
|
2230
|
+
tabs.forEach(function(t) {
|
|
2231
|
+
const btn = document.getElementById('btn-mcp-' + t);
|
|
2232
|
+
if (btn) {
|
|
2233
|
+
if (t === type) {
|
|
2234
|
+
btn.className = 'px-3 py-1.5 rounded-lg text-xs font-semibold bg-indigo-600 text-white';
|
|
2235
|
+
} else {
|
|
2236
|
+
btn.className = 'px-3 py-1.5 rounded-lg text-xs font-semibold bg-slate-800 text-slate-400 hover:text-white';
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
});
|
|
2240
|
+
|
|
2241
|
+
if (cursorBtn) {
|
|
2242
|
+
if (type === 'cursor') {
|
|
2243
|
+
cursorBtn.href = 'cursor://anysphere.cursor-mcp/install?name=tailprint&url=' + encodeURIComponent(window.location.origin + '/v1/mcp/sse?api_key=' + apiKey);
|
|
2244
|
+
cursorBtn.classList.remove('hidden');
|
|
2245
|
+
} else {
|
|
2246
|
+
cursorBtn.classList.add('hidden');
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
if (type === 'claude') {
|
|
2251
|
+
block.innerText = snippets.claude;
|
|
2252
|
+
hint.innerHTML = 'Add this snippet to <code class="text-indigo-300">~/Library/Application Support/Claude/claude_desktop_config.json</code> on macOS (or <code class="text-indigo-300">%APPDATA%\\Claude\\claude_desktop_config.json</code> on Windows).';
|
|
2253
|
+
} else if (type === 'cursor') {
|
|
2254
|
+
block.innerText = snippets.cursor;
|
|
2255
|
+
hint.innerHTML = 'Add this snippet to your project root under <code class="text-indigo-300">.cursor/mcp.json</code> or open Cursor Settings > MCP to add via Remote SSE.';
|
|
2256
|
+
} else if (type === 'sse') {
|
|
2257
|
+
block.innerText = snippets.sse;
|
|
2258
|
+
hint.innerHTML = 'Directly connect web agents, custom GPTs, LangChain, or remote LLM runtimes to this live endpoint.';
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
|
|
2262
|
+
window.copyMcpSnippet = function() {
|
|
2263
|
+
const block = document.getElementById('mcp-code-block');
|
|
2264
|
+
const btn = document.getElementById('mcp-copy-btn');
|
|
2265
|
+
if (!block) return;
|
|
2266
|
+
navigator.clipboard.writeText(block.innerText).then(function() {
|
|
2267
|
+
if (btn) {
|
|
2268
|
+
btn.innerHTML = '<span>✅ Copied!</span>';
|
|
2269
|
+
setTimeout(function() {
|
|
2270
|
+
btn.innerHTML = '<span>📋 Copy Config</span>';
|
|
2271
|
+
}, 2000);
|
|
2272
|
+
}
|
|
2273
|
+
});
|
|
2274
|
+
};
|
|
2275
|
+
|
|
2276
|
+
window.startCheckout = async function(planId) {
|
|
2277
|
+
try {
|
|
2278
|
+
const res = await fetch('/v1/billing/checkout', {
|
|
2279
|
+
method: 'POST',
|
|
2280
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2281
|
+
body: JSON.stringify({ planId: planId })
|
|
2282
|
+
});
|
|
2283
|
+
const data = await res.json();
|
|
2284
|
+
if (data.checkoutUrl) {
|
|
2285
|
+
window.location.href = data.checkoutUrl;
|
|
2286
|
+
} else {
|
|
2287
|
+
alert('Checkout session created: ' + data.sessionId);
|
|
2288
|
+
}
|
|
2289
|
+
} catch (err) {
|
|
2290
|
+
alert('Failed to initiate checkout: ' + err.message);
|
|
2291
|
+
}
|
|
2292
|
+
};
|
|
2293
|
+
|
|
2294
|
+
// Auto-detect Stripe Checkout return and reveal live API key
|
|
2295
|
+
(function checkCheckoutSuccess() {
|
|
2296
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
2297
|
+
const isSuccess = urlParams.get('checkout_success') === 'true';
|
|
2298
|
+
const sessionId = urlParams.get('session_id');
|
|
2299
|
+
|
|
2300
|
+
if (!isSuccess || !sessionId) return;
|
|
2301
|
+
|
|
2302
|
+
const modal = document.getElementById('success-modal');
|
|
2303
|
+
const keyEl = document.getElementById('revealed-api-key');
|
|
2304
|
+
const curlEl = document.getElementById('revealed-curl');
|
|
2305
|
+
if (modal) modal.classList.remove('hidden');
|
|
2306
|
+
|
|
2307
|
+
let attempts = 0;
|
|
2308
|
+
const maxAttempts = 15;
|
|
2309
|
+
|
|
2310
|
+
async function fetchKey() {
|
|
2311
|
+
try {
|
|
2312
|
+
const res = await fetch('/v1/billing/session-key?session_id=' + encodeURIComponent(sessionId));
|
|
2313
|
+
const data = await res.json();
|
|
2314
|
+
if (data.status === 'ready' && data.apiKey) {
|
|
2315
|
+
keyEl.innerText = data.apiKey;
|
|
2316
|
+
keyEl.className = 'font-mono text-sm text-emerald-400 break-all bg-emerald-950/40 p-3 rounded-lg border border-emerald-500/40 font-bold';
|
|
2317
|
+
const sq = String.fromCharCode(39);
|
|
2318
|
+
const demoPayload = '{"html":"<h1 class=\\"text-2xl font-bold text-indigo-600\\">Hello from Tailprint</h1>"}';
|
|
2319
|
+
curlEl.innerText = 'curl -X POST ' + window.location.origin + '/v1/render/pdf \\\n' +
|
|
2320
|
+
' -H "Authorization: Bearer ' + data.apiKey + '" \\\n' +
|
|
2321
|
+
' -H "Content-Type: application/json" \\\n' +
|
|
2322
|
+
' -d ' + sq + demoPayload + sq + ' \\\n' +
|
|
2323
|
+
' --output test.pdf';
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
} catch {}
|
|
2327
|
+
|
|
2328
|
+
attempts++;
|
|
2329
|
+
if (attempts < maxAttempts) {
|
|
2330
|
+
setTimeout(fetchKey, 1200);
|
|
2331
|
+
} else {
|
|
2332
|
+
keyEl.innerText = 'Key provisioned! Check your Stripe confirmation email.';
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
fetchKey();
|
|
2337
|
+
})();
|
|
2338
|
+
|
|
2339
|
+
window.copyRevealedKey = function() {
|
|
2340
|
+
const keyEl = document.getElementById('revealed-api-key');
|
|
2341
|
+
if (!keyEl || !keyEl.innerText) return;
|
|
2342
|
+
navigator.clipboard.writeText(keyEl.innerText).then(function() {
|
|
2343
|
+
const btn = document.getElementById('copy-revealed-key-btn');
|
|
2344
|
+
if (btn) btn.innerHTML = '<span>✅ Copied!</span>';
|
|
2345
|
+
setTimeout(function() { if (btn) btn.innerHTML = '<span>📋 Copy Key</span>'; }, 2000);
|
|
2346
|
+
});
|
|
2347
|
+
};
|
|
2348
|
+
|
|
2349
|
+
window.closeSuccessModal = function() {
|
|
2350
|
+
const modal = document.getElementById('success-modal');
|
|
2351
|
+
if (modal) modal.classList.add('hidden');
|
|
2352
|
+
window.history.replaceState({}, document.title, window.location.pathname);
|
|
2353
|
+
};
|
|
2354
|
+
|
|
2355
|
+
window.openFreeKeyModal = function() {
|
|
2356
|
+
const modal = document.getElementById('free-key-modal');
|
|
2357
|
+
const formView = document.getElementById('free-key-form-view');
|
|
2358
|
+
const successView = document.getElementById('free-key-success-view');
|
|
2359
|
+
const errEl = document.getElementById('free-key-error');
|
|
2360
|
+
if (modal) modal.classList.remove('hidden');
|
|
2361
|
+
if (formView) formView.classList.remove('hidden');
|
|
2362
|
+
if (successView) successView.classList.add('hidden');
|
|
2363
|
+
if (errEl) errEl.classList.add('hidden');
|
|
2364
|
+
};
|
|
2365
|
+
|
|
2366
|
+
window.closeFreeKeyModal = function() {
|
|
2367
|
+
const modal = document.getElementById('free-key-modal');
|
|
2368
|
+
if (modal) modal.classList.add('hidden');
|
|
2369
|
+
};
|
|
2370
|
+
|
|
2371
|
+
window.handleClaimFreeKey = async function(e) {
|
|
2372
|
+
e.preventDefault();
|
|
2373
|
+
const emailInput = document.getElementById('free-key-email');
|
|
2374
|
+
const btn = document.getElementById('free-key-submit-btn');
|
|
2375
|
+
const errEl = document.getElementById('free-key-error');
|
|
2376
|
+
const formView = document.getElementById('free-key-form-view');
|
|
2377
|
+
const successView = document.getElementById('free-key-success-view');
|
|
2378
|
+
const keyDisplay = document.getElementById('free-key-display');
|
|
2379
|
+
const curlDisplay = document.getElementById('free-key-curl');
|
|
2380
|
+
|
|
2381
|
+
if (!emailInput || !emailInput.value) return;
|
|
2382
|
+
|
|
2383
|
+
btn.disabled = true;
|
|
2384
|
+
btn.innerHTML = '<span>Provisioning Key...</span>';
|
|
2385
|
+
if (errEl) errEl.classList.add('hidden');
|
|
2386
|
+
|
|
2387
|
+
try {
|
|
2388
|
+
const res = await fetch('/v1/auth/claim-free-key', {
|
|
2389
|
+
method: 'POST',
|
|
2390
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2391
|
+
body: JSON.stringify({ email: emailInput.value.trim() }),
|
|
2392
|
+
});
|
|
2393
|
+
|
|
2394
|
+
const data = await res.json();
|
|
2395
|
+
if (!res.ok) {
|
|
2396
|
+
errEl.innerText = data.message || 'Failed to claim key. Please try again.';
|
|
2397
|
+
errEl.classList.remove('hidden');
|
|
2398
|
+
btn.disabled = false;
|
|
2399
|
+
btn.innerHTML = '<span>Claim 100 Free Renders ⚡</span>';
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
localStorage.setItem('tailprint_api_key', data.apiKey);
|
|
2404
|
+
keyDisplay.innerText = data.apiKey;
|
|
2405
|
+
if (typeof window.switchMcpTab === 'function') {
|
|
2406
|
+
window.switchMcpTab('claude');
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
const sq = String.fromCharCode(39);
|
|
2410
|
+
const demoPayload = '{"html":"<h1 class=\\"text-2xl font-bold text-indigo-600\\">Hello from Tailprint</h1>"}';
|
|
2411
|
+
curlDisplay.innerText = 'curl -X POST ' + window.location.origin + '/v1/render/pdf \\\n' +
|
|
2412
|
+
' -H "Authorization: Bearer ' + data.apiKey + '" \\\n' +
|
|
2413
|
+
' -H "Content-Type: application/json" \\\n' +
|
|
2414
|
+
' -d ' + sq + demoPayload + sq + ' \\\n' +
|
|
2415
|
+
' --output invoice.pdf';
|
|
2416
|
+
|
|
2417
|
+
formView.classList.add('hidden');
|
|
2418
|
+
successView.classList.remove('hidden');
|
|
2419
|
+
} catch (err) {
|
|
2420
|
+
errEl.innerText = 'Network error. Please try again.';
|
|
2421
|
+
errEl.classList.remove('hidden');
|
|
2422
|
+
btn.disabled = false;
|
|
2423
|
+
btn.innerHTML = '<span>Claim 100 Free Renders ⚡</span>';
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
|
|
2427
|
+
window.copyFreeKey = function() {
|
|
2428
|
+
const keyDisplay = document.getElementById('free-key-display');
|
|
2429
|
+
if (!keyDisplay || !keyDisplay.innerText) return;
|
|
2430
|
+
navigator.clipboard.writeText(keyDisplay.innerText).then(function() {
|
|
2431
|
+
const btn = document.getElementById('copy-free-key-btn');
|
|
2432
|
+
if (btn) btn.innerText = 'Copied!';
|
|
2433
|
+
setTimeout(function() { if (btn) btn.innerText = 'Copy'; }, 2000);
|
|
2434
|
+
});
|
|
2435
|
+
};
|
|
2436
|
+
|
|
2437
|
+
// Initialize default MCP config on page load
|
|
2438
|
+
setTimeout(function() {
|
|
2439
|
+
if (typeof window.switchMcpTab === 'function') {
|
|
2440
|
+
window.switchMcpTab('claude');
|
|
2441
|
+
}
|
|
2442
|
+
}, 100);
|
|
2443
|
+
</script>
|
|
2444
|
+
</body>
|
|
2445
|
+
</html>`;
|
|
2446
|
+
return reply.code(200).header('Content-Type', 'text/html; charset=utf-8').send(html);
|
|
2447
|
+
});
|
|
2448
|
+
};
|
|
2449
|
+
//# sourceMappingURL=templates.js.map
|