BUG-049: No invoice for Pro customers
This commit is contained in:
parent
0ab4afd398
commit
0abd81f024
13 changed files with 139 additions and 212 deletions
|
|
@ -15,6 +15,7 @@
|
|||
"CI/CD PIPELINE: Forgejo Actions workflow created. Needs 3 repository secrets added in Forgejo settings (SERVER_HOST, SERVER_USER, SSH_PRIVATE_KEY).",
|
||||
"REPRODUCIBLE INFRASTRUCTURE: DONE \u2014 setup.sh, docker-compose, configs, disaster recovery docs all in infrastructure/ directory.",
|
||||
"PRO PLAN LIMITS: DONE \u2014 Set to 2,500 PDFs/month at \u20ac9/mo. Competitive with html2pdf.app. Enforced in code, updated on landing page + JSON-LD + Stripe.",
|
||||
"BUG-049 HIGH: Pro customers do not receive an invoice after payment. This is legally required in Austria/EU. Stripe can auto-generate invoices for subscriptions — enable Stripe Invoicing or implement invoice generation. Customer must receive a proper invoice with: company name, ATU number, invoice number, date, amount, VAT breakdown.",
|
||||
"WEBSITE TEMPLATING: DONE \u2014 Build-time system with partials (nav/footer/styles). Source in public/src/, build with node scripts/build-html.cjs."
|
||||
],
|
||||
"launchChecklist": {
|
||||
|
|
@ -87,7 +88,7 @@
|
|||
},
|
||||
"openBugs": {
|
||||
"CRITICAL": [],
|
||||
"HIGH": [],
|
||||
"HIGH": ["BUG-049: No invoice sent to Pro customers after payment"],
|
||||
"MEDIUM": [],
|
||||
"LOW": [],
|
||||
"note": "All bugs (040-048) resolved as of Session 41. BUG-046 (usage data leak), BUG-047 (copy button), BUG-048 (change email) fixed."
|
||||
|
|
|
|||
|
|
@ -3,63 +3,13 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<title>Impressum — DocFast</title>
|
||||
<meta name="description" content="Legal notice and company information for DocFast API service.">
|
||||
<link rel="canonical" href="https://docfast.dev/impressum">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--bg: #0b0d11; --bg2: #12151c; --fg: #e4e7ed; --muted: #7a8194;
|
||||
--accent: #34d399; --accent-hover: #5eead4; --accent-glow: rgba(52,211,153,0.12);
|
||||
--accent2: #60a5fa; --card: #151922; --border: #1e2433;
|
||||
--radius: 12px; --radius-lg: 16px;
|
||||
}
|
||||
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--fg); line-height: 1.65; -webkit-font-smoothing: antialiased; }
|
||||
a { color: var(--accent); text-decoration: none; transition: color 0.2s; }
|
||||
a:hover { color: var(--accent-hover); }
|
||||
.container { max-width: 1020px; margin: 0 auto; padding: 0 24px; }
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
/* Nav */
|
||||
nav { padding: 20px 0; border-bottom: 1px solid var(--border); }
|
||||
nav .container { display: flex; align-items: center; justify-content: space-between; }
|
||||
.logo { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.5px; color: var(--fg); display: flex; align-items: center; gap: 8px; text-decoration: none; }
|
||||
.logo:hover { color: var(--fg); }
|
||||
.logo span { color: var(--accent); }
|
||||
.nav-links { display: flex; gap: 28px; align-items: center; }
|
||||
.nav-links a { color: var(--muted); font-size: 0.9rem; font-weight: 500; }
|
||||
.nav-links a:hover { color: var(--fg); }
|
||||
|
||||
/* Footer */
|
||||
footer { padding: 40px 0; border-top: 1px solid var(--border); }
|
||||
footer .container { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; }
|
||||
.footer-left { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links { display: flex; gap: 24px; flex-wrap: wrap; }
|
||||
.footer-links a { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links a:hover { color: var(--fg); }
|
||||
|
||||
/* Buttons */
|
||||
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 14px 28px; border-radius: 10px; font-size: 0.95rem; font-weight: 600; transition: all 0.2s; border: none; cursor: pointer; text-decoration: none; }
|
||||
.btn-primary { background: var(--accent); color: #0b0d11; }
|
||||
.btn-primary:hover { background: var(--accent-hover); text-decoration: none; transform: translateY(-1px); box-shadow: 0 8px 24px rgba(52,211,153,0.2); }
|
||||
.btn-secondary { border: 1px solid var(--border); color: var(--fg); background: transparent; }
|
||||
.btn-secondary:hover { border-color: var(--muted); text-decoration: none; background: rgba(255,255,255,0.03); }
|
||||
.btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
|
||||
.btn:focus-visible, a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
|
||||
/* Responsive base */
|
||||
@media (max-width: 640px) {
|
||||
.nav-links { gap: 16px; }
|
||||
.footer-links { gap: 16px; }
|
||||
footer .container { flex-direction: column; text-align: center; }
|
||||
}
|
||||
.container { max-width: 800px; margin: 0 auto; padding: 0 24px; }
|
||||
|
||||
/* Legal page overrides */
|
||||
.container { max-width: 800px; }
|
||||
/* Content */
|
||||
main { padding: 60px 0 80px; }
|
||||
h1 { font-size: 2.5rem; font-weight: 800; margin-bottom: 16px; letter-spacing: -1px; }
|
||||
h2 { font-size: 1.5rem; font-weight: 700; margin: 32px 0 16px; color: var(--accent); }
|
||||
|
|
@ -71,11 +21,22 @@ li { margin-bottom: 8px; line-height: 1.7; }
|
|||
.info { background: rgba(96,165,250,0.08); border: 1px solid rgba(96,165,250,0.15); border-radius: 8px; padding: 16px; margin: 24px 0; color: #60a5fa; font-size: 0.9rem; }
|
||||
.warning { background: rgba(251,191,36,0.08); border: 1px solid rgba(251,191,36,0.15); border-radius: 8px; padding: 16px; margin: 24px 0; color: #fbbf24; font-size: 0.9rem; }
|
||||
|
||||
/* Footer */
|
||||
footer { padding: 40px 0; border-top: 1px solid var(--border); }
|
||||
footer .container { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; }
|
||||
.footer-left { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links { display: flex; gap: 24px; flex-wrap: wrap; }
|
||||
.footer-links a { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links a:hover { color: var(--fg); }
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 640px) {
|
||||
main { padding: 40px 0 60px; }
|
||||
h1 { font-size: 2rem; }
|
||||
.footer-links { gap: 16px; }
|
||||
footer .container { flex-direction: column; text-align: center; }
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
|
|
|||
|
|
@ -280,6 +280,7 @@ html, body {
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
<main class="hero" role="main">
|
||||
<div class="container">
|
||||
<div class="badge">🚀 Simple PDF API for Developers</div>
|
||||
|
|
@ -415,7 +416,6 @@ html, body {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<footer aria-label="Footer">
|
||||
<div class="container">
|
||||
<div class="footer-left">© 2026 DocFast. Fast PDF generation for developers.</div>
|
||||
|
|
|
|||
|
|
@ -3,63 +3,36 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
|
||||
<title>Privacy Policy — DocFast</title>
|
||||
<meta name="description" content="Privacy policy for DocFast API service - GDPR compliant data protection information.">
|
||||
<link rel="canonical" href="https://docfast.dev/privacy">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--bg: #0b0d11; --bg2: #12151c; --fg: #e4e7ed; --muted: #7a8194;
|
||||
--accent: #34d399; --accent-hover: #5eead4; --accent-glow: rgba(52,211,153,0.12);
|
||||
--accent2: #60a5fa; --card: #151922; --border: #1e2433;
|
||||
--card: #151922; --border: #1e2433;
|
||||
--radius: 12px; --radius-lg: 16px;
|
||||
}
|
||||
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--fg); line-height: 1.65; -webkit-font-smoothing: antialiased; }
|
||||
a { color: var(--accent); text-decoration: none; transition: color 0.2s; }
|
||||
a:hover { color: var(--accent-hover); }
|
||||
.container { max-width: 1020px; margin: 0 auto; padding: 0 24px; }
|
||||
|
||||
/* Nav */
|
||||
nav { padding: 20px 0; border-bottom: 1px solid var(--border); }
|
||||
nav .container { display: flex; align-items: center; justify-content: space-between; }
|
||||
.logo { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.5px; color: var(--fg); display: flex; align-items: center; gap: 8px; text-decoration: none; }
|
||||
.logo:hover { color: var(--fg); }
|
||||
.logo { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.5px; color: var(--fg); display: flex; align-items: center; gap: 8px; }
|
||||
.logo span { color: var(--accent); }
|
||||
.nav-links { display: flex; gap: 28px; align-items: center; }
|
||||
.nav-links a { color: var(--muted); font-size: 0.9rem; font-weight: 500; }
|
||||
.nav-links a:hover { color: var(--fg); }
|
||||
|
||||
/* Footer */
|
||||
footer { padding: 40px 0; border-top: 1px solid var(--border); }
|
||||
footer .container { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; }
|
||||
.footer-left { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links { display: flex; gap: 24px; flex-wrap: wrap; }
|
||||
.footer-links a { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links a:hover { color: var(--fg); }
|
||||
|
||||
/* Buttons */
|
||||
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 14px 28px; border-radius: 10px; font-size: 0.95rem; font-weight: 600; transition: all 0.2s; border: none; cursor: pointer; text-decoration: none; }
|
||||
.btn-primary { background: var(--accent); color: #0b0d11; }
|
||||
.btn-primary:hover { background: var(--accent-hover); text-decoration: none; transform: translateY(-1px); box-shadow: 0 8px 24px rgba(52,211,153,0.2); }
|
||||
.btn-secondary { border: 1px solid var(--border); color: var(--fg); background: transparent; }
|
||||
.btn-secondary:hover { border-color: var(--muted); text-decoration: none; background: rgba(255,255,255,0.03); }
|
||||
.btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
|
||||
.btn:focus-visible, a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
|
||||
/* Responsive base */
|
||||
@media (max-width: 640px) {
|
||||
.nav-links { gap: 16px; }
|
||||
.footer-links { gap: 16px; }
|
||||
footer .container { flex-direction: column; text-align: center; }
|
||||
}
|
||||
.container { max-width: 800px; margin: 0 auto; padding: 0 24px; }
|
||||
|
||||
/* Legal page overrides */
|
||||
.container { max-width: 800px; }
|
||||
/* Content */
|
||||
main { padding: 60px 0 80px; }
|
||||
h1 { font-size: 2.5rem; font-weight: 800; margin-bottom: 16px; letter-spacing: -1px; }
|
||||
h2 { font-size: 1.5rem; font-weight: 700; margin: 32px 0 16px; color: var(--accent); }
|
||||
|
|
@ -71,10 +44,22 @@ li { margin-bottom: 8px; line-height: 1.7; }
|
|||
.info { background: rgba(96,165,250,0.08); border: 1px solid rgba(96,165,250,0.15); border-radius: 8px; padding: 16px; margin: 24px 0; color: #60a5fa; font-size: 0.9rem; }
|
||||
.warning { background: rgba(251,191,36,0.08); border: 1px solid rgba(251,191,36,0.15); border-radius: 8px; padding: 16px; margin: 24px 0; color: #fbbf24; font-size: 0.9rem; }
|
||||
|
||||
/* Footer */
|
||||
footer { padding: 40px 0; border-top: 1px solid var(--border); }
|
||||
footer .container { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; }
|
||||
.footer-left { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links { display: flex; gap: 24px; flex-wrap: wrap; }
|
||||
.footer-links a { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links a:hover { color: var(--fg); }
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 640px) {
|
||||
main { padding: 40px 0 60px; }
|
||||
h1 { font-size: 2rem; }
|
||||
.footer-links { gap: 16px; }
|
||||
footer .container { flex-direction: column; text-align: center; }
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -3,63 +3,36 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
|
||||
<title>Terms of Service — DocFast</title>
|
||||
<meta name="description" content="Terms of service for DocFast API - legal terms and conditions for using our PDF generation service.">
|
||||
<link rel="canonical" href="https://docfast.dev/terms">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--bg: #0b0d11; --bg2: #12151c; --fg: #e4e7ed; --muted: #7a8194;
|
||||
--accent: #34d399; --accent-hover: #5eead4; --accent-glow: rgba(52,211,153,0.12);
|
||||
--accent2: #60a5fa; --card: #151922; --border: #1e2433;
|
||||
--card: #151922; --border: #1e2433;
|
||||
--radius: 12px; --radius-lg: 16px;
|
||||
}
|
||||
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--fg); line-height: 1.65; -webkit-font-smoothing: antialiased; }
|
||||
a { color: var(--accent); text-decoration: none; transition: color 0.2s; }
|
||||
a:hover { color: var(--accent-hover); }
|
||||
.container { max-width: 1020px; margin: 0 auto; padding: 0 24px; }
|
||||
|
||||
/* Nav */
|
||||
nav { padding: 20px 0; border-bottom: 1px solid var(--border); }
|
||||
nav .container { display: flex; align-items: center; justify-content: space-between; }
|
||||
.logo { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.5px; color: var(--fg); display: flex; align-items: center; gap: 8px; text-decoration: none; }
|
||||
.logo:hover { color: var(--fg); }
|
||||
.logo { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.5px; color: var(--fg); display: flex; align-items: center; gap: 8px; }
|
||||
.logo span { color: var(--accent); }
|
||||
.nav-links { display: flex; gap: 28px; align-items: center; }
|
||||
.nav-links a { color: var(--muted); font-size: 0.9rem; font-weight: 500; }
|
||||
.nav-links a:hover { color: var(--fg); }
|
||||
|
||||
/* Footer */
|
||||
footer { padding: 40px 0; border-top: 1px solid var(--border); }
|
||||
footer .container { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; }
|
||||
.footer-left { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links { display: flex; gap: 24px; flex-wrap: wrap; }
|
||||
.footer-links a { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links a:hover { color: var(--fg); }
|
||||
|
||||
/* Buttons */
|
||||
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 14px 28px; border-radius: 10px; font-size: 0.95rem; font-weight: 600; transition: all 0.2s; border: none; cursor: pointer; text-decoration: none; }
|
||||
.btn-primary { background: var(--accent); color: #0b0d11; }
|
||||
.btn-primary:hover { background: var(--accent-hover); text-decoration: none; transform: translateY(-1px); box-shadow: 0 8px 24px rgba(52,211,153,0.2); }
|
||||
.btn-secondary { border: 1px solid var(--border); color: var(--fg); background: transparent; }
|
||||
.btn-secondary:hover { border-color: var(--muted); text-decoration: none; background: rgba(255,255,255,0.03); }
|
||||
.btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
|
||||
.btn:focus-visible, a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
|
||||
/* Responsive base */
|
||||
@media (max-width: 640px) {
|
||||
.nav-links { gap: 16px; }
|
||||
.footer-links { gap: 16px; }
|
||||
footer .container { flex-direction: column; text-align: center; }
|
||||
}
|
||||
.container { max-width: 800px; margin: 0 auto; padding: 0 24px; }
|
||||
|
||||
/* Legal page overrides */
|
||||
.container { max-width: 800px; }
|
||||
/* Content */
|
||||
main { padding: 60px 0 80px; }
|
||||
h1 { font-size: 2.5rem; font-weight: 800; margin-bottom: 16px; letter-spacing: -1px; }
|
||||
h2 { font-size: 1.5rem; font-weight: 700; margin: 32px 0 16px; color: var(--accent); }
|
||||
|
|
@ -71,10 +44,22 @@ li { margin-bottom: 8px; line-height: 1.7; }
|
|||
.info { background: rgba(96,165,250,0.08); border: 1px solid rgba(96,165,250,0.15); border-radius: 8px; padding: 16px; margin: 24px 0; color: #60a5fa; font-size: 0.9rem; }
|
||||
.warning { background: rgba(251,191,36,0.08); border: 1px solid rgba(251,191,36,0.15); border-radius: 8px; padding: 16px; margin: 24px 0; color: #fbbf24; font-size: 0.9rem; }
|
||||
|
||||
/* Footer */
|
||||
footer { padding: 40px 0; border-top: 1px solid var(--border); }
|
||||
footer .container { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; }
|
||||
.footer-left { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links { display: flex; gap: 24px; flex-wrap: wrap; }
|
||||
.footer-links a { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links a:hover { color: var(--fg); }
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 640px) {
|
||||
main { padding: 40px 0 60px; }
|
||||
h1 { font-size: 2rem; }
|
||||
.footer-links { gap: 16px; }
|
||||
footer .container { flex-direction: column; text-align: center; }
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@
|
|||
* - Reads page sources from templates/pages/*.html
|
||||
* - Reads partials from templates/partials/*.html
|
||||
* - Replaces {{> partial_name}} with partial content
|
||||
* - Supports {{title}} variable (set via <!-- title: Page Title --> comment at top)
|
||||
* - Supports <!-- key: value --> metadata comments at top of page files
|
||||
* - Replaces {{key}} variables with extracted metadata
|
||||
* - Writes output to public/
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync, readdirSync, mkdirSync } from 'node:fs';
|
||||
import { join, basename } from 'node:path';
|
||||
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
|
@ -37,13 +38,13 @@ console.log(`Processing ${pages.length} pages...`);
|
|||
for (const file of pages) {
|
||||
let content = readFileSync(join(PAGES_DIR, file), 'utf-8');
|
||||
|
||||
// Extract title from <!-- title: ... --> comment
|
||||
let title = '';
|
||||
const titleMatch = content.match(/^<!--\s*title:\s*(.+?)\s*-->/);
|
||||
if (titleMatch) {
|
||||
title = titleMatch[1];
|
||||
// Remove the title comment from output
|
||||
content = content.replace(/^<!--\s*title:.+?-->\n?/, '');
|
||||
// Extract all <!-- key: value --> metadata comments from the top
|
||||
const vars = {};
|
||||
while (true) {
|
||||
const m = content.match(/^<!--\s*([a-zA-Z_-]+):\s*(.+?)\s*-->\n?/);
|
||||
if (!m) break;
|
||||
vars[m[1]] = m[2];
|
||||
content = content.slice(m[0].length);
|
||||
}
|
||||
|
||||
// Replace {{> partial_name}} with partial content (support nested partials)
|
||||
|
|
@ -58,8 +59,12 @@ for (const file of pages) {
|
|||
});
|
||||
}
|
||||
|
||||
// Replace {{title}} variable
|
||||
content = content.replace(/\{\{title\}\}/g, title);
|
||||
// Replace {{variable}} with extracted metadata
|
||||
content = content.replace(/\{\{([a-zA-Z_-]+)\}\}/g, (match, key) => {
|
||||
if (key in vars) return vars[key];
|
||||
console.warn(` Warning: variable "${key}" not defined in ${file}`);
|
||||
return match;
|
||||
});
|
||||
|
||||
// Write output
|
||||
const outPath = join(OUTPUT_DIR, file);
|
||||
|
|
|
|||
|
|
@ -52,4 +52,4 @@
|
|||
{{> footer}}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{head}}
|
||||
<title>{{title}}</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DocFast — HTML & Markdown to PDF API</title>
|
||||
<meta name="description" content="Convert HTML and Markdown to beautiful PDFs with a simple API call. Built-in invoice templates. Fast, reliable, developer-friendly.">
|
||||
<meta property="og:title" content="DocFast — HTML & Markdown to PDF API">
|
||||
<meta property="og:description" content="Convert HTML and Markdown to beautiful PDFs with a simple API call. Fast, reliable, developer-friendly.">
|
||||
|
|
@ -14,11 +15,33 @@
|
|||
<meta name="twitter:title" content="DocFast — HTML & Markdown to PDF API">
|
||||
<meta name="twitter:description" content="Convert HTML and Markdown to beautiful PDFs with a simple API call.">
|
||||
<link rel="canonical" href="https://docfast.dev">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<script type="application/ld+json">
|
||||
{"@context":"https://schema.org","@type":"SoftwareApplication","name":"DocFast","url":"https://docfast.dev","applicationCategory":"DeveloperApplication","operatingSystem":"Web","description":"Convert HTML and Markdown to beautiful PDFs with a simple API call. Fast, reliable, developer-friendly.","offers":[{"@type":"Offer","price":"0","priceCurrency":"EUR","name":"Free","description":"100 PDFs/month"},{"@type":"Offer","price":"9","priceCurrency":"EUR","name":"Pro","description":"5,000 PDFs / month","billingIncrement":"P1M"}]}
|
||||
</script>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
||||
<style>
|
||||
{{styles_base}}
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--bg: #0b0d11; --bg2: #12151c; --fg: #e4e7ed; --muted: #7a8194;
|
||||
--accent: #34d399; --accent-hover: #5eead4; --accent-glow: rgba(52,211,153,0.12);
|
||||
--accent2: #60a5fa; --card: #151922; --border: #1e2433;
|
||||
--radius: 12px; --radius-lg: 16px;
|
||||
}
|
||||
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--fg); line-height: 1.65; -webkit-font-smoothing: antialiased; }
|
||||
a { color: var(--accent); text-decoration: none; transition: color 0.2s; }
|
||||
a:hover { color: var(--accent-hover); }
|
||||
.container { max-width: 1020px; margin: 0 auto; padding: 0 24px; }
|
||||
|
||||
/* Nav */
|
||||
nav { padding: 20px 0; border-bottom: 1px solid var(--border); }
|
||||
nav .container { display: flex; align-items: center; justify-content: space-between; }
|
||||
.logo { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.5px; color: var(--fg); display: flex; align-items: center; gap: 8px; }
|
||||
.logo span { color: var(--accent); }
|
||||
.nav-links { display: flex; gap: 28px; align-items: center; }
|
||||
.nav-links a { color: var(--muted); font-size: 0.9rem; font-weight: 500; }
|
||||
.nav-links a:hover { color: var(--fg); }
|
||||
|
||||
/* Hero */
|
||||
.hero { padding: 100px 0 80px; text-align: center; position: relative; }
|
||||
|
|
@ -28,6 +51,12 @@
|
|||
.hero h1 .gradient { background: linear-gradient(135deg, var(--accent) 0%, var(--accent2) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
|
||||
.hero p { font-size: 1.2rem; color: var(--muted); max-width: 560px; margin: 0 auto 40px; line-height: 1.7; }
|
||||
.hero-actions { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
|
||||
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 14px 28px; border-radius: 10px; font-size: 0.95rem; font-weight: 600; transition: all 0.2s; border: none; cursor: pointer; text-decoration: none; }
|
||||
.btn-primary { background: var(--accent); color: #0b0d11; }
|
||||
.btn-primary:hover { background: var(--accent-hover); text-decoration: none; transform: translateY(-1px); box-shadow: 0 8px 24px rgba(52,211,153,0.2); }
|
||||
.btn-secondary { border: 1px solid var(--border); color: var(--fg); background: transparent; }
|
||||
.btn-secondary:hover { border-color: var(--muted); text-decoration: none; background: rgba(255,255,255,0.03); }
|
||||
.btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
|
||||
|
||||
/* Code block */
|
||||
.code-section { margin: 56px auto 0; max-width: 660px; text-align: left; display: flex; flex-direction: column; }
|
||||
|
|
@ -93,6 +122,14 @@ section { position: relative; }
|
|||
.eu-icon { font-size: 2.5rem; }
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer { padding: 40px 0; border-top: 1px solid var(--border); }
|
||||
footer .container { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; }
|
||||
.footer-left { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links { display: flex; gap: 24px; flex-wrap: wrap; }
|
||||
.footer-links a { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links a:hover { color: var(--fg); }
|
||||
|
||||
/* Modal */
|
||||
.modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.75); backdrop-filter: blur(4px); z-index: 100; align-items: center; justify-content: center; }
|
||||
.modal-overlay.active { display: flex; }
|
||||
|
|
@ -120,23 +157,10 @@ section { position: relative; }
|
|||
.warning-box .icon { font-size: 1.1rem; flex-shrink: 0; }
|
||||
.signup-error { color: #f87171; font-size: 0.85rem; margin-bottom: 16px; display: none; padding: 10px 14px; background: rgba(248,113,113,0.06); border: 1px solid rgba(248,113,113,0.15); border-radius: 8px; }
|
||||
|
||||
/* Recovery modal states */
|
||||
#recoverInitial, #recoverLoading, #recoverVerify, #recoverResult { display: none; }
|
||||
#recoverInitial.active { display: block; }
|
||||
#recoverLoading.active { display: flex; flex-direction: column; align-items: center; padding: 40px 0; text-align: center; }
|
||||
#recoverResult.active { display: block; }
|
||||
#recoverVerify.active { display: block; }
|
||||
|
||||
/* Email change modal states */
|
||||
#emailChangeInitial, #emailChangeLoading, #emailChangeVerify, #emailChangeResult { display: none; }
|
||||
#emailChangeInitial.active { display: block; }
|
||||
#emailChangeLoading.active { display: flex; flex-direction: column; align-items: center; padding: 40px 0; text-align: center; }
|
||||
#emailChangeResult.active { display: block; }
|
||||
#emailChangeVerify.active { display: block; }
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 640px) {
|
||||
.hero { padding: 72px 0 56px; }
|
||||
.nav-links { gap: 16px; }
|
||||
.code-block {
|
||||
font-size: 0.75rem;
|
||||
padding: 18px 16px;
|
||||
|
|
@ -145,6 +169,8 @@ section { position: relative; }
|
|||
white-space: pre-wrap;
|
||||
}
|
||||
.trust-grid { gap: 24px; }
|
||||
.footer-links { gap: 16px; }
|
||||
footer .container { flex-direction: column; text-align: center; }
|
||||
}
|
||||
|
||||
/* Fix mobile terminal gaps at 375px and smaller */
|
||||
|
|
@ -220,11 +246,31 @@ html, body {
|
|||
white-space: normal !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Recovery modal states */
|
||||
#recoverInitial, #recoverLoading, #recoverVerify, #recoverResult { display: none; }
|
||||
#recoverInitial.active { display: block; }
|
||||
#recoverLoading.active { display: flex; flex-direction: column; align-items: center; padding: 40px 0; text-align: center; }
|
||||
#recoverResult.active { display: block; }
|
||||
#recoverVerify.active { display: block; }
|
||||
|
||||
/* Email change modal states */
|
||||
#emailChangeInitial, #emailChangeLoading, #emailChangeVerify, #emailChangeResult { display: none; }
|
||||
#emailChangeInitial.active { display: block; }
|
||||
#emailChangeLoading.active { display: flex; flex-direction: column; align-items: center; padding: 40px 0; text-align: center; }
|
||||
#emailChangeResult.active { display: block; }
|
||||
#emailChangeVerify.active { display: block; }
|
||||
|
||||
/* Focus-visible for accessibility */
|
||||
.btn:focus-visible, a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
</style>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{{nav}}
|
||||
{{> nav-home}}
|
||||
|
||||
<main class="hero" role="main">
|
||||
<div class="container">
|
||||
|
|
@ -361,7 +407,7 @@ html, body {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
{{footer}}
|
||||
{{> footer}}
|
||||
|
||||
<!-- Signup Modal -->
|
||||
<div class="modal-overlay" id="signupModal" role="dialog" aria-label="Sign up for API key">
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
<title>{{title}}</title>
|
||||
<meta name="description" content="Terms of service for DocFast API - legal terms and conditions for using our PDF generation service.">
|
||||
<link rel="canonical" href="https://docfast.dev/terms">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
{{> styles-base}}
|
||||
.container { max-width: 800px; margin: 0 auto; padding: 0 24px; }
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<nav aria-label="Main navigation">
|
||||
<div class="container">
|
||||
<div class="logo">⚡ Doc<span>Fast</span></div>
|
||||
<div class="nav-links">
|
||||
<a href="#features">Features</a>
|
||||
<a href="#pricing">Pricing</a>
|
||||
<a href="/docs">Docs</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--bg: #0b0d11; --bg2: #12151c; --fg: #e4e7ed; --muted: #7a8194;
|
||||
--accent: #34d399; --accent-hover: #5eead4; --accent-glow: rgba(52,211,153,0.12);
|
||||
--card: #151922; --border: #1e2433;
|
||||
--radius: 12px; --radius-lg: 16px;
|
||||
}
|
||||
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--fg); line-height: 1.65; -webkit-font-smoothing: antialiased; }
|
||||
a { color: var(--accent); text-decoration: none; transition: color 0.2s; }
|
||||
a:hover { color: var(--accent-hover); }
|
||||
|
||||
/* Nav */
|
||||
nav { padding: 20px 0; border-bottom: 1px solid var(--border); }
|
||||
nav .container { display: flex; align-items: center; justify-content: space-between; }
|
||||
.logo { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.5px; color: var(--fg); display: flex; align-items: center; gap: 8px; }
|
||||
.logo span { color: var(--accent); }
|
||||
.nav-links { display: flex; gap: 28px; align-items: center; }
|
||||
.nav-links a { color: var(--muted); font-size: 0.9rem; font-weight: 500; }
|
||||
.nav-links a:hover { color: var(--fg); }
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
/* Content */
|
||||
main { padding: 60px 0 80px; }
|
||||
h1 { font-size: 2.5rem; font-weight: 800; margin-bottom: 16px; letter-spacing: -1px; }
|
||||
h2 { font-size: 1.5rem; font-weight: 700; margin: 32px 0 16px; color: var(--accent); }
|
||||
h3 { font-size: 1.2rem; font-weight: 600; margin: 24px 0 12px; }
|
||||
p { margin-bottom: 16px; line-height: 1.7; }
|
||||
ul { margin-bottom: 16px; padding-left: 24px; }
|
||||
li { margin-bottom: 8px; line-height: 1.7; }
|
||||
.highlight { background: rgba(52,211,153,0.08); border: 1px solid rgba(52,211,153,0.15); border-radius: 8px; padding: 16px; margin: 24px 0; color: var(--accent); font-size: 0.9rem; }
|
||||
.info { background: rgba(96,165,250,0.08); border: 1px solid rgba(96,165,250,0.15); border-radius: 8px; padding: 16px; margin: 24px 0; color: #60a5fa; font-size: 0.9rem; }
|
||||
.warning { background: rgba(251,191,36,0.08); border: 1px solid rgba(251,191,36,0.15); border-radius: 8px; padding: 16px; margin: 24px 0; color: #fbbf24; font-size: 0.9rem; }
|
||||
|
||||
/* Footer */
|
||||
footer { padding: 40px 0; border-top: 1px solid var(--border); }
|
||||
footer .container { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; }
|
||||
.footer-left { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links { display: flex; gap: 24px; flex-wrap: wrap; }
|
||||
.footer-links a { color: var(--muted); font-size: 0.85rem; }
|
||||
.footer-links a:hover { color: var(--fg); }
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 640px) {
|
||||
main { padding: 40px 0 60px; }
|
||||
h1 { font-size: 2rem; }
|
||||
.footer-links { gap: 16px; }
|
||||
footer .container { flex-direction: column; text-align: center; }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue