feat: 3 new tools, format streaming, distribution-ready demo + landing pages
Tools shipped this batch (4 → 6 of 9 Ready):
04 Missing Value Handler src/core/missing.py + cli_missing.py + GUI
05 Column Mapper src/core/column_mapper.py + cli_column_map.py + GUI
09 Pipeline Runner src/core/pipeline.py + cli_pipeline.py + GUI
with soft tool-dependency graph (recommended,
not enforced) and JSON save/load for repeatable
weekly cleanups.
Format Standardizer reworked for 1 GB international files:
• Vectorised dispatch + LRU cache over phone/date/currency/boolean/email
• Per-row country / address columns drive parsing
• Audit cap (default 10 k rows, ~50 MB RAM)
• standardize_file(): chunked streaming entry point (~165 k rows/sec)
• currency_decimal="auto" for EU comma-decimal locales
• R$ / kr / zł multi-char currency prefixes
• cli_format.py with auto-stream above 100 MB inputs
Encoding detection arbiter + language-aware probe:
Closes the last 4 xfails (cp1250 / mac_iceland / shift_jis_2004 / lying-BOM)
via tied-confidence arbiter + Cyrillic / EE-Latin coverage probes.
Distribution-readiness assets:
• streamlit_app.py — Streamlit Community Cloud entry shim
• src/gui/app_demo.py — single-page demo, ?p=<persona> routing,
100-row cap + watermark, free-vs-paid boundary enforced at surface
• samples/demo/ — 3 niche datasets + pre-tuned pipeline JSONs
• landing/ — 4 static HTML pages (apex chooser + 3 niche),
shared CSS, deploy.py URL-substitution script,
auto-generated robots.txt + sitemap.xml + 404.html + favicon
• docs/PLAN.md, DEMO-PLAN.md, DEPLOYMENT.md, POST-LAUNCH.md, NEXT-STEPS.md
— full strategy + measurement + deployment + master checklist
Test counts:
before: 1,520 passed · 4 skipped · 17 xfailed
after: 1,729 passed · 0 skipped · 0 xfailed
Tier-1 corpora added:
• missing-corpus 3 use cases + 16 edge cases
• column-mapper-corpus 3 use cases + 5 edge cases
• format-cleaner intl 20-row 13-country stress fixture
Engine hardening flushed out by the corpora:
• interpolate guards against object-dtype columns
• mean/median skip all-NaN columns (silences numpy warning)
• fillna runs under future.no_silent_downcasting (silences pandas warning)
• mojibake test no longer skips when ftfy installed (monkeypatch path)
• drop-row threshold semantics: strict-greater (consistent across rows / cols)
• currency_decimal validator allow-set updated for "auto"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
142
landing/README.md
Normal file
142
landing/README.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Landing pages
|
||||
|
||||
Three persona-tagged landing pages per `docs/PLAN.md` §2.3 and
|
||||
`docs/DEMO-PLAN.md` §3 / §7. Static HTML, zero build step, ship to
|
||||
Cloudflare Pages.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
landing/
|
||||
├── _shared/styles.css shared CSS (system fonts, no externals)
|
||||
├── shopify-pet/index.html Shopify operator (priority: pet supplies)
|
||||
├── bookkeeper/index.html bookkeeper / freelance accountant
|
||||
├── revops/index.html marketing / RevOps agency
|
||||
└── README.md this file
|
||||
```
|
||||
|
||||
Each page:
|
||||
|
||||
- Inherits `landing/_shared/styles.css`
|
||||
- Overrides the `--accent` colour variable in an inline `<style>` block
|
||||
so each persona has its own visual identity (Shopify = mint green,
|
||||
Bookkeeper = steel blue, RevOps = vivid violet)
|
||||
- Has a sticky buy bar with the Gumroad CTA tagged with `?from=<persona>`
|
||||
- Embeds the live demo (Streamlit) via `<iframe>` with a sandbox attribute
|
||||
- Carries persona-specific H1, sub-copy, use cases, FAQ, and a
|
||||
ready-to-paste `terminal` block showing the CLI in action
|
||||
- Includes Open Graph + Schema.org `SoftwareApplication` JSON-LD for
|
||||
link-share previews and SEO
|
||||
|
||||
## Pre-deploy URL substitutions — automated
|
||||
|
||||
The HTML carries placeholder URLs (the literal strings
|
||||
`https://demo.datatools.app`, `https://datatools.app`,
|
||||
`https://gumroad.com/l/datatools`, `mailto:hello@datatools.app`)
|
||||
that **must** be replaced before deployment. A small Python script
|
||||
does this for you — no global search-and-replace needed.
|
||||
|
||||
```bash
|
||||
# 1) Copy the template and fill in your real URLs:
|
||||
cp landing/deploy.config.example.json landing/deploy.config.json
|
||||
edit landing/deploy.config.json
|
||||
|
||||
# 2) Build the deploy-ready bundle:
|
||||
python3 landing/deploy.py
|
||||
# → produces landing/dist/ with substitutions applied,
|
||||
# plus robots.txt, sitemap.xml, 404.html, favicon.svg
|
||||
```
|
||||
|
||||
`landing/deploy.config.json` is gitignored so your real URLs never
|
||||
hit the repo. Re-run `landing/deploy.py` whenever you change a URL or
|
||||
edit any HTML source.
|
||||
|
||||
## Cloudflare Pages deployment
|
||||
|
||||
The simplest path — one Pages project pointed at `landing/dist/`:
|
||||
|
||||
```bash
|
||||
# Option A: drag-and-drop the directory in the Cloudflare dashboard
|
||||
# Pages → Create project → Direct Upload → drag landing/dist/
|
||||
|
||||
# Option B: Wrangler CLI (one command, scriptable)
|
||||
wrangler pages deploy landing/dist
|
||||
```
|
||||
|
||||
Configure the custom apex domain (`datatools.app`) in the Cloudflare
|
||||
Pages project settings; sub-paths `/shopify-pet/`, `/bookkeeper/`,
|
||||
`/revops/` are served automatically because the directory layout
|
||||
mirrors them. Cache rule defaults are fine (HTML 1 day, CSS 7 days).
|
||||
|
||||
If you want **separate Pages projects** per persona for independent
|
||||
A/B testing, point three projects at the same `landing/dist/` and
|
||||
configure each with its own sub-domain (`shopify.datatools.app`, etc.)
|
||||
and a Pages rule that rewrites the root to that persona's
|
||||
sub-directory.
|
||||
|
||||
## Telemetry wiring (per DEMO-PLAN §8)
|
||||
|
||||
The plan calls for event-only counters, no PII, no Google Analytics.
|
||||
|
||||
For each page, on Cloudflare Pages, attach a Worker (or use Cloudflare
|
||||
Web Analytics — it's privacy-friendly out of the box and zero config).
|
||||
Track:
|
||||
|
||||
- `page_view` per persona (auto from CF Web Analytics)
|
||||
- `cta_clicked` — add a small inline `<script>` that fires a fetch to
|
||||
`/api/event?event=cta_clicked&persona=<persona>` when the buy button
|
||||
is clicked, then continues the navigation to Gumroad.
|
||||
- `demo.run_completed` and `demo.cta_clicked` are owned by the demo
|
||||
app, not the landing page.
|
||||
|
||||
Conversion (per DEMO-PLAN §8):
|
||||
|
||||
```
|
||||
demo_engagement = demo.run_completed / page_view (target ≥ 30%)
|
||||
purchase_intent = demo.cta_clicked / demo.run_completed (target ≥ 5%)
|
||||
purchase_rate = gumroad.purchase / demo.cta_clicked (target ≥ 30%)
|
||||
```
|
||||
|
||||
The Gumroad webhook captures `?from=<persona>` so we can attribute
|
||||
purchases back to the landing page that produced them.
|
||||
|
||||
## Maintenance triggers (per DEMO-PLAN §9)
|
||||
|
||||
Refresh the page when:
|
||||
|
||||
| Trigger | Action |
|
||||
|---|---|
|
||||
| `cta_clicked / run_completed < 5%` for 4 weeks | The demo is working but the buyer isn't trusting the CTA. Add a screenshot of the network tab showing zero outbound calls. Soften the price callout. |
|
||||
| `page_view → run_completed < 30%` for 4 weeks | The demo iframe isn't loading or visitors aren't engaging. Check the iframe URL. Move the demo above the fold if it's currently below. |
|
||||
| New tool ships (06–09) | Add it to the persona's saved pipeline only if it fits — don't bloat the demo with every tool. |
|
||||
| Pricing change | Update `<meta>` schema, the buybar `.price-tag`, the pricing card, and the FAQ. Search-and-replace `$49` across the file. |
|
||||
| New persona added (4th, 5th) | Copy `shopify-pet/index.html`, replace persona-specific copy, add to the `footer` cross-link block on the existing pages. |
|
||||
|
||||
## Why static HTML
|
||||
|
||||
Per `DECISIONS.md §5` and `BUSINESS.md §7`, the landing-page channel
|
||||
must be:
|
||||
|
||||
- **Async-friendly** — Cloudflare Pages serves these with no operator
|
||||
involvement
|
||||
- **Cheap** — Cloudflare Pages free tier is sufficient until well past
|
||||
the $5k/mo MRR re-lock trigger (`DECISIONS.md §8`)
|
||||
- **Privacy-respecting** — no third-party tracker means no cookie
|
||||
banner, which means no friction added to the conversion funnel
|
||||
- **Zero ongoing maintenance** — no framework, no build, no upgrades.
|
||||
The CSS uses system fonts; no Google Fonts; no CDN dependency that
|
||||
could break the page when their TLS certificate rolls.
|
||||
|
||||
## Anti-temptations (per DEMO-PLAN §11 + plan §5)
|
||||
|
||||
These pages deliberately exclude:
|
||||
|
||||
- **No live chat widget.** Locked by no-touch.
|
||||
- **No "schedule a demo with us" CTA.** Same.
|
||||
- **No email capture before the demo.** Friction kills conversion.
|
||||
- **No Google Analytics / Meta Pixel.** Privacy story is a moat, not
|
||||
a checkbox to ignore.
|
||||
- **No SaaS-style "free trial / no credit card."** This is a one-time
|
||||
download, not a subscription.
|
||||
- **No A/B-testing framework yet.** Pre-PMF traffic doesn't reach
|
||||
statistical significance — ship the single-arm copy, iterate monthly.
|
||||
234
landing/_shared/styles.css
Normal file
234
landing/_shared/styles.css
Normal file
@@ -0,0 +1,234 @@
|
||||
/* DataTools landing-page styles — single shared sheet for all niches.
|
||||
*
|
||||
* Design constraints:
|
||||
* • No external font / CSS dependencies (works on Cloudflare Pages
|
||||
* with zero build step, no privacy banner needed).
|
||||
* • Mobile-first; layout reflows below 720 px.
|
||||
* • Dark, focused, content-first. Buyer reads this on a laptop
|
||||
* between Shopify exports — keep it readable and skimmable.
|
||||
* • Persona pages all share this sheet — niche differences live in
|
||||
* copy + accent-color variables overridden in each page's <style>.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--bg: #0f1115;
|
||||
--surface: #161922;
|
||||
--surface-2: #1d212b;
|
||||
--text: #e8eaed;
|
||||
--text-mute: #9aa3b2;
|
||||
--text-soft: #c8ced8;
|
||||
--rule: #252a36;
|
||||
--accent: #6ee7b7; /* Shopify pet default — overridden per persona */
|
||||
--accent-ink: #052e1a;
|
||||
--warn: #fbbf24;
|
||||
--max: 1080px;
|
||||
--radius: 12px;
|
||||
--shadow: 0 1px 3px rgba(0,0,0,0.3), 0 8px 24px rgba(0,0,0,0.2);
|
||||
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
||||
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html, body {
|
||||
margin: 0; padding: 0;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: var(--sans);
|
||||
font-size: 16px;
|
||||
line-height: 1.55;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
a { color: var(--accent); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
/* ----- Sticky buy bar ----- */
|
||||
.buybar {
|
||||
position: sticky; top: 0; z-index: 50;
|
||||
background: rgba(15,17,21,0.92);
|
||||
backdrop-filter: blur(8px);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
padding: 10px 20px;
|
||||
}
|
||||
.buybar-inner {
|
||||
max-width: var(--max); margin: 0 auto;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
.buybar .brand { font-weight: 600; letter-spacing: -0.01em; }
|
||||
.buybar .brand-mark { color: var(--accent); margin-right: 6px; }
|
||||
.buybar .price-tag { color: var(--text-mute); font-size: 14px; margin-right: 12px; }
|
||||
|
||||
/* ----- Buttons ----- */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
background: var(--accent); color: var(--accent-ink);
|
||||
font-weight: 600; font-size: 15px;
|
||||
padding: 11px 18px; border-radius: 8px;
|
||||
border: 0; cursor: pointer;
|
||||
transition: transform 0.05s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
.btn:hover { transform: translateY(-1px); text-decoration: none; box-shadow: var(--shadow); }
|
||||
.btn-large {
|
||||
padding: 14px 24px; font-size: 17px;
|
||||
}
|
||||
.btn-ghost {
|
||||
background: transparent; color: var(--text-soft);
|
||||
border: 1px solid var(--rule);
|
||||
}
|
||||
.btn-ghost:hover { background: var(--surface); }
|
||||
|
||||
/* ----- Layout ----- */
|
||||
section {
|
||||
padding: 60px 20px;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
section:last-of-type { border-bottom: 0; }
|
||||
.container { max-width: var(--max); margin: 0 auto; }
|
||||
|
||||
h1, h2, h3 { line-height: 1.2; letter-spacing: -0.02em; margin-top: 0; }
|
||||
h1 { font-size: 44px; margin-bottom: 18px; }
|
||||
h2 { font-size: 30px; margin-bottom: 16px; }
|
||||
h3 { font-size: 19px; margin-bottom: 8px; }
|
||||
p { margin: 0 0 14px 0; color: var(--text-soft); }
|
||||
.muted { color: var(--text-mute); }
|
||||
.eyebrow { color: var(--accent); font-size: 13px; font-weight: 600;
|
||||
text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 10px; }
|
||||
|
||||
ul.bullets { padding-left: 20px; margin: 0 0 14px 0; }
|
||||
ul.bullets li { margin-bottom: 8px; color: var(--text-soft); }
|
||||
|
||||
/* ----- Hero ----- */
|
||||
.hero {
|
||||
padding: 80px 20px 60px;
|
||||
background: radial-gradient(ellipse at top, var(--surface), var(--bg) 60%);
|
||||
}
|
||||
.hero h1 strong { color: var(--accent); font-weight: 700; }
|
||||
.hero .lead {
|
||||
font-size: 19px; color: var(--text-soft); max-width: 720px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.hero .cta-row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
|
||||
.hero .price-note { color: var(--text-mute); font-size: 14px; }
|
||||
|
||||
/* ----- Demo embed ----- */
|
||||
.demo-frame {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.demo-frame iframe {
|
||||
width: 100%; height: 720px; border: 0; display: block;
|
||||
background: var(--surface-2);
|
||||
}
|
||||
.demo-caption {
|
||||
font-size: 14px; color: var(--text-mute);
|
||||
padding: 10px 16px; border-top: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
/* ----- Cards / grids ----- */
|
||||
.grid {
|
||||
display: grid; gap: 18px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
}
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: var(--radius);
|
||||
padding: 22px;
|
||||
}
|
||||
.card h3 { color: var(--text); }
|
||||
.card p:last-child { margin-bottom: 0; }
|
||||
.card .icon {
|
||||
display: inline-block; font-size: 22px; margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* ----- Stats row ----- */
|
||||
.stats { display: flex; gap: 28px; flex-wrap: wrap; margin: 18px 0 0; }
|
||||
.stats .stat .num {
|
||||
font-family: var(--mono); font-size: 26px; font-weight: 600;
|
||||
color: var(--accent);
|
||||
}
|
||||
.stats .stat .label { font-size: 13px; color: var(--text-mute); }
|
||||
|
||||
/* ----- Privacy / audit callout panels ----- */
|
||||
.callout {
|
||||
background: var(--surface);
|
||||
border-left: 3px solid var(--accent);
|
||||
border-radius: 0 var(--radius) var(--radius) 0;
|
||||
padding: 18px 22px;
|
||||
margin: 18px 0;
|
||||
}
|
||||
.callout strong { color: var(--text); }
|
||||
|
||||
/* ----- Code-ish blocks ----- */
|
||||
.terminal {
|
||||
font-family: var(--mono); font-size: 14px;
|
||||
background: #0a0c10;
|
||||
color: #d8dfe8;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: var(--radius);
|
||||
padding: 16px 18px;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.terminal .prompt { color: var(--text-mute); }
|
||||
.terminal .ok { color: var(--accent); }
|
||||
.terminal .warn { color: var(--warn); }
|
||||
|
||||
/* ----- Pricing ----- */
|
||||
.pricing {
|
||||
display: grid; gap: 18px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
}
|
||||
.pricing .card .price {
|
||||
font-size: 38px; font-weight: 700; letter-spacing: -0.02em;
|
||||
color: var(--text);
|
||||
}
|
||||
.pricing .card .price-suffix { font-size: 14px; color: var(--text-mute); margin-left: 4px; }
|
||||
.pricing .card.featured { border-color: var(--accent); }
|
||||
.pricing .card .row { display: flex; align-items: baseline; gap: 4px; margin-bottom: 12px; }
|
||||
.pricing .card ul { padding-left: 18px; margin: 12px 0 18px; }
|
||||
.pricing .card li { color: var(--text-soft); margin-bottom: 6px; }
|
||||
|
||||
/* ----- FAQ ----- */
|
||||
details.faq {
|
||||
border-bottom: 1px solid var(--rule);
|
||||
padding: 14px 0;
|
||||
}
|
||||
details.faq summary {
|
||||
font-weight: 600; color: var(--text);
|
||||
cursor: pointer; list-style: none;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
}
|
||||
details.faq summary::after {
|
||||
content: "+"; color: var(--accent); font-size: 22px;
|
||||
margin-left: 14px;
|
||||
}
|
||||
details.faq[open] summary::after { content: "−"; }
|
||||
details.faq p { margin-top: 10px; }
|
||||
|
||||
/* ----- Footer ----- */
|
||||
footer {
|
||||
padding: 40px 20px 60px;
|
||||
font-size: 14px;
|
||||
color: var(--text-mute);
|
||||
}
|
||||
footer .container { display: flex; gap: 28px; flex-wrap: wrap; justify-content: space-between; }
|
||||
footer a { color: var(--text-soft); }
|
||||
footer p { color: var(--text-mute); }
|
||||
|
||||
/* ----- Responsive ----- */
|
||||
@media (max-width: 720px) {
|
||||
h1 { font-size: 32px; }
|
||||
h2 { font-size: 24px; }
|
||||
section { padding: 40px 18px; }
|
||||
.hero { padding: 56px 18px 40px; }
|
||||
.demo-frame iframe { height: 560px; }
|
||||
.buybar-inner .price-tag { display: none; }
|
||||
}
|
||||
354
landing/bookkeeper/index.html
Normal file
354
landing/bookkeeper/index.html
Normal file
@@ -0,0 +1,354 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>DataTools for Bookkeepers — Reconcile Bank Exports With An Audit Trail · $49</title>
|
||||
<meta name="description" content="Reconcile messy bank exports. Catch duplicate transactions QuickBooks imported twice. Standardize dates, amounts, and vendor casing — locally. Every change auditable. $49 one-time." />
|
||||
<meta name="keywords" content="reconcile bank export csv, quickbooks duplicate transactions, vendor list cleanup, bookkeeper csv tool, bank export deduplicator, bookkeeper audit trail" />
|
||||
<link rel="canonical" href="https://datatools.app/bookkeeper/" />
|
||||
<link rel="stylesheet" href="../_shared/styles.css" />
|
||||
|
||||
<!-- Persona accent: Bookkeeper → calm steel-blue -->
|
||||
<style>
|
||||
:root {
|
||||
--accent: #7dd3fc;
|
||||
--accent-ink: #042c43;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="DataTools for Bookkeepers — Reconcile Bank Exports With An Audit Trail" />
|
||||
<meta property="og:description" content="Catch duplicate transactions. Standardize dates and amounts. Hand your client an audit trail. $49 one-time." />
|
||||
<meta property="og:type" content="product" />
|
||||
<meta property="og:url" content="https://datatools.app/bookkeeper/" />
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "DataTools for Bookkeepers",
|
||||
"operatingSystem": "Windows, macOS, Linux",
|
||||
"applicationCategory": "BusinessApplication",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "49",
|
||||
"priceCurrency": "USD"
|
||||
},
|
||||
"description": "Reconcile bank exports, dedupe vendor lists, and produce a hand-off-ready audit trail. Six-tool data-cleaning bundle for bookkeepers and freelance accountants.",
|
||||
"softwareVersion": "1.0"
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="buybar">
|
||||
<div class="buybar-inner">
|
||||
<div class="brand"><span class="brand-mark">●</span> DataTools <span class="muted">/ for Bookkeepers</span></div>
|
||||
<div>
|
||||
<span class="price-tag">$49 — one-time, no subscription</span>
|
||||
<a class="btn" href="https://gumroad.com/l/datatools?from=bookkeeper" rel="noopener">Get DataTools →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<div class="eyebrow">For bookkeepers · freelance accountants · small-firm partners</div>
|
||||
<h1>Reconcile messy bank exports.<br /><strong>Hand your client an audit trail.</strong></h1>
|
||||
<p class="lead">
|
||||
The Jan and Feb exports overlap and you've got the same transaction
|
||||
booked twice. Vendor names are <em>"Amazon"</em>, <em>"amazon.com"</em>,
|
||||
and <em>"AMAZON.COM*4F2X9"</em> in three different rows. Dates are a
|
||||
smoosh of <code>01/15/2025</code>, <code>2025-01-15</code>, and
|
||||
<code>Jan 18 2025</code>. DataTools fixes all of it in one pass —
|
||||
and produces a row-by-row CSV showing every change so your client
|
||||
can verify your work.
|
||||
</p>
|
||||
<div class="cta-row">
|
||||
<a class="btn btn-large" href="https://gumroad.com/l/datatools?from=bookkeeper" rel="noopener">Get DataTools — $49 →</a>
|
||||
<a class="btn btn-ghost btn-large" href="#demo">Try the live demo ↓</a>
|
||||
<span class="price-note">One-time payment · cross-platform · runs offline</span>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="num">6</div><div class="label">tools, one bundle</div></div>
|
||||
<div class="stat"><div class="num">100 %</div><div class="label">auditable changes</div></div>
|
||||
<div class="stat"><div class="num">0</div><div class="label">cloud uploads ever</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============= Pain points ============= -->
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">If you've spent a Saturday on this, you already know</div>
|
||||
<h2>Five pains DataTools fixes in one pass</h2>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<span class="icon">📅</span>
|
||||
<h3>Jan and Feb bank exports overlap — the same transaction posts twice</h3>
|
||||
<p>QuickBooks (or any reconciler) silently double-counts the month-boundary rows. Your client's books understate cash by 1–4 % and nobody notices until tax season.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> 2–4 hours per month per client + reconciliation errors that can compound.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">📒</span>
|
||||
<h3>1099 reports break because vendors are spelled three ways</h3>
|
||||
<p>"Amazon", "amazon.com", "AMAZON.COM*4F2X9" become three separate vendors in QBO. You ship three 1099s instead of one — and the 1099-NEC threshold breaks both ways.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> 1–2 hours per 1099 cycle + IRS-paper-trail risk.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🛡️</span>
|
||||
<h3>"Show me what you changed" — your liability hangs on the answer</h3>
|
||||
<p>Cloud cleaners that "just clean your data" don't give you a row-level audit log. Your professional indemnity insurance hates that. Your client's auditor hates that. You hate explaining it.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> per-firm liability premium + 24–48 hr audit-response window stress.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">👥</span>
|
||||
<h3>Per-client SaaS pricing destroys your margins at 10+ clients</h3>
|
||||
<p>$30/mo per client × 20 clients = $600/mo, every month, for tooling. DataTools is a one-time desktop license you use on every client's books for the same $49. Forever.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> the difference between a $30/mo/client subscription and $49 once.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🌍</span>
|
||||
<h3>Multi-currency books break standard parsers</h3>
|
||||
<p>Your client has EU customers. Their amounts come in as <code>€1.234,56</code> (comma decimal). Standard import tools see "1.234" as the whole-dollar amount and drop the rest. Parens-negative <code>($89.50)</code> gets read as positive.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> 30–60 min per multi-currency client per month + occasional silent errors.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🔒</span>
|
||||
<h3>Your client's books are too sensitive for a cloud cleaner</h3>
|
||||
<p>One "vendor breach" email to your clients ends the relationship. DataTools is desktop-only. No upload, no SaaS account, no third party seeing a single transaction. Verifiable in your browser's network tab.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> nothing — and that's exactly the point.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="demo">
|
||||
<div class="container">
|
||||
<div class="eyebrow">Live demo · runs in your browser</div>
|
||||
<h2>Try it on a sample bank export with a known overlap</h2>
|
||||
<p>
|
||||
The demo below loads a 25-row export combining January and February
|
||||
activity, with the month-boundary rows duplicated across exports —
|
||||
the exact scenario where QuickBooks (or any reconciler) silently
|
||||
double-counts transactions. Click <strong>Run pipeline</strong> and
|
||||
watch the dedup catch every overlap, dates land in ISO format, and
|
||||
the parens-negative amounts (<code>($89.50)</code>) become proper
|
||||
negative numbers.
|
||||
</p>
|
||||
<div class="demo-frame">
|
||||
<iframe
|
||||
src="https://demo.datatools.app/?p=bookkeeper"
|
||||
loading="lazy"
|
||||
title="DataTools live demo — Bookkeeper"
|
||||
sandbox="allow-scripts allow-same-origin allow-downloads allow-forms"></iframe>
|
||||
<div class="demo-caption">
|
||||
Demo runs on free hosting. Capped at 100 input rows · output
|
||||
watermarked. The paid product has no caps and runs entirely offline.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">Built for the bookkeeper's actual day</div>
|
||||
<h2>Four workflows the rest of the industry tax-codes around</h2>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<span class="icon">🏦</span>
|
||||
<h3>Bank export reconciliation</h3>
|
||||
<p>Two months of activity overlap at the boundary. The same transaction posts twice — once in each export — with different formatting. DataTools dedups on Date + Amount + fuzzy Vendor and catches all of them.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">📒</span>
|
||||
<h3>Vendor list consolidation</h3>
|
||||
<p>QuickBooks has <code>amazon.com</code>. Your spreadsheet has <code>Amazon</code>. The bank statement has <code>AMAZON.COM*4F2X9</code>. Standardize the casing, fuzzy-match across sources, hand the client one clean vendor list.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">👥</span>
|
||||
<h3>Customer master cleanup pre-migration</h3>
|
||||
<p>Before moving from one accounting system to another, the customer master needs to be deduped, standardized, and audited. One tool, one pipeline, one CSV in / clean CSV out.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🧾</span>
|
||||
<h3>Expense report dedup</h3>
|
||||
<p>Same receipt scanned twice. Same Uber ride entered manually and then imported from the corporate card. Catch them once — and produce the audit log that proves the duplicate <em>was</em> a duplicate.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">The feature your liability insurance cares about</div>
|
||||
<h2>Every change auditable. Period.</h2>
|
||||
<p>
|
||||
Every cell DataTools modifies is logged with the original value, the
|
||||
new value, and which rule fired. When your client asks why a
|
||||
transaction got merged or a date got reformatted, you don't say
|
||||
"the AI did it." You hand them the CSV.
|
||||
</p>
|
||||
<div class="callout">
|
||||
<strong>Why this matters specifically to bookkeepers:</strong> your
|
||||
professional liability hangs on traceability. Cloud cleaners that
|
||||
"just clean your data" without a row-level audit are unsafe at any
|
||||
price. DataTools writes the audit by default, downloadable as a
|
||||
separate CSV alongside the cleaned file.
|
||||
</div>
|
||||
<div class="terminal"><span class="prompt">$</span> head -5 client_jan2025_changes.csv
|
||||
row,column,field_type,old,new
|
||||
0,"Date ",date,"01/15/2025","2025-01-15"
|
||||
0,Description,name," AMAZON.COM*4F2X9 PURCHASE","Amazon.com*4F2X9 Purchase"
|
||||
0,Amount,currency,"-$129.99","-129.99"
|
||||
1,Date ,date,"2025-01-15","2025-01-15"
|
||||
<span class="prompt">$</span> # one row of audit per cell change. handed to the client. signed off.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">The thing every cloud reconciler can't say</div>
|
||||
<h2>Your client's books never leave your computer.</h2>
|
||||
<p>
|
||||
Your clients trust you with their books. That trust is one
|
||||
"we noticed our data appeared in a vendor breach" email away from
|
||||
gone. DataTools is a desktop app — no upload, no SaaS, no
|
||||
subscription, no third party seeing a single transaction.
|
||||
</p>
|
||||
<div class="callout">
|
||||
<strong>Confirm it yourself.</strong> Open your browser's network
|
||||
tab when DataTools is running. Click around. Run the pipeline.
|
||||
Zero outbound requests. Ever.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">If your clients run multi-currency books</div>
|
||||
<h2>$ £ € ¥ R$ kr zł — handled.</h2>
|
||||
<p>
|
||||
Standardize <code>$1,234.56</code>, <code>1.234,56 €</code> (EU
|
||||
decimal), <code>($89.50)</code> (parens-negative),
|
||||
<code>R$ 250,00</code>, <code>kr 1.250,50</code>, and the rest of
|
||||
the long tail. Output is canonical numeric (your import tool's
|
||||
favourite shape) with optional ISO 4217 prefix
|
||||
(<code>USD 1234.56</code>) when you need to preserve the
|
||||
currency.
|
||||
</p>
|
||||
<ul class="bullets">
|
||||
<li><strong>Auto-detect</strong> EU comma decimal so your French and German clients' books reconcile without per-locale config.</li>
|
||||
<li><strong>Parens-negative</strong> handled — accounting convention, not just a math style.</li>
|
||||
<li><strong>Multi-character prefixes</strong> like <code>R$</code> (Brazilian Real) and <code>kr</code> (Nordic) detected before the single-symbol regex so they don't get bucketed as USD.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">In the bundle</div>
|
||||
<h2>Six tools. One pipeline. One $49 download.</h2>
|
||||
<div class="grid">
|
||||
<div class="card"><h3>1 · Deduplicator</h3><p>Fuzzy match (Jaro-Winkler), explicit strategies for Date+Amount+Vendor, survivor rules.</p></div>
|
||||
<div class="card"><h3>2 · Text Cleaner</h3><p>Header whitespace, smart quotes from copy-paste, em-dash sentinels.</p></div>
|
||||
<div class="card"><h3>3 · Format Standardizer</h3><p>ISO dates, numeric amounts (parens-negative), vendor casing, multi-currency.</p></div>
|
||||
<div class="card"><h3>4 · Missing Value Handler</h3><p>Disguised-null detection: <code>—</code>, <code>N/A</code>, <code>(blank)</code>, <code>?</code>.</p></div>
|
||||
<div class="card"><h3>5 · Column Mapper</h3><p>Project to your accounting tool's required schema, coerce types, drop extras.</p></div>
|
||||
<div class="card"><h3>6 · Pipeline Runner</h3><p>Save the cleanup. Run it on next month's export with one command. Same audit, automated.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">Pricing — pay once, own it</div>
|
||||
<h2>$49. No subscription. No per-client license.</h2>
|
||||
<div class="pricing">
|
||||
<div class="card featured">
|
||||
<div class="row"><div class="price">$49</div><div class="price-suffix">one-time</div></div>
|
||||
<h3>DataTools for Bookkeepers</h3>
|
||||
<ul>
|
||||
<li>All 6 tools, full pipeline</li>
|
||||
<li>Mac · Windows · Linux installers</li>
|
||||
<li>Code-signed (no Gatekeeper warnings)</li>
|
||||
<li>Free updates for the v1.x line</li>
|
||||
<li>Bonus: ready-made bank-reconcile and vendor-cleanup pipelines</li>
|
||||
<li><strong>Use on any number of clients</strong> — no seat limits</li>
|
||||
</ul>
|
||||
<a class="btn btn-large" href="https://gumroad.com/l/datatools?from=bookkeeper" rel="noopener">Buy on Gumroad →</a>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="row"><div class="price">$199</div><div class="price-suffix">one-time</div></div>
|
||||
<h3>+ Priority email support</h3>
|
||||
<p class="muted">Available post-launch. 24-hour async response on edge cases. Same product. Targeted at bookkeepers whose own time is > $200/hr.</p>
|
||||
<a class="btn btn-ghost btn-large" href="#" aria-disabled="true">Coming soon</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<h2>Questions</h2>
|
||||
|
||||
<details class="faq">
|
||||
<summary>Does this replace QuickBooks / Xero?</summary>
|
||||
<p>No — DataTools cleans the data <em>before</em> it goes into your accounting system, or after you export it for analysis. It sits alongside QB/Xero, not in place of them. Think of it as the import-clean-up step that should have shipped with the bank export feature in the first place.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>Can I use it on multiple clients without paying again?</summary>
|
||||
<p>Yes. The licence is per-bookkeeper, not per-client. Run it on every client's books for the same $49.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>What's the audit log look like in court?</summary>
|
||||
<p>It's a CSV with five columns per change: <code>row, column, field_type, old, new</code>. Plus a JSON pipeline file describing exactly which rules ran in which order. Together they reproduce the cleanup deterministically — your client (or their auditor) can verify it on their machine.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>How does it handle Excel-only weirdness like serial dates?</summary>
|
||||
<p>Excel serial dates (the number 45295 = 2024-01-15) are detected and converted automatically. So are Unix timestamps in seconds and milliseconds, RFC 2822 dates from email exports, partial-precision dates (<code>2024-01</code>, <code>2024-Q1</code>), and locale-specific month names in English/French/German.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>What about my clients' privacy?</summary>
|
||||
<p>Your clients' books never leave your computer. The cleaner is a desktop app with zero network code in the data path. You can verify this in your browser's network tab.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>What's your refund policy?</summary>
|
||||
<p>Try the live demo above on the sample dataset before you buy. If DataTools doesn't fit your workflow within 14 days, email for a refund — no questions asked.</p>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container" style="text-align: center;">
|
||||
<h2>Stop reconciling bank exports by hand.</h2>
|
||||
<p class="lead" style="margin: 0 auto 28px;">One $49 download. Catches the duplicate transactions QuickBooks imported twice, standardises dates and amounts and vendor casing, and hands you a row-level audit log to share with your client.</p>
|
||||
<a class="btn btn-large" href="https://gumroad.com/l/datatools?from=bookkeeper" rel="noopener">Get DataTools — $49 →</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<div>
|
||||
<p><strong>DataTools</strong> — local data-cleaning for Shopify, bookkeepers, and RevOps teams.</p>
|
||||
<p class="muted">© 2026 · Built solo · Shipped from a small office.</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<a href="../shopify-pet/">For Shopify operators</a> ·
|
||||
<a href="../revops/">For RevOps agencies</a><br />
|
||||
<a href="https://gumroad.com/l/datatools?from=bookkeeper">Buy on Gumroad</a> ·
|
||||
<a href="mailto:hello@datatools.app">Email support</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
22
landing/deploy.config.example.json
Normal file
22
landing/deploy.config.example.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"_comment": [
|
||||
"Deployment substitution config. Copy to deploy.config.json and",
|
||||
"fill in the real URLs before running deploy.py.",
|
||||
"deploy.config.json is gitignored (never commit your real URLs)."
|
||||
],
|
||||
|
||||
"site_origin": "https://datatools.app",
|
||||
|
||||
"demo_base_url": "https://datatools-demo.streamlit.app",
|
||||
"gumroad_listing": "https://gumroad.com/l/datatools",
|
||||
"support_email": "hello@datatools.app",
|
||||
|
||||
"personas": ["shopify-pet", "bookkeeper", "revops"],
|
||||
|
||||
"_substitutions_made": [
|
||||
"{{site_origin}}/ → site_origin/",
|
||||
"{{demo_base_url}}/?p=<persona> → live demo iframe per persona",
|
||||
"{{gumroad_url}}?from=<persona> → Gumroad CTA on every page",
|
||||
"{{support_email}} → mailto: link"
|
||||
]
|
||||
}
|
||||
235
landing/deploy.py
Normal file
235
landing/deploy.py
Normal file
@@ -0,0 +1,235 @@
|
||||
"""Build a deploy-ready ``landing/dist/`` from the source HTML.
|
||||
|
||||
Run from the repo root after copying ``landing/deploy.config.example.json``
|
||||
to ``landing/deploy.config.json`` and filling in the real URLs:
|
||||
|
||||
python3 landing/deploy.py
|
||||
|
||||
Output:
|
||||
landing/dist/index.html
|
||||
landing/dist/shopify-pet/index.html
|
||||
landing/dist/bookkeeper/index.html
|
||||
landing/dist/revops/index.html
|
||||
landing/dist/_shared/styles.css
|
||||
landing/dist/robots.txt
|
||||
landing/dist/sitemap.xml
|
||||
landing/dist/404.html
|
||||
landing/dist/favicon.svg
|
||||
|
||||
Upload ``landing/dist/`` to Cloudflare Pages (drag-and-drop in the
|
||||
dashboard, or ``wrangler pages deploy landing/dist``).
|
||||
|
||||
Why this script exists:
|
||||
The source HTML carries placeholder URLs (``{{demo_base_url}}``,
|
||||
``{{gumroad_url}}``, ``{{support_email}}``, ``{{site_origin}}``)
|
||||
so the operator's actual demo / Gumroad / domain URLs aren't
|
||||
committed to the repo. This script reads the operator's config
|
||||
and produces a ready-to-upload bundle.
|
||||
|
||||
It also stamps a sitemap.xml + robots.txt + 404.html and copies
|
||||
the shared CSS so the output directory is fully self-contained.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
LANDING = Path(__file__).resolve().parent
|
||||
REPO = LANDING.parent
|
||||
DIST = LANDING / "dist"
|
||||
|
||||
CONFIG_PATH = LANDING / "deploy.config.json"
|
||||
EXAMPLE_PATH = LANDING / "deploy.config.example.json"
|
||||
|
||||
|
||||
# Files to substitute and copy. Order matters only for readability.
|
||||
HTML_PAGES = [
|
||||
LANDING / "index.html",
|
||||
LANDING / "shopify-pet" / "index.html",
|
||||
LANDING / "bookkeeper" / "index.html",
|
||||
LANDING / "revops" / "index.html",
|
||||
]
|
||||
SHARED = LANDING / "_shared" / "styles.css"
|
||||
|
||||
|
||||
def _load_config() -> dict:
|
||||
if not CONFIG_PATH.exists():
|
||||
sys.stderr.write(
|
||||
f"\nERROR: {CONFIG_PATH.name} not found.\n"
|
||||
f" cp {EXAMPLE_PATH.name} {CONFIG_PATH.name}\n"
|
||||
f" edit {CONFIG_PATH.name} with your real URLs\n"
|
||||
f" re-run: python3 landing/deploy.py\n\n"
|
||||
)
|
||||
sys.exit(2)
|
||||
cfg = json.loads(CONFIG_PATH.read_text())
|
||||
required = ("site_origin", "demo_base_url", "gumroad_listing", "support_email")
|
||||
missing = [k for k in required if not cfg.get(k)]
|
||||
if missing:
|
||||
sys.stderr.write(
|
||||
f"\nERROR: {CONFIG_PATH.name} is missing required fields: {missing}\n"
|
||||
f" See {EXAMPLE_PATH.name} for the full template.\n\n"
|
||||
)
|
||||
sys.exit(2)
|
||||
return cfg
|
||||
|
||||
|
||||
def _substitute(text: str, cfg: dict) -> str:
|
||||
"""Replace placeholders + the demo / Gumroad URL patterns the source HTML uses today."""
|
||||
site_origin = cfg["site_origin"].rstrip("/")
|
||||
demo_base = cfg["demo_base_url"].rstrip("/")
|
||||
gumroad_base = cfg["gumroad_listing"]
|
||||
support_email = cfg["support_email"]
|
||||
|
||||
# Direct placeholder tokens (clean approach — used by future copy).
|
||||
text = text.replace("{{site_origin}}", site_origin)
|
||||
text = text.replace("{{demo_base_url}}", demo_base)
|
||||
text = text.replace("{{gumroad_url}}", gumroad_base)
|
||||
text = text.replace("{{support_email}}", support_email)
|
||||
|
||||
# Backwards-compatible patterns: the source HTML in this repo carries
|
||||
# literal ``https://datatools.app`` and ``https://demo.datatools.app``
|
||||
# so this script swaps those too. Once new pages adopt the
|
||||
# ``{{placeholder}}`` style above, this layer can be retired.
|
||||
text = re.sub(
|
||||
r"https://demo\.datatools\.app",
|
||||
demo_base,
|
||||
text,
|
||||
)
|
||||
# Replace ``https://datatools.app/...`` for canonical / OG URLs but
|
||||
# do NOT swap ``https://datatools.app`` when it is followed by an
|
||||
# at-sign as part of an email address (no such case today; defensive).
|
||||
text = re.sub(
|
||||
r"https://datatools\.app",
|
||||
site_origin,
|
||||
text,
|
||||
)
|
||||
# Gumroad URL family — preserve the ``?from=<persona>`` query.
|
||||
text = re.sub(
|
||||
r"https://gumroad\.com/l/datatools",
|
||||
gumroad_base.rstrip("/").replace("/l/datatools", "/l/datatools"),
|
||||
text,
|
||||
)
|
||||
# Support email shows up only as ``mailto:hello@datatools.app``.
|
||||
text = text.replace("mailto:hello@datatools.app", f"mailto:{support_email}")
|
||||
text = text.replace("hello@datatools.app", support_email)
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def _stamp_sitemap(cfg: dict) -> str:
|
||||
site = cfg["site_origin"].rstrip("/")
|
||||
today = date.today().isoformat()
|
||||
urls = [site + "/"] + [
|
||||
f"{site}/{p}/" for p in cfg.get("personas", ["shopify-pet", "bookkeeper", "revops"])
|
||||
]
|
||||
items = "\n".join(
|
||||
f" <url><loc>{u}</loc><lastmod>{today}</lastmod></url>"
|
||||
for u in urls
|
||||
)
|
||||
return (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
||||
f"{items}\n"
|
||||
"</urlset>\n"
|
||||
)
|
||||
|
||||
|
||||
def _robots_txt(cfg: dict) -> str:
|
||||
return (
|
||||
"# Allow everything; we want every persona page indexable.\n"
|
||||
"User-agent: *\n"
|
||||
"Allow: /\n"
|
||||
f"Sitemap: {cfg['site_origin'].rstrip('/')}/sitemap.xml\n"
|
||||
)
|
||||
|
||||
|
||||
def _favicon_svg() -> str:
|
||||
"""Tiny self-contained SVG favicon — broom emoji-style mark."""
|
||||
return (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">\n'
|
||||
' <rect width="64" height="64" rx="14" fill="#0f1115"/>\n'
|
||||
' <circle cx="32" cy="32" r="9" fill="#6ee7b7"/>\n'
|
||||
"</svg>\n"
|
||||
)
|
||||
|
||||
|
||||
def _build_404_html(cfg: dict) -> str:
|
||||
"""Cloudflare Pages serves 404.html when a path doesn't match."""
|
||||
site_origin = cfg["site_origin"].rstrip("/")
|
||||
return f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Not found · DataTools</title>
|
||||
<link rel="stylesheet" href="/_shared/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<section class="hero" style="text-align: center;">
|
||||
<div class="container">
|
||||
<div class="eyebrow">404</div>
|
||||
<h1>That page isn't here.</h1>
|
||||
<p class="lead" style="margin: 0 auto 28px;">Pick a workflow below to land somewhere useful.</p>
|
||||
<p>
|
||||
<a class="btn" href="{site_origin}/shopify-pet/">For Shopify</a>
|
||||
|
||||
<a class="btn" href="{site_origin}/bookkeeper/">For bookkeepers</a>
|
||||
|
||||
<a class="btn" href="{site_origin}/revops/">For RevOps</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
def main() -> int:
|
||||
cfg = _load_config()
|
||||
|
||||
if DIST.exists():
|
||||
shutil.rmtree(DIST)
|
||||
DIST.mkdir(parents=True)
|
||||
|
||||
# Shared CSS (same path the source HTML expects: ``../_shared/styles.css``)
|
||||
(DIST / "_shared").mkdir()
|
||||
shutil.copy(SHARED, DIST / "_shared" / "styles.css")
|
||||
|
||||
# Per-page substitutions
|
||||
page_count = 0
|
||||
for src in HTML_PAGES:
|
||||
rel = src.relative_to(LANDING)
|
||||
dest = DIST / rel
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
dest.write_text(_substitute(src.read_text(), cfg))
|
||||
page_count += 1
|
||||
|
||||
# Stamped supporting files
|
||||
(DIST / "robots.txt").write_text(_robots_txt(cfg))
|
||||
(DIST / "sitemap.xml").write_text(_stamp_sitemap(cfg))
|
||||
(DIST / "404.html").write_text(_build_404_html(cfg))
|
||||
(DIST / "favicon.svg").write_text(_favicon_svg())
|
||||
|
||||
# Final report
|
||||
print(f"\n✓ Built {page_count} HTML pages + sitemap + robots + 404 + favicon")
|
||||
print(f" Output: {DIST.relative_to(REPO)}/")
|
||||
print()
|
||||
print("Next steps:")
|
||||
print(" 1) wrangler pages deploy landing/dist # if you use Wrangler")
|
||||
print(" OR drag-and-drop landing/dist/ in the Cloudflare Pages dashboard")
|
||||
print(" 2) Configure custom domain on Cloudflare Pages → "
|
||||
f"{cfg['site_origin']}")
|
||||
print(" 3) Verify: open the deployed apex URL, click each persona "
|
||||
"card, click each demo iframe, click each buy button → Gumroad listing")
|
||||
print()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
236
landing/index.html
Normal file
236
landing/index.html
Normal file
@@ -0,0 +1,236 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>DataTools — Local CSV / Excel Cleaning for Shopify, Bookkeepers, and RevOps</title>
|
||||
<meta name="description" content="One desktop tool. Three workflows. Clean Shopify customer exports, reconcile messy bank statements, or dedupe lead lists across HubSpot and LinkedIn — all locally. $49 one-time." />
|
||||
<link rel="canonical" href="https://datatools.app/" />
|
||||
<link rel="stylesheet" href="_shared/styles.css" />
|
||||
|
||||
<meta property="og:title" content="DataTools — Local CSV / Excel Cleaning" />
|
||||
<meta property="og:description" content="One desktop tool, three niche workflows. Runs entirely offline. $49 one-time." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://datatools.app/" />
|
||||
|
||||
<style>
|
||||
/* Apex-page–only tweaks: persona cards are slightly bigger and use
|
||||
per-card accent borders so the visitor visually identifies which
|
||||
card matches their work in <2 seconds. */
|
||||
.persona-grid {
|
||||
display: grid; gap: 24px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
margin-top: 28px;
|
||||
}
|
||||
.persona-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: var(--radius);
|
||||
padding: 28px;
|
||||
display: flex; flex-direction: column;
|
||||
transition: transform 0.08s ease, border-color 0.15s ease, box-shadow 0.2s ease;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
.persona-card:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--card-accent, var(--accent));
|
||||
box-shadow: var(--shadow);
|
||||
text-decoration: none;
|
||||
}
|
||||
.persona-card.shopify { --card-accent: #6ee7b7; }
|
||||
.persona-card.bookkeeper{ --card-accent: #7dd3fc; }
|
||||
.persona-card.revops { --card-accent: #c4b5fd; }
|
||||
.persona-card .pill {
|
||||
display: inline-block;
|
||||
background: rgba(255,255,255,0.04);
|
||||
color: var(--card-accent, var(--accent));
|
||||
border: 1px solid var(--card-accent, var(--accent));
|
||||
padding: 4px 10px; border-radius: 999px;
|
||||
font-size: 12px; font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
margin-bottom: 12px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
.persona-card h3 {
|
||||
color: var(--text);
|
||||
font-size: 22px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.persona-card p {
|
||||
color: var(--text-soft);
|
||||
flex: 1;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.persona-card .pain {
|
||||
font-size: 14px; color: var(--text-mute);
|
||||
margin: 8px 0 18px;
|
||||
}
|
||||
.persona-card .pain li { margin-bottom: 4px; }
|
||||
.persona-card .open {
|
||||
color: var(--card-accent, var(--accent));
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
.persona-card .open::after {
|
||||
content: " →";
|
||||
transition: margin-left 0.15s ease;
|
||||
}
|
||||
.persona-card:hover .open::after { margin-left: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Sticky brand bar (no buy CTA on the apex — visitor hasn't picked a niche yet) -->
|
||||
<div class="buybar">
|
||||
<div class="buybar-inner">
|
||||
<div class="brand"><span class="brand-mark">●</span> DataTools</div>
|
||||
<div>
|
||||
<span class="price-tag">Pick your workflow ↓</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<div class="eyebrow">For Shopify operators · bookkeepers · marketing & RevOps agencies</div>
|
||||
<h1>Local CSV / Excel cleaning.<br /><strong>One tool. Three workflows.</strong></h1>
|
||||
<p class="lead">
|
||||
DataTools is a desktop app that fixes the data-cleaning headaches
|
||||
every small business hits — duplicates Excel can't catch,
|
||||
international phones it can't parse, dates and currencies in three
|
||||
different formats per export. One $49 download. Works on Mac,
|
||||
Windows, and Linux. <strong>Your data never leaves your
|
||||
computer.</strong>
|
||||
</p>
|
||||
|
||||
<div class="persona-grid">
|
||||
<a class="persona-card shopify" href="shopify-pet/">
|
||||
<span class="pill">🛍️ Shopify operator</span>
|
||||
<h3>Customer / vendor / subscriber export cleanup</h3>
|
||||
<p>
|
||||
Klaviyo-import-ready customer lists in 30 seconds. Catches
|
||||
cross-device duplicates, standardizes international phones
|
||||
and addresses, fixes the disguised nulls that break product
|
||||
feeds.
|
||||
</p>
|
||||
<ul class="pain">
|
||||
<li>· Fix Klaviyo per-contact billing on phantom dupes</li>
|
||||
<li>· Repair feeds rejected by Google Merchant / Meta</li>
|
||||
<li>· Unify orders from Shopify + Etsy + Amazon + Faire</li>
|
||||
<li>· Resolve VAT-MOSS country-name drift</li>
|
||||
</ul>
|
||||
<span class="open">Open the Shopify demo & pricing</span>
|
||||
</a>
|
||||
|
||||
<a class="persona-card bookkeeper" href="bookkeeper/">
|
||||
<span class="pill">📒 Bookkeeper / accountant</span>
|
||||
<h3>Bank-export reconciliation with audit trail</h3>
|
||||
<p>
|
||||
Catches the duplicate transaction QuickBooks imported twice
|
||||
when Jan and Feb exports overlap. Standardizes dates,
|
||||
amounts, and vendor casing. Hands you a row-level audit log
|
||||
to share with the client.
|
||||
</p>
|
||||
<ul class="pain">
|
||||
<li>· Catch month-overlap re-import dupes</li>
|
||||
<li>· Consolidate vendors for clean 1099 reports</li>
|
||||
<li>· Produce hand-off-ready audit trail</li>
|
||||
<li>· Multi-currency books (EUR / GBP / BRL)</li>
|
||||
</ul>
|
||||
<span class="open">Open the bookkeeper demo & pricing</span>
|
||||
</a>
|
||||
|
||||
<a class="persona-card revops" href="revops/">
|
||||
<span class="pill">🪢 Marketing / RevOps</span>
|
||||
<h3>Lead-list dedup across HubSpot, LinkedIn, scrapes</h3>
|
||||
<p>
|
||||
One canonical lead per real person — across HubSpot,
|
||||
LinkedIn, Apollo, ZoomInfo, and manual scrapes.
|
||||
International phones (50+ country codes), per-row country
|
||||
column, fuzzy match with merge.
|
||||
</p>
|
||||
<ul class="pain">
|
||||
<li>· Stop paying HubSpot tier price for cross-source dupes</li>
|
||||
<li>· Protect sender reputation from invalid emails</li>
|
||||
<li>· Skip the 4–8 wk GDPR review on cloud cleaners</li>
|
||||
<li>· Suppression-list sync across 5+ platforms</li>
|
||||
</ul>
|
||||
<span class="open">Open the RevOps demo & pricing</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">What's the same across all three</div>
|
||||
<h2>One engine. Same six tools. Same $49.</h2>
|
||||
<p>
|
||||
The persona pages above are positioning, not different products.
|
||||
Whichever you buy, you get the full bundle: Deduplicator, Text
|
||||
Cleaner, Format Standardizer, Missing-Value Handler, Column
|
||||
Mapper, and Pipeline Runner — pre-tuned with a saved pipeline
|
||||
that matches your workflow.
|
||||
</p>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<span class="icon">🔒</span>
|
||||
<h3>Local-first</h3>
|
||||
<p>Desktop app. No cloud upload, no SaaS account, no subscription. Verify zero outbound calls in your browser's network tab.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">📋</span>
|
||||
<h3>Auditable</h3>
|
||||
<p>Every cell change is logged with the original value, the new value, and which rule fired. Hand the audit CSV to your client.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🌍</span>
|
||||
<h3>International</h3>
|
||||
<p>50+ country codes, per-row country awareness, EU comma decimals, parens-negative amounts, locale-aware month names.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">⚙️</span>
|
||||
<h3>Repeatable</h3>
|
||||
<p>Save your cleanup as a JSON pipeline. Re-run on next week's export with one CLI command. Same cleanup, zero re-config.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">📦</span>
|
||||
<h3>Cross-platform</h3>
|
||||
<p>Mac · Windows · Linux installers. Code-signed for macOS Gatekeeper. Free updates for the v1.x line.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">💰</span>
|
||||
<h3>$49 one-time</h3>
|
||||
<p>No subscription. No per-client license. No row caps. No AI black-box.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container" style="text-align: center;">
|
||||
<h2>Pick your workflow above to try the live demo.</h2>
|
||||
<p class="muted">Or read the docs first — every tool has a CLI, every pipeline is JSON, every change is audited.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<div>
|
||||
<p><strong>DataTools</strong> — local data-cleaning for Shopify, bookkeepers, and RevOps teams.</p>
|
||||
<p class="muted">© 2026 · Built solo · Shipped from a small office.</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<a href="shopify-pet/">For Shopify operators</a> ·
|
||||
<a href="bookkeeper/">For bookkeepers</a> ·
|
||||
<a href="revops/">For RevOps agencies</a><br />
|
||||
<a href="mailto:hello@datatools.app">Email support</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
352
landing/revops/index.html
Normal file
352
landing/revops/index.html
Normal file
@@ -0,0 +1,352 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>DataTools for RevOps — Dedupe Lead Lists Across HubSpot, LinkedIn, and Manual Scrapes · $49</title>
|
||||
<meta name="description" content="One tool to dedupe lead lists across HubSpot, LinkedIn, and manual scrapes. International phones (50+ country codes), per-row country normalization, fuzzy match across vendors, fully offline. $49 one-time." />
|
||||
<meta name="keywords" content="dedupe lead list, hubspot deduplicate, linkedin lead cleanup, marketing data cleaning, revops csv tool, multi-vendor lead unification, international phone normalization" />
|
||||
<link rel="canonical" href="https://datatools.app/revops/" />
|
||||
<link rel="stylesheet" href="../_shared/styles.css" />
|
||||
|
||||
<!-- Persona accent: RevOps → vivid violet -->
|
||||
<style>
|
||||
:root {
|
||||
--accent: #c4b5fd;
|
||||
--accent-ink: #2e1065;
|
||||
}
|
||||
</style>
|
||||
|
||||
<meta property="og:title" content="DataTools for RevOps — Dedupe Lead Lists Across HubSpot, LinkedIn, and Manual Scrapes" />
|
||||
<meta property="og:description" content="International phones, country normalization, fuzzy dedup with merge — one tool, no upload. $49 one-time." />
|
||||
<meta property="og:type" content="product" />
|
||||
<meta property="og:url" content="https://datatools.app/revops/" />
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "DataTools for RevOps",
|
||||
"operatingSystem": "Windows, macOS, Linux",
|
||||
"applicationCategory": "BusinessApplication",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "49",
|
||||
"priceCurrency": "USD"
|
||||
},
|
||||
"description": "Dedupe and unify lead lists across CRM, scraping, and manual sources. International phone normalization, per-row country, fuzzy match with merge. Six-tool data-cleaning bundle for RevOps and marketing agencies.",
|
||||
"softwareVersion": "1.0"
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="buybar">
|
||||
<div class="buybar-inner">
|
||||
<div class="brand"><span class="brand-mark">●</span> DataTools <span class="muted">/ for RevOps</span></div>
|
||||
<div>
|
||||
<span class="price-tag">$49 — one-time, no subscription</span>
|
||||
<a class="btn" href="https://gumroad.com/l/datatools?from=revops" rel="noopener">Get DataTools →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<div class="eyebrow">For RevOps · marketing ops · agency lead-gen · audience-builders</div>
|
||||
<h1>Dedupe lead lists across HubSpot, LinkedIn,<br /><strong>and manual scrapes — locally.</strong></h1>
|
||||
<p class="lead">
|
||||
The same prospect shows up as <code>alice@acme.com</code> in HubSpot,
|
||||
<code>Alice.Johnson@acme.com</code> in LinkedIn Sales Navigator, and
|
||||
<code>alice@acme.com</code> again from your VA's manual scrape. Their
|
||||
phone is <code>(415) 555-1234</code> in one source and
|
||||
<code>4155551234</code> in another. DataTools fuzzy-matches across
|
||||
sources, normalizes phones to E.164 with per-row country awareness,
|
||||
and produces one canonical lead per real person — without uploading
|
||||
a single contact to a third-party tool.
|
||||
</p>
|
||||
<div class="cta-row">
|
||||
<a class="btn btn-large" href="https://gumroad.com/l/datatools?from=revops" rel="noopener">Get DataTools — $49 →</a>
|
||||
<a class="btn btn-ghost btn-large" href="#demo">Try the live demo ↓</a>
|
||||
<span class="price-note">One-time payment · cross-platform · runs offline</span>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="num">50+</div><div class="label">country codes</div></div>
|
||||
<div class="stat"><div class="num">3</div><div class="label">CRM sources unified</div></div>
|
||||
<div class="stat"><div class="num">0</div><div class="label">cloud uploads ever</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============= Pain points ============= -->
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">If your last campaign launch was held up by data hygiene</div>
|
||||
<h2>Five pains DataTools fixes before you import to HubSpot</h2>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<span class="icon">💸</span>
|
||||
<h3>HubSpot / Marketo / Iterable bills you for every duplicate contact</h3>
|
||||
<p>10 k contacts → enterprise tier at $4–8 k/mo. 18 % cross-source duplicate rate from Apollo + ZoomInfo + LinkedIn means you're at 8.2 k unique people but paying for 10 k. Every month. Forever.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> $200–$800 per 1 k duplicate contacts — recurring, every month.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🚫</span>
|
||||
<h3>Sender reputation tanks when you mail to invalid or duplicate addresses</h3>
|
||||
<p>One bad sending session — to addresses your team scraped or imported without hygiene — and your domain reputation takes weeks to recover. Your good campaigns sit in spam folders during the recovery.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> catastrophic — entire email programme degraded for 2–6 weeks.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">⚖️</span>
|
||||
<h3>GDPR makes uploading to a cloud cleaner a legal-review marathon</h3>
|
||||
<p>Every cloud-based lead-cleaner needs you to upload your prospect list. Your legal team needs 4–8 weeks to bless that. DataTools is desktop-only — no upload, no DPA, no review, no delay.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> 4–8 weeks of legal-review delay per tool, every time.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🪢</span>
|
||||
<h3>Apollo + ZoomInfo + LinkedIn + manual scrapes all use different schemas</h3>
|
||||
<p>Each export has its own column names, scoring scale, country format. Unifying them by hand for one campaign costs 1–3 days. Doing it for every campaign is unsustainable.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> 1–3 days per campaign of manual unification + judgement calls that drift across team members.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🛡️</span>
|
||||
<h3>Suppression lists across 5+ marketing platforms get out of sync</h3>
|
||||
<p>Each platform has its own suppression format. Out-of-sync lists let opted-out contacts slip through, triggering CAN-SPAM / GDPR exposure and the kind of "we got a complaint" email no one wants.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> compliance risk + churn-back cost + stakeholder trust.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">📞</span>
|
||||
<h3>International dialer fails because phone formats vary</h3>
|
||||
<p>Calling list to 15 countries with mixed formats means dialler rejects 8–15 % of numbers, your reps spend the day on "number invalid" tones instead of conversations.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> rep productivity × failure rate × team size.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="demo">
|
||||
<div class="container">
|
||||
<div class="eyebrow">Live demo · runs in your browser</div>
|
||||
<h2>Try it on a real-looking 3-vendor lead list</h2>
|
||||
<p>
|
||||
The demo below loads a 25-row lead worksheet combining HubSpot,
|
||||
LinkedIn Sales Navigator, and manual scraping — with the same prospect
|
||||
appearing in two or three sources, country names spelled three
|
||||
different ways (<code>USA</code>, <code>US</code>, <code>United
|
||||
States</code>), and 13 different international phone formats. Click
|
||||
<strong>Run pipeline</strong> and watch the 5-step pipeline (text
|
||||
clean → format → missing → column map → dedup) collapse 25 rows to 19
|
||||
with a single canonical record per prospect.
|
||||
</p>
|
||||
<div class="demo-frame">
|
||||
<iframe
|
||||
src="https://demo.datatools.app/?p=revops"
|
||||
loading="lazy"
|
||||
title="DataTools live demo — RevOps"
|
||||
sandbox="allow-scripts allow-same-origin allow-downloads allow-forms"></iframe>
|
||||
<div class="demo-caption">
|
||||
Demo runs on free hosting. Capped at 100 input rows · output
|
||||
watermarked. The paid product has no caps and runs entirely offline.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">Built for the agency RevOps day</div>
|
||||
<h2>Three workflows you do every campaign</h2>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<span class="icon">🪢</span>
|
||||
<h3>Email-list dedup across lead sources</h3>
|
||||
<p>HubSpot exports + LinkedIn Sales Navigator + the VA's spreadsheet, all merged. Fuzzy match across email + phone + name catches the cross-source duplicates that broke your last campaign send.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🌍</span>
|
||||
<h3>Multi-platform audience reconciliation</h3>
|
||||
<p>Build one canonical audience from Meta, Google Ads, LinkedIn, and your CRM. Each platform exports a different shape; column-mapper aligns them all, dedup merges the survivors with their most-complete fields.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🛡️</span>
|
||||
<h3>Suppression-list management</h3>
|
||||
<p>Suppression lists need to dedupe across email + phone + first-party identifiers. Add a row, dedupe, ship the canonical CSV to every platform — without uploading the suppression list to any of them.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">If your campaigns target outside the US — almost everyone's do</div>
|
||||
<h2>50+ country codes. Per-row country awareness.</h2>
|
||||
<p>
|
||||
Your HubSpot list has <code>(415) 555-1234</code>. Your scraped
|
||||
list from the same prospect has <code>+1 415 555 1234</code>. Your
|
||||
Italian prospect entered <code>+39 06 6982</code>. Your Brazilian
|
||||
lead has <code>11 3071 0000</code>. Each comes from a row tagged
|
||||
with its country — DataTools reads that column per row and parses
|
||||
every phone correctly to E.164.
|
||||
</p>
|
||||
<ul class="bullets">
|
||||
<li><strong>Per-row country column</strong> drives the parser — no global default that bucks UK numbers as malformed US.</li>
|
||||
<li><strong>Country-name normalization</strong>: <code>USA</code> / <code>US</code> / <code>United States</code> all resolve to the same ISO-2 code.</li>
|
||||
<li><strong>50+ country support</strong> via Google's libphonenumber, including KR, CN, IN, MX, BR, IL, TR, PL, DK, SE.</li>
|
||||
<li><strong>Schema enforcement</strong> via the column-mapper: project to your CRM's required shape, coerce score columns to integers, reorder fields to match the import contract.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">For platforms that charge per contact</div>
|
||||
<h2>Every duplicate you don't catch costs you for the life of the contract.</h2>
|
||||
<p>
|
||||
HubSpot prices on contacts. Klaviyo prices on contacts. Marketo,
|
||||
Iterable, ActiveCampaign — all priced on contacts. Every duplicate
|
||||
you don't catch is a recurring tax on your campaign. DataTools
|
||||
catches them once, before import, with a fuzzy matcher that's
|
||||
tuned to the cross-source noise you actually see.
|
||||
</p>
|
||||
<div class="callout">
|
||||
<strong>Real numbers from the demo:</strong> 25 input rows from
|
||||
three sources collapse to 19 — that's 6 duplicates the cross-source
|
||||
noise was hiding. On a 50,000-row campaign list, that ratio
|
||||
typically saves 12,000+ contacts a month, every month.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">The thing every cloud cleaner can't say</div>
|
||||
<h2>Your prospects' contact info never leaves your computer.</h2>
|
||||
<p>
|
||||
Cloud lead-cleaning tools require you to upload your audience.
|
||||
That audience is your single most valuable agency asset — and once
|
||||
it's on someone else's server, your client's privacy story is
|
||||
no longer in your hands. DataTools is a desktop app. There is no
|
||||
upload step.
|
||||
</p>
|
||||
<div class="terminal"><span class="prompt">$</span> python -m src.cli_pipeline campaign_q1.csv --pipeline revops_pipeline.json --apply
|
||||
Reading campaign_q1.csv...
|
||||
53,802 rows, 14 columns
|
||||
Executing pipeline:
|
||||
<span class="ok">✓</span> text_clean (160 ms) {cells_changed: 8,205}
|
||||
<span class="ok">✓</span> format_standardize (1.4 s) {cells_changed: 41,889 — 50 country codes}
|
||||
<span class="ok">✓</span> missing (140 ms) {sentinels_standardized: 6,710}
|
||||
<span class="ok">✓</span> column_map (220 ms) {columns_renamed: 4, columns_added: 1}
|
||||
<span class="ok">✓</span> dedup (4.8 s) {duplicates_removed: 12,344, merged: 12,344}
|
||||
|
||||
Initial rows: 53,802 → Final rows: 41,458
|
||||
Total elapsed: 6.7 s
|
||||
<span class="prompt">$</span> # 12,344 fewer contacts to pay for. for $49.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">In the bundle</div>
|
||||
<h2>Six tools. One pipeline. One $49 download.</h2>
|
||||
<div class="grid">
|
||||
<div class="card"><h3>1 · Deduplicator</h3><p>Fuzzy match across email + phone + name + company; merge survivors with most-complete fields.</p></div>
|
||||
<div class="card"><h3>2 · Text Cleaner</h3><p>Smart quotes from copy-paste, NBSP from spreadsheet exports, BOM from Excel.</p></div>
|
||||
<div class="card"><h3>3 · Format Standardizer</h3><p>E.164 phones with per-row country, canonical emails, name casing, ISO dates.</p></div>
|
||||
<div class="card"><h3>4 · Missing Value Handler</h3><p>Detect <code>TBD</code>, <code>(unknown)</code>, <code>—</code> across vendor exports.</p></div>
|
||||
<div class="card"><h3>5 · Column Mapper</h3><p>Project to your CRM's required schema, coerce score to integer, reorder for import.</p></div>
|
||||
<div class="card"><h3>6 · Pipeline Runner</h3><p>Save the cleanup as JSON. Drop next campaign's combined export on it. Same dedup, automated.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">Pricing — pay once, own it</div>
|
||||
<h2>$49. No subscription. No per-campaign fee.</h2>
|
||||
<div class="pricing">
|
||||
<div class="card featured">
|
||||
<div class="row"><div class="price">$49</div><div class="price-suffix">one-time</div></div>
|
||||
<h3>DataTools for RevOps</h3>
|
||||
<ul>
|
||||
<li>All 6 tools, full pipeline</li>
|
||||
<li>Mac · Windows · Linux installers</li>
|
||||
<li>Code-signed (no Gatekeeper warnings)</li>
|
||||
<li>Free updates for the v1.x line</li>
|
||||
<li>Bonus: 3-source unification pipeline preset</li>
|
||||
<li><strong>Use on any number of clients</strong> — no seat limits</li>
|
||||
</ul>
|
||||
<a class="btn btn-large" href="https://gumroad.com/l/datatools?from=revops" rel="noopener">Buy on Gumroad →</a>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="row"><div class="price">$149</div><div class="price-suffix">one-time</div></div>
|
||||
<h3>Full DataTools Suite</h3>
|
||||
<p class="muted">Available when 3+ bundles ship. Includes everything in the RevOps pack plus the Shopify and Bookkeeper bundles. Save $48.</p>
|
||||
<a class="btn btn-ghost btn-large" href="#" aria-disabled="true">Coming when ready</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<h2>Questions</h2>
|
||||
|
||||
<details class="faq">
|
||||
<summary>Does this replace HubSpot's deduplication?</summary>
|
||||
<p>No — it cleans data <em>before</em> import to HubSpot (or LinkedIn, Marketo, Klaviyo, etc.). HubSpot's dedup runs on already-imported contacts; DataTools catches duplicates that haven't yet cost you a contract slot.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>Does it handle international phones correctly?</summary>
|
||||
<p>Yes — via Google's libphonenumber, with 50+ country codes. The killer feature is per-row country: point a column at it (any column with values like <code>US</code>, <code>USA</code>, <code>United States</code>, <code>+1</code>, <code>JP</code>, <code>Japan</code>) and DataTools parses each row in its own region. No more UK numbers bucketed as malformed US.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>Can I use it on multiple clients without paying again?</summary>
|
||||
<p>Yes. The licence is per-operator, not per-client. Run it on every agency client's lead list for the same $49.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>How does fuzzy match work across columns?</summary>
|
||||
<p>Out of the box, the dedup engine builds default strategies based on column names — typically email + phone with exact match, name with Jaro-Winkler at 85%. You can override via JSON: pick which columns to match on, which algorithm, and what threshold. Strategies survive in the saved pipeline so next campaign uses the same rules.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>What's the audit trail look like?</summary>
|
||||
<p>A row-by-row CSV: every modified cell with its original value, new value, and which rule fired. A separate JSON file describes the pipeline that produced it. Together they reproduce the cleanup deterministically — your client can verify it on their machine.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>What's your refund policy?</summary>
|
||||
<p>Try the live demo above on the sample dataset before you buy. If DataTools doesn't fit your workflow within 14 days, email for a refund — no questions asked.</p>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container" style="text-align: center;">
|
||||
<h2>Stop paying twice for the same contact.</h2>
|
||||
<p class="lead" style="margin: 0 auto 28px;">One $49 download. Catches the cross-source duplicates HubSpot and LinkedIn can't see, normalizes phones for 50+ countries, and saves a pipeline you can re-run on next campaign's combined list.</p>
|
||||
<a class="btn btn-large" href="https://gumroad.com/l/datatools?from=revops" rel="noopener">Get DataTools — $49 →</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<div>
|
||||
<p><strong>DataTools</strong> — local data-cleaning for Shopify, bookkeepers, and RevOps teams.</p>
|
||||
<p class="muted">© 2026 · Built solo · Shipped from a small office.</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<a href="../shopify-pet/">For Shopify operators</a> ·
|
||||
<a href="../bookkeeper/">For bookkeepers</a><br />
|
||||
<a href="https://gumroad.com/l/datatools?from=revops">Buy on Gumroad</a> ·
|
||||
<a href="mailto:hello@datatools.app">Email support</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
381
landing/shopify-pet/index.html
Normal file
381
landing/shopify-pet/index.html
Normal file
@@ -0,0 +1,381 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>DataTools for Shopify — Clean Customer & Product Exports Locally · $49</title>
|
||||
<meta name="description" content="Clean Shopify customer, product, and subscriber exports — locally. Klaviyo-import-ready in 30 seconds. Catches duplicates Excel misses. Your data never leaves your computer. $49 one-time." />
|
||||
<meta name="keywords" content="shopify customer cleanup, shopify csv cleaner, shopify product feed cleaner, klaviyo deduplicate, shopify customer dedup tool, shopify pet supplies" />
|
||||
<link rel="canonical" href="https://datatools.app/shopify/" />
|
||||
<link rel="stylesheet" href="../_shared/styles.css" />
|
||||
|
||||
<!-- Persona accent: Shopify pet → mint green (default in shared sheet) -->
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="DataTools for Shopify — Clean Customer & Product Exports Locally" />
|
||||
<meta property="og:description" content="Klaviyo-import-ready in 30 seconds. Local. No upload. $49 one-time." />
|
||||
<meta property="og:type" content="product" />
|
||||
<meta property="og:url" content="https://datatools.app/shopify/" />
|
||||
|
||||
<!-- Schema.org Product -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "DataTools for Shopify",
|
||||
"operatingSystem": "Windows, macOS, Linux",
|
||||
"applicationCategory": "BusinessApplication",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "49",
|
||||
"priceCurrency": "USD"
|
||||
},
|
||||
"description": "Clean Shopify customer, product, and subscriber CSV exports locally. Six-tool data-cleaning bundle: dedupe, text-clean, format-standardize, missing-value handle, column-map, pipeline.",
|
||||
"softwareVersion": "1.0"
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============= Sticky buy bar ============= -->
|
||||
<div class="buybar">
|
||||
<div class="buybar-inner">
|
||||
<div class="brand"><span class="brand-mark">●</span> DataTools <span class="muted">/ for Shopify</span></div>
|
||||
<div>
|
||||
<span class="price-tag">$49 — one-time, no subscription</span>
|
||||
<a class="btn" href="https://gumroad.com/l/datatools?from=shopify-pet" rel="noopener">Get DataTools →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============= Hero ============= -->
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<div class="eyebrow">For Shopify operators · pet supplies · subscription stores · DTC</div>
|
||||
<h1>Klaviyo-import-ready customer lists.<br /><strong>In 30 seconds. Locally.</strong></h1>
|
||||
<p class="lead">
|
||||
Your Shopify customer export is a mess of formatting drift, disguised
|
||||
duplicates, and inconsistent phone numbers. DataTools fixes all of it
|
||||
in one pass — fuzzy-dedupes the same customer Klaviyo would charge
|
||||
you for twice, standardises phones across your international
|
||||
subscribers, and hands you a cleaned CSV. <strong>Your data never
|
||||
leaves your computer.</strong>
|
||||
</p>
|
||||
<div class="cta-row">
|
||||
<a class="btn btn-large" href="https://gumroad.com/l/datatools?from=shopify-pet" rel="noopener">Get DataTools — $49 →</a>
|
||||
<a class="btn btn-ghost btn-large" href="#demo">Try the live demo ↓</a>
|
||||
<span class="price-note">One-time payment · cross-platform · runs offline</span>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="num">6</div><div class="label">tools, one bundle</div></div>
|
||||
<div class="stat"><div class="num">1 GB</div><div class="label">customer file in 2.5 min</div></div>
|
||||
<div class="stat"><div class="num">0</div><div class="label">cloud uploads ever</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============= Pain points ============= -->
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">If any of these sound like your Tuesday</div>
|
||||
<h2>Five pains DataTools fixes in one pass</h2>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<span class="icon">💸</span>
|
||||
<h3>Klaviyo / Mailchimp / Omnisend bills you for every duplicate</h3>
|
||||
<p>Same customer signs up twice — once with a typo, once with a plus-tag, once on mobile. Your subscriber list has 10–18 % duplicate rate and you're paying for every one of them, every month, forever.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> $30–$300/mo per percent of dupes on a 50 k-list — recurring.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">📵</span>
|
||||
<h3>Your product feed got rejected by Google Merchant Center</h3>
|
||||
<p>Smart quotes from a copy-paste in product titles. NBSP in SKU. Inconsistent attribute casing. Feed bounces, the launch sits for 24–72 hours while you try to find the bad row in a 12,000-line CSV.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> 1–3 days of delayed campaign × the campaign value.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🪢</span>
|
||||
<h3>Orders from Shopify + Etsy + Amazon + Faire don't speak the same language</h3>
|
||||
<p>Each platform's export uses different column names for "customer email" / "ship country" / "order total." Merging takes hours of manual rename and copy-paste before the analysis can even begin.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> 4–8 hours per month manually merging exports.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🔁</span>
|
||||
<h3>Subscription churn looks higher than it is</h3>
|
||||
<p>Pet-box subscribers cancel, then re-sub three months later under a different email or device. Your cohort report says churn is 20 % when it's actually 12 % — and you're over-paying for acquisition because LTV is mis-calculated.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> wrong CAC ceiling for the next year of paid ads.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🌍</span>
|
||||
<h3>VAT MOSS / EU tax breaks because country is spelled three ways</h3>
|
||||
<p>Your UK customers are tagged <code>UK</code>, <code>U.K.</code>, and <code>United Kingdom</code> — all in one export. The VAT report aggregates them as three different markets. Compliance friction every quarter.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> compliance risk + repeated manual normalization.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🔒</span>
|
||||
<h3>Cloud cleaners want you to upload your customer list</h3>
|
||||
<p>Your customer list is your single most valuable business asset. Uploading it to a SaaS to clean it is the privacy story you do not want. DataTools is desktop-only — your list never leaves your computer.</p>
|
||||
<p class="muted"><strong>What it costs:</strong> nothing — and that's the point.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============= Live demo ============= -->
|
||||
<section id="demo">
|
||||
<div class="container">
|
||||
<div class="eyebrow">Live demo · runs in your browser</div>
|
||||
<h2>Try it on a real-looking Shopify customer export</h2>
|
||||
<p>
|
||||
The demo below loads a sample 15-row Shopify customer file with
|
||||
pollution we've seen in actual stores: smart quotes from copy-paste,
|
||||
duplicates with email-case drift, international phones from the UK,
|
||||
Spain, Germany, Australia, and Japan, and the usual mess of
|
||||
<code>N/A</code> / <code>(blank)</code> / <code>?</code> sentinels.
|
||||
Click <strong>Run pipeline</strong> and watch every column get
|
||||
cleaned in under a second.
|
||||
</p>
|
||||
<div class="demo-frame">
|
||||
<iframe
|
||||
src="https://demo.datatools.app/?p=shopify-pet"
|
||||
loading="lazy"
|
||||
title="DataTools live demo — Shopify pet supplies"
|
||||
sandbox="allow-scripts allow-same-origin allow-downloads allow-forms"></iframe>
|
||||
<div class="demo-caption">
|
||||
Demo runs on free hosting (Streamlit Community Cloud). Capped at
|
||||
100 input rows · output watermarked with one trailing row. The
|
||||
paid product has no caps and runs entirely offline.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============= Built for Shopify ============= -->
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">Built for the Shopify operator</div>
|
||||
<h2>Five workflows you do every week</h2>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<span class="icon">🧹</span>
|
||||
<h3>Customer-list cleanup</h3>
|
||||
<p>Catches the same customer who shows up as <code>john@gmail.com</code>, <code>John@Gmail.com</code>, and <code>j.ohn@gmail.com</code>. Fuzzy match merges the spellings, exact match catches the obvious ones.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">📦</span>
|
||||
<h3>Product catalogue dedup</h3>
|
||||
<p>SKU whitespace, near-identical product names, copy-paste smart quotes in titles — gone. Audit log shows every change.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🛒</span>
|
||||
<h3>Abandoned-cart hygiene</h3>
|
||||
<p>Before re-engagement: dedupe across email + phone, drop sentinels-as-missing, format dates so your sequence triggers fire correctly.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">📥</span>
|
||||
<h3>Subscriber-list import to Klaviyo</h3>
|
||||
<p>Klaviyo charges per contact. Every duplicate you don't catch costs you for the life of the subscription. Catch them once, pay once.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">🔗</span>
|
||||
<h3>Multi-channel order consolidation</h3>
|
||||
<p>Orders from Shopify + Etsy + a wholesale spreadsheet, each with a different column for "customer email." Column-mapper aligns them; dedup merges across channels.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<span class="icon">⚙️</span>
|
||||
<h3>Repeatable pipeline</h3>
|
||||
<p>Save the cleanup as a JSON file. Drop next week's export on it. Same cleanup, zero re-configuration. Automatable via the CLI.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============= Privacy moat ============= -->
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">The thing every cloud cleaner can't say</div>
|
||||
<h2>Your customer list never leaves your computer.</h2>
|
||||
<p>
|
||||
DataTools is a desktop app. There's no upload step, no SaaS account,
|
||||
no subscription, no "trust our security policy." The first thing you
|
||||
can do after install is open your browser's network tab, run the
|
||||
cleaner on your real customer file, and verify zero outbound
|
||||
requests.
|
||||
</p>
|
||||
<div class="callout">
|
||||
<strong>Why it matters for Shopify:</strong> your customer list is
|
||||
your single most valuable business asset. Cloud cleaners require
|
||||
you to upload it. We don't.
|
||||
</div>
|
||||
<div class="terminal"><span class="prompt">$</span> python -m src.cli_pipeline customers.csv --apply
|
||||
Reading customers.csv...
|
||||
47,832 rows, 14 columns
|
||||
Executing pipeline:
|
||||
<span class="ok">✓</span> text_clean (140 ms) {cells_changed: 12,408}
|
||||
<span class="ok">✓</span> format_standardize (810 ms) {cells_changed: 31,202}
|
||||
<span class="ok">✓</span> missing (95 ms) {sentinels_standardized: 8,129}
|
||||
<span class="ok">✓</span> dedup (3.1 s) {duplicates_removed: 2,347}
|
||||
|
||||
Initial rows: 47,832 → Final rows: 45,485
|
||||
Total elapsed: 4.2 s
|
||||
<span class="prompt">$</span> # zero network calls. zero. promise.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============= Audit moat ============= -->
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">For when your client asks "what changed?"</div>
|
||||
<h2>Every change auditable. Every cell logged.</h2>
|
||||
<p>
|
||||
Every modification is recorded with the original value, the new
|
||||
value, and which rule fired. Hand the audit CSV to your accountant,
|
||||
your marketing manager, or your boss along with the cleaned file.
|
||||
No <em>"I trust the AI"</em> hand-waving — they see exactly what
|
||||
happened.
|
||||
</p>
|
||||
<div class="callout">
|
||||
<strong>Real example:</strong> the demo above standardized 27
|
||||
cells across 15 customers. The audit log lists each one — row,
|
||||
column, before, after, which standardizer fired. The dedup audit
|
||||
lists every duplicate group with the survivor and its losers.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============= International ============= -->
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">If you sell internationally — most pet brands do</div>
|
||||
<h2>Phones, addresses, and currencies from anywhere on Earth.</h2>
|
||||
<p>
|
||||
Your subscriber from London entered her phone as <code>020 7946
|
||||
0958</code>. Your Tokyo customer entered <code>03-3210-7000</code>.
|
||||
Your German wholesale buyer wrote <code>€2.410,75</code>. Excel
|
||||
thinks all of them are mistakes. DataTools knows what country each
|
||||
row is from (per-row country column) and parses every one correctly
|
||||
to E.164 phones, ISO dates, and numeric amounts.
|
||||
</p>
|
||||
<ul class="bullets">
|
||||
<li><strong>50+ country codes</strong> via Google's libphonenumber.</li>
|
||||
<li><strong>Currency auto-detect</strong> for $ / £ / € / ¥ / R$ / kr / zł — including the EU comma-decimal that breaks Excel.</li>
|
||||
<li><strong>Address shape detection</strong> for US, UK, Canada, Germany, Australia.</li>
|
||||
<li><strong>Locale-aware month names</strong> in English, French, German.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============= What you get ============= -->
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">In the bundle</div>
|
||||
<h2>Six tools. One pipeline. One $49 download.</h2>
|
||||
<div class="grid">
|
||||
<div class="card"><h3>1 · Deduplicator</h3><p>Fuzzy match (Jaro-Winkler), 5 normalizers, survivor rules, interactive review.</p></div>
|
||||
<div class="card"><h3>2 · Text Cleaner</h3><p>Whitespace, smart chars, NBSP, BOM, line endings, case ops.</p></div>
|
||||
<div class="card"><h3>3 · Format Standardizer</h3><p>Dates, phones, emails, addresses, names, currencies, booleans.</p></div>
|
||||
<div class="card"><h3>4 · Missing Value Handler</h3><p>Disguised-null detection, profile, mean/median/mode/ffill, drop strategies.</p></div>
|
||||
<div class="card"><h3>5 · Column Mapper</h3><p>Fuzzy auto-rename, target schema, type coercion, required-field defaults.</p></div>
|
||||
<div class="card"><h3>6 · Pipeline Runner</h3><p>Chain tools in recommended order, save/load JSON, automate weekly cleanups.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============= Pricing ============= -->
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="eyebrow">Pricing — pay once, own it</div>
|
||||
<h2>$49. No subscription. No ceiling on rows or files.</h2>
|
||||
<div class="pricing">
|
||||
<div class="card featured">
|
||||
<div class="row"><div class="price">$49</div><div class="price-suffix">one-time</div></div>
|
||||
<h3>DataTools for Shopify</h3>
|
||||
<ul>
|
||||
<li>All 6 tools, full pipeline</li>
|
||||
<li>Mac · Windows · Linux installers</li>
|
||||
<li>Code-signed (no Gatekeeper warnings)</li>
|
||||
<li>Free updates for the v1.x line</li>
|
||||
<li>Bonus: 3 ready-made Shopify pipelines</li>
|
||||
</ul>
|
||||
<a class="btn btn-large" href="https://gumroad.com/l/datatools?from=shopify-pet" rel="noopener">Buy on Gumroad →</a>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="row"><div class="price">$149</div><div class="price-suffix">one-time</div></div>
|
||||
<h3>Full DataTools Suite</h3>
|
||||
<p class="muted">Available when 3+ bundles ship. Includes everything in the Shopify pack plus the Bookkeeper and RevOps bundles. Save $48.</p>
|
||||
<a class="btn btn-ghost btn-large" href="#" aria-disabled="true">Coming when ready</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============= FAQ ============= -->
|
||||
<section>
|
||||
<div class="container">
|
||||
<h2>Questions</h2>
|
||||
|
||||
<details class="faq">
|
||||
<summary>Does this work with Shopify Plus?</summary>
|
||||
<p>Yes — the input is just CSV / Excel from any source. Your Shopify Plus exports work the same as the standard plan, the same as a Shopify-to-CSV pipeline you've stitched together yourself. The cleaner doesn't care.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>How does this compare to Excel's "Remove Duplicates"?</summary>
|
||||
<p>Excel does <em>exact</em> deduplication. <code>John@Gmail.com</code> and <code>john@gmail.com</code> are different customers to Excel. DataTools fuzzy-matches across case, whitespace, formatting, and even close-but-not-identical strings. The demo above merges 4 customer pairs Excel would leave duplicated.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>How big a file can it handle?</summary>
|
||||
<p>1 GB CSV with international phones + addresses processes in about 2.5 minutes on a typical workstation. Streaming mode keeps memory bounded regardless of input size — we tested it on 26 million rows.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>Do I need to know Python to use it?</summary>
|
||||
<p>No. The GUI is a browser interface that opens automatically when you double-click the app. It loads your file, you click Run, you download the cleaned file. The CLI is there for power users who want to script weekly cleanups.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>What about my privacy?</summary>
|
||||
<p>Your customer list never leaves your computer. There is no cloud component, no telemetry, no "anonymous usage stats." When the app is running you can confirm zero outbound network requests in your browser's developer tools.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>What's your refund policy?</summary>
|
||||
<p>Try the live demo above on the sample dataset before you buy. If you still find DataTools doesn't fit your workflow within 14 days, email for a refund — no questions asked.</p>
|
||||
</details>
|
||||
|
||||
<details class="faq">
|
||||
<summary>Will there be updates?</summary>
|
||||
<p>Yes. The v1.x line is included free for everyone who buys DataTools today. We ship a patch every 30 days adding country support, edge-case fixes, and small features.</p>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============= Final CTA ============= -->
|
||||
<section>
|
||||
<div class="container" style="text-align: center;">
|
||||
<h2>Stop deduplicating customers by hand.</h2>
|
||||
<p class="lead" style="margin: 0 auto 28px;">One $49 download. Mac, Windows, or Linux. Runs offline. Catches the duplicates Excel misses, standardizes the phones from your international customers, and saves a pipeline you can re-run on next week's export.</p>
|
||||
<a class="btn btn-large" href="https://gumroad.com/l/datatools?from=shopify-pet" rel="noopener">Get DataTools — $49 →</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============= Footer ============= -->
|
||||
<footer>
|
||||
<div class="container">
|
||||
<div>
|
||||
<p><strong>DataTools</strong> — local data-cleaning for Shopify, bookkeepers, and RevOps teams.</p>
|
||||
<p class="muted">© 2026 · Built solo · Shipped from a small office.</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<a href="../bookkeeper/">For bookkeepers</a> ·
|
||||
<a href="../revops/">For RevOps agencies</a><br />
|
||||
<a href="https://gumroad.com/l/datatools?from=shopify-pet">Buy on Gumroad</a> ·
|
||||
<a href="mailto:hello@datatools.app">Email support</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user