/* Backentra — Blog index + the cornerstone CRM guide article.
   window.BackentraBlog, window.BackentraBlogPost */
const BL = window.BackentraDesignSystem_93cf3a;
const { Button: BLBtn, Icon: BLIcon, Tag: BLTag } = BL;
const bcontainer = { maxWidth: "var(--container-max)", margin: "0 auto", padding: "0 32px" };
const beyebrow = { fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 11, letterSpacing: ".08em", textTransform: "uppercase", color: "var(--text-muted)" };
/* Page key -> real URL, so every internal blog link is a real href and not just an onClick. */
const bP2P = window.BackentraPageToPath;

const BL_BATCH1 = [window.BK_BLOG_PILLARS, window.BK_BLOG_BATCH1, window.BK_BLOG_BATCH2, window.BK_BLOG_BATCH3, window.BK_BLOG_BATCH4].reduce((acc, batch) => {
  if (!batch) return acc;
  return acc.concat(Object.keys(batch).map((k) => {
    const p = batch[k];
    return { key: k, kicker: p.kicker, title: p.title, dek: p.dek, author: p.author, date: p.date, read: p.read, tags: p.tags };
  }));
}, []);

/* ---------- articles published from the CRM console ----------
   These arrive at runtime through /api/blog rather than shipping with the deploy. The hook
   merges them into BL_ARTICLES — the same registry the hand-written ones live in — so
   BlogArticle, the table of contents, `related` filtering and the search index all treat
   them identically. Nothing downstream of this function knows there are two sources.

   Merging happens in the effect rather than during render because it mutates BL_ARTICLES,
   and a render that writes to module state runs twice under StrictMode.

   A repo article WINS a key collision. The one in this file has been reviewed and deployed;
   the one in the database can be changed by anyone with the console open. Silently shadowing
   the deployed copy is the worse failure, so the collision is warned about instead. */
function useRemoteArticles() {
  const R = window.BK_BLOG_REMOTE;
  const [state, setState] = React.useState(() => (R ? R.get() : { status: "idle", posts: [] }));
  React.useEffect(() => {
    if (!R) return;
    const apply = () => {
      const st = R.get();
      st.posts.forEach((p) => {
        if (BL_ARTICLES[p.key] && !BL_ARTICLES[p.key].remote) {
          console.warn('Blog: published article "' + p.key + '" has the same address as one written in this repo. The repo copy is shown; rename one of them.');
          return;
        }
        BL_ARTICLES[p.key] = p;
      });
      setState(st);
    };
    apply();
    const off = R.subscribe(apply);
    R.load();
    return off;
  }, [R]);
  return state;
}

/* The index's card shape. Same fields BL_BATCH1 derives from the batch files. */
const remoteCard = (p) => ({ key: p.key, kicker: p.kicker, title: p.title, dek: p.dek, author: p.author, date: p.date, read: p.read, tags: p.tags, remote: true });

const BL_POSTS = [
  {
    key: "crm-guide",
    kicker: "Cornerstone guide",
    title: "The Ultimate Guide to CRM Software for Home Service Businesses (2026)",
    dek: "Everything to know before choosing software for your business: features, pricing, integrations, AI, scheduling, and the mistakes that cost operators the most.",
    author: "The Backentra Team",
    date: "July 2026",
    read: "42 min read",
    tags: ["CRM", "Software buying", "Field service"],
  },
  {
    key: "vision",
    kicker: "Company",
    title: "From Home Services to a Complete Business Operating System: The Vision Behind Backentra",
    dek: "Why we are starting with home service businesses, how the platform is built to grow beyond them, and why we integrate with the tools you already run before replacing anything.",
    author: "The Backentra Team",
    date: "July 2026",
    read: "14 min read",
    tags: ["Vision", "Product", "Integrations"],
  },
  {
    key: "hidden-cost",
    kicker: "Operations",
    title: "The Hidden Cost of Running a Business Across Too Many Platforms",
    dek: "Duplicate entry, five logins, stale information in the field and a subscription bill nobody audits. What disconnected systems actually cost, and how to close the gaps.",
    author: "The Backentra Team",
    date: "July 2026",
    read: "16 min read",
    tags: ["Operations", "Integrations", "Education"],
  },
  {
    key: "integrations-first",
    kicker: "Platform strategy",
    title: "Why Backentra Is Starting With Integrations",
    dek: "Your accountant, your calendar and your processor already work. Here is why we connect to them first, how we decide what becomes native, and what a real integration means.",
    author: "The Backentra Team",
    date: "July 2026",
    read: "13 min read",
    tags: ["Integrations", "Strategy", "Product"],
  },
  {
    key: "philosophy",
    kicker: "Product philosophy",
    title: "Why Business Software Should Work the Way Your Company Works",
    dek: "No two service businesses run the same way. Roles, permissions, department views and connected workflows, and why flexibility has to survive contact with simplicity.",
    author: "The Backentra Team",
    date: "July 2026",
    read: "12 min read",
    tags: ["Philosophy", "Workflows", "Product"],
  },
  {
    key: "how-we-decide",
    kicker: "Behind the build",
    title: "How We Decide What to Build Into Backentra",
    dek: "Problems before features, gaps between departments, native versus integration, and why plenty of good ideas do not belong in the platform.",
    author: "The Backentra Team",
    date: "July 2026",
    read: "11 min read",
    tags: ["Development", "Transparency", "Roadmap"],
  },
];

function BlogIndex({ onNav, onJoin }) {
  const CP = window.BK_CONTENT_PLAN;
  const open = (k) => (e) => { e.preventDefault(); onNav && onNav("blog-" + k); };
  React.useEffect(() => {
    window.BackentraSetMeta && window.BackentraSetMeta({
      path: "/blog",
      title: "Blog — Backentra",
      description: "Operational advice for service businesses: scheduling, paperwork, integrations and the real cost of running a company across too many tools.",
    });
    window.BackentraAnalytics && window.BackentraAnalytics.trackEvent("blog_view", { slug: null });
  }, []);
  const [q, setQ] = React.useState("");
  const [openKey, setOpenKey] = React.useState(null);
  const remote = useRemoteArticles();

  /* Written posts, keyed by title so a plan entry can find its article without a second id. */
  const listed = BL_POSTS.concat(BL_BATCH1).concat(remote.posts.map(remoteCard));
  const norm = (s) => String(s).toLowerCase().replace(/[^a-z0-9]+/g, "");
  const byTitle = {};
  listed.forEach((p) => { byTitle[norm(p.title)] = p; });
  const articleFor = (title) => byTitle[norm(title)] || null;
  /* Keyed as well as titled: pillarFor may return a legacy key that has no BL_ARTICLES entry. */
  const byKey = {};
  listed.forEach((p) => { byKey[p.key] = p; });
  const pillarFor = (cl) => {
    const flagged = Object.keys(BL_ARTICLES).filter((k) => BL_ARTICLES[k].pillar && BL_ARTICLES[k].cluster === cl.key)[0];
    if (flagged) return flagged;
    const byLead = articleFor(cl.lead);
    return byLead ? byLead.key : null;
  };

  /* Company posts sit outside the topic clusters, so they get their own group rather than
     being left off the page. */
  const inPlan = {};
  (CP ? CP.flat() : []).forEach((p) => { const a = articleFor(p.title); if (a) inPlan[a.key] = true; });
  /* Lead titles live on the cluster rather than in posts, so they must be marked here too. */
  (CP ? CP.CLUSTERS : []).forEach((cl) => { const a = articleFor(cl.lead); if (a) inPlan[a.key] = true; });
  const pillarKeys = Object.keys(BL_ARTICLES).filter((k) => BL_ARTICLES[k].pillar);
  /* Every published article now belongs to one of the fourteen subjects, so there is no company
     group: the pieces about how Backentra is built live in Building Backentra, where a reader
     looking for them would go. Anything the plan does not list is a plan error and is reported
     rather than routed somewhere plausible, which is what hid three title drifts. */
  const unclustered = [];
  /* Articles published from the console are exempt: they are filed by an explicit `cluster`
     chosen when they were written, not by matching a title against the plan, so the plan
     legitimately does not list them. Reporting them here would make this warning noise and
     hide the title drifts it exists to catch. Their own failure mode is checked below. */
  const misfiled = listed.filter((p) => !p.remote && !inPlan[p.key] && pillarKeys.indexOf(p.key) < 0);
  if (misfiled.length) console.warn("Blog: published articles the plan does not list. Add them to contentplan.js or fix the title drift:", misfiled.map((p) => p.key + " \u2014 " + p.title));

  const clusterKeys = (CP ? CP.CLUSTERS : []).map((c) => c.key);
  const homeless = remote.posts.filter((p) => clusterKeys.indexOf(p.cluster) < 0);
  if (homeless.length) console.warn("Blog: published articles whose subject this site has no group for. They are not shown anywhere:", homeless.map((p) => p.key + " \u2014 " + (p.cluster || "no subject")));

  const groups = (CP ? CP.CLUSTERS : []).map((cl) => {
    const pk = pillarFor(cl);
    /* Plan rows first, then the console-published pieces for the same subject. A remote post
       is shaped into the same row the plan produces \u2014 `article` already resolved, since it
       is its own article \u2014 so everything downstream (the feed, search, the counts) treats
       the two the same way. */
    const rows = cl.posts.map((p) => ({ ...p, article: articleFor(p.title) }))
      .concat(remote.posts.filter((p) => p.cluster === cl.key).map((p) => ({
        n: "r-" + p.key, title: p.title, focus: p.focus || "", angle: "",
        level: p.pillar ? "lead" : "support", article: remoteCard(p),
      })));
    return { key: cl.key, label: cl.label, intent: cl.intent, pillarKey: pk,
      pillarTitle: (pk && byKey[pk] ? byKey[pk].title : null) || cl.lead, pillarFocus: cl.leadFocus,
      rows: rows, done: rows.filter((r) => r.article).length };
  });

  /* Subjects with at least one written piece. Static, so the masthead never reacts to search,
     and honest, so it does not count subjects with nothing to read. */
  const covered = groups.filter((g) => g.rows.some((r) => r.article)).length;
  const FEATURED_KEY = "paper-guide";
  /* Round-robin across subjects. Ten paper articles in a row is what made the blog read as narrow,
     and sorting by date would reproduce that because they were written in batches. */
  const bySubject = groups.map((g) => g.rows.filter((r) => r.article).map((r) => ({
    key: r.article.key, title: r.article.title, dek: r.article.dek, read: r.article.read,
    subject: g.label, isLead: r.article.key === g.pillarKey, hay: (r.title + " " + r.focus + " " + g.label).toLowerCase(),
  })));
  groups.forEach((g, i) => {
    /* The lead belongs in the feed too, and is not in its own posts array. */
    if (g.pillarKey && !bySubject[i].some((x) => x.key === g.pillarKey)) {
      const a = byKey[g.pillarKey];
      if (a) bySubject[i].unshift({ key: a.key, title: a.title, dek: a.dek, read: a.read, subject: g.label, isLead: true, hay: (a.title + " " + g.label).toLowerCase() });
    }
  });
  const interleaved = [];
  for (let i = 0; ; i++) {
    let took = false;
    for (const list of bySubject) { if (list[i]) { interleaved.push(list[i]); took = true; } }
    if (!took) break;
  }
  const term = q.trim().toLowerCase();
  const feed = term
    ? interleaved.filter((p) => p.hay.indexOf(term) >= 0 || p.title.toLowerCase().indexOf(term) >= 0)
    : interleaved.filter((p) => p.key !== FEATURED_KEY);
  const matches = (r, cl) => {
    if (!r.article) return false;
    if (!term) return true;
    return (r.title + " " + r.focus + " " + r.angle + " " + cl.label).toLowerCase().indexOf(term) >= 0;
  };
  const visible = groups.map((g) => {
    const hits = g.rows.filter((r) => matches(r, g));
    /* The lead piece is rendered as a row in the panel, so it belongs in the count. Search has
       to be able to hide it too, or a term that matches nothing would still show it. */
    const leadShown = !!g.pillarKey && (!term || norm(g.pillarTitle).indexOf(norm(term)) >= 0 || hits.length > 0);
    return { ...g, hits: hits, leadShown: leadShown, shown: hits.length + (leadShown ? 1 : 0) };
  }).filter((g) => g.shown > 0);

  const companyHits = unclustered.filter((p) => !term || (p.title + " " + p.kicker).toLowerCase().indexOf(term) >= 0);
  const featured = BL_ARTICLES[FEATURED_KEY] ? { key: FEATURED_KEY, ...BL_ARTICLES[FEATURED_KEY] } : listed[0];
  const c = CP ? CP.counts(BL_ARTICLES, listed.length) : { posts: 0, clusters: 0, leadsWritten: 0, leadsDeclared: 0, published: 0 };

  const row = (r) => (
    <a key={r.n} href={bP2P("blog-" + r.article.key)} onClick={open(r.article.key)} style={{ display: "flex", gap: 11, padding: "12px 16px", borderTop: "1px solid var(--border)", textDecoration: "none", alignItems: "flex-start" }}>
      <BLIcon name="arrow-right" size={15} color="var(--accent-text)" style={{ flex: "none", marginTop: 3 }} />
      <span style={{ minWidth: 0, flex: 1 }}>
        <span style={{ display: "block", fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 14.5, color: "var(--text-strong)" }}>{r.title}</span>
        <span style={{ display: "block", fontSize: 12.5, color: "var(--text-muted)", marginTop: 2 }}>{r.article.read}</span>
      </span>
    </a>
  );

  return (
    <div style={{ background: "var(--surface-page)", minHeight: "100vh" }}>
      <window.BackentraSiteHeader current="blog" onNav={onNav} />

      {/* The hero says what this is. A specific article's headline here made the whole blog read
          as one post, and gave a reader no idea what else was on the page. */}
      <section style={{ background: "var(--navy-900)", padding: "54px 0 48px" }}>
        <div style={bcontainer}>
          <div style={{ ...beyebrow, color: "var(--accent-text)", marginBottom: 14 }}>Backentra Blog</div>
          <div style={{ display: "grid", gridTemplateColumns: "minmax(0,1.6fr) minmax(0,1fr)", gap: 44, alignItems: "end" }}>
            <div style={{ minWidth: 0 }}>
              <h1 style={{ color: "#fff", fontSize: 40, lineHeight: 1.14, letterSpacing: "-0.02em", margin: "0 0 16px" }}>
                What we have learned running the work, written down
              </h1>
              <p style={{ color: "var(--gray-300)", fontSize: 17.5, lineHeight: 1.6, margin: 0, maxWidth: 640, textWrap: "pretty" }}>
                We have run the schedule, chased the invoice and taken the call at 5:15. These are the operational
                problems we kept hitting, what they actually cost, and what we would do differently. Opinions rather
                than instructions, and we say plainly where we are still working something out.
              </p>
            </div>
            <div style={{ minWidth: 0, display: "grid", gap: 10 }}>
              {[[String(c.published), "articles"], [String(covered), covered === 1 ? "subject" : "subjects"]].map(([n, l]) => (
                <div key={l} style={{ display: "flex", alignItems: "baseline", gap: 10 }}>
                  <span style={{ fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 26, color: "#fff", letterSpacing: "-0.02em" }}>{n}</span>
                  <span style={{ fontSize: 13.5, color: "var(--gray-300)" }}>{l}</span>
                </div>
              ))}
            </div>
          </div>
        </div>
      </section>

      {/* Filter and search decide what is in view, replacing the choice between two lists. */}
      <section style={{ padding: "26px 0 0", background: "var(--surface-page)", position: "sticky", top: 0, zIndex: 5 }}>
        <div style={{ ...bcontainer, paddingBottom: 18, borderBottom: "1px solid var(--border)" }}>
          <div style={{ display: "flex", gap: 10, flexWrap: "wrap", alignItems: "center" }}>
            <div style={{ flex: "1 1 320px", minWidth: 0, position: "relative" }}>
              <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search"
                style={{ width: "100%", height: 40, padding: "0 14px 0 38px", borderRadius: "var(--radius-md)", border: "1px solid var(--border)",
                  background: "var(--surface-card)", fontFamily: "var(--font-body)", fontSize: 14.5, color: "var(--text-strong)", boxSizing: "border-box" }} />
              <span style={{ position: "absolute", left: 13, top: 12 }}><BLIcon name="search" size={16} color="var(--text-subtle)" /></span>
            </div>
            <span style={{ fontSize: 13, color: "var(--text-muted)" }}>
              Showing {feed.length} of {interleaved.length}
            </span>
          </div>
        </div>
      </section>

      <section style={{ padding: "26px 0 56px" }}>
        <div style={bcontainer}>
          {!term && featured && (
            <a href={bP2P("blog-" + featured.key)} onClick={open(featured.key)} style={{ display: "block", marginBottom: 22, padding: "22px 24px", borderRadius: "var(--radius-lg)",
              background: "var(--surface-card)", border: "1px solid var(--border)", borderLeft: "3px solid var(--accent)", textDecoration: "none" }}>
              <span style={{ ...beyebrow, color: "var(--accent-text)" }}>Start with this one</span>
              <span style={{ display: "block", fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 22, lineHeight: 1.25, color: "var(--text-strong)", margin: "8px 0 6px" }}>{featured.title}</span>
              <span style={{ display: "block", fontSize: 15, lineHeight: 1.55, color: "var(--text-muted)", maxWidth: 660, textWrap: "pretty" }}>{featured.dek}</span>
            </a>
          )}

          {/* One list, interleaved by subject rather than grouped. Grouping put ten paper articles
              in a row and made the blog look narrow; taking one subject at a time in rotation
              spreads them out while each card still says which subject it belongs to. */}
          <div style={{ ...beyebrow, marginBottom: 16 }}>Latest</div>
          <div style={{ display: "grid", gap: 16 }}>
            {feed.map((p) => (
              <a key={p.key} href={bP2P("blog-" + p.key)} onClick={open(p.key)} style={{ display: "block", padding: "22px 24px", borderRadius: "var(--radius-lg)",
                background: "var(--surface-card)", border: "1px solid var(--border)", boxShadow: "var(--shadow-sm)", textDecoration: "none" }}>
                <span style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap", marginBottom: 9 }}>
                  <span style={{ display: "inline-flex", alignItems: "center", height: 22, padding: "0 9px", borderRadius: "var(--radius-pill)",
                    background: "var(--orange-50)", color: "var(--orange-700)", fontFamily: "var(--font-display)", fontWeight: 700, fontSize: "var(--text-2xs)", letterSpacing: ".05em", textTransform: "uppercase" }}>{p.subject}</span>
                  {p.isLead && <span style={{ ...beyebrow, color: "var(--accent-text)" }}>The long read</span>}
                  <span style={{ fontSize: 12.5, color: "var(--text-muted)" }}>{p.read}</span>
                </span>
                <span style={{ display: "block", fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 21, lineHeight: 1.28, letterSpacing: "-0.01em", color: "var(--text-strong)", marginBottom: 7 }}>{p.title}</span>
                <span style={{ display: "block", fontSize: 15, lineHeight: 1.6, color: "var(--text-muted)", maxWidth: 720, textWrap: "pretty" }}>{p.dek}</span>
              </a>
            ))}
          </div>

          {!feed.length && (
            <div style={{ padding: "28px 24px", borderRadius: "var(--radius-lg)", background: "var(--surface-card)", border: "1px solid var(--border)", fontSize: 15, color: "var(--text-muted)" }}>
              Nothing matches that. Try another word, or clear the search to see everything.
            </div>
          )}

          <div style={{ marginTop: 26, padding: "26px 28px", borderRadius: "var(--radius-xl)", background: "var(--navy-900)", display: "flex", gap: 20, alignItems: "center", flexWrap: "wrap" }}>
            <div style={{ minWidth: 0, flex: "1 1 340px" }}>
              <h3 style={{ color: "#fff", fontSize: 22, margin: "0 0 8px", lineHeight: 1.25 }}>New pieces as they publish.</h3>
              <p style={{ color: "var(--gray-300)", fontSize: 15, lineHeight: 1.6, margin: 0, textWrap: "pretty" }}>
                We write about the operational problems we keep running into. Join the list and we send each piece as it lands.
              </p>
            </div>
            <BLBtn variant="primary" onClick={onJoin} iconRight={<BLIcon name="arrow-right" size={18} />}>Join the list</BLBtn>
          </div>
        </div>
      </section>

      <window.BackentraSiteFooter onNav={onNav} />
    </div>
  );
}

/* ---------- article primitives ---------- */
const artWrap = { maxWidth: 780, margin: "0 auto", padding: "0 32px" };
function H2({ id, children }) {
  return (
    <h2 id={id} style={{ fontSize: 30, lineHeight: 1.2, margin: "52px 0 8px", scrollMarginTop: 120 }}>
      {children}
      <span style={{ display: "block", width: 44, height: 4, borderRadius: 2, background: "var(--accent)", marginTop: 14 }} />
    </h2>
  );
}
function H3({ children }) {
  return <h3 style={{ fontSize: 19, lineHeight: 1.3, margin: "28px 0 8px", color: "var(--text-strong)" }}>{children}</h3>;
}
function P({ children }) {
  return <p style={{ fontSize: 17, lineHeight: 1.72, color: "var(--text-body)", margin: "0 0 16px", textWrap: "pretty" }}>{children}</p>;
}
function UL({ items }) {
  return (
    <ul style={{ margin: "0 0 18px", padding: 0, listStyle: "none", display: "grid", gap: 9 }}>
      {items.map((t, i) => (
        <li key={i} style={{ display: "flex", gap: 11, fontSize: 16.5, lineHeight: 1.6, color: "var(--text-body)" }}>
          <BLIcon name="check" size={17} color="var(--accent-text)" style={{ flex: "none", marginTop: 4 }} />
          <span>{t}</span>
        </li>
      ))}
    </ul>
  );
}
function Pull({ children }) {
  return (
    <div style={{ margin: "26px 0", padding: "20px 24px", background: "var(--gray-100)", borderRadius: "var(--radius-lg)", border: "1px solid var(--border)" }}>
      <p style={{ margin: 0, fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 17, lineHeight: 1.55, color: "var(--text-strong)" }}>{children}</p>
    </div>
  );
}

const ART_TOC = [
  ["what-is", "What is a home service CRM?"],
  ["outgrow", "Why most companies outgrow spreadsheets"],
  ["signs", "Signs you need a CRM"],
  ["features", "Features every CRM should have"],
  ["missing", "Features most CRMs do not have"],
  ["industries", "Choosing software by industry"],
  ["ai", "How AI is changing home services"],
  ["cloud", "Cloud vs desktop, mobile and customer portals"],
  ["payments", "Payment processing"],
  ["integrations", "Integrations that matter"],
  ["pricing", "How this software is priced"],
  ["mistakes", "Common mistakes when buying"],
  ["faq", "Frequently asked questions"],
  ["compare", "The main options on the market"],
  ["conclusion", "Conclusion: how to actually decide"],
];

const ART_FEATURES = [
  ["calendar-days", "Scheduling", "Drag-and-drop dispatch across crews and days, with travel time, recurring work and conflict detection. This is the feature you will use forty times a day, so test it before anything else."],
  ["file-text", "Estimates", "Templated line items, good-better-best options, photos attached to the scope, and e-signature. An estimate that turns into a job without retyping is the whole point."],
  ["receipt", "Invoices", "Generated from the job that happened, not the job that was quoted. Progress billing, deposits, retainage and automatic reminders on anything past due."],
  ["credit-card", "Payments", "Card and ACH in the field and from the invoice, with fees you can read and deposits you can reconcile."],
  ["user-round", "Customer portal", "Clients approve estimates, see appointments, pay invoices and find their history without calling the office."],
  ["camera", "Photos and documents", "Before and after on the job record, not in a phone gallery. Permits, warranties and signed change orders in the same place."],
  ["bar-chart-3", "Reporting", "Revenue by service, close rate by salesperson, job costing against estimate, and where the margin actually went."],
  ["users", "Employees and crews", "Time tracking, skills, certifications, crew assignment and labor cost flowing into job cost."],
  ["megaphone", "Marketing", "Lead source tracking, review requests, and follow-up on estimates nobody answered."],
  ["workflow", "Automation", "Reminders, status changes, review requests and follow-ups that fire without anyone remembering."],
  ["package", "Inventory and materials", "What is on the truck, what was used on the job, what needs reordering."],
  ["sparkles", "AI assistance", "Drafting estimates and messages, routing a day, summarizing a long customer history."],
  ["message-square", "Communications", "Calls, texts and email attached to the customer record so any person in the office has the full thread."],
  ["shield", "Permissions", "A tech sees their jobs. A manager sees the schedule. Only the owner sees payroll."],
  ["folder", "Document storage", "Contracts, insurance certificates, SOPs and license renewals with expiry dates that warn you."],
];

const ART_INDUSTRIES = [
  ["Tree service", "Crew-and-equipment scheduling, crane and chipper assignment, tree inventory per property, and multi-day jobs that span weather delays."],
  ["Cleaning", "Recurring visits at scale, per-visit checklists, key and alarm-code handling, and high turnover means onboarding has to be quick."],
  ["Pressure washing", "Fast-cycle jobs, square-footage pricing, water source notes, and before-and-after photos that sell the next one."],
  ["Landscaping and lawn", "Route density, seasonal contracts, per-property service maps, and crews that need a route before they leave the yard."],
  ["Roofing", "Insurance claims and supplements, material orders per job, staged payments, and a document trail that survives a dispute."],
  ["HVAC", "Maintenance agreements, equipment history per address, parts on truck, and after-hours dispatch."],
  ["Electrical", "Permits and inspections, panel and circuit notes, and code documentation attached to the job."],
  ["Plumbing", "Emergency dispatch, flat-rate price books, and warranty tracking on parts and labor."],
  ["Gutters", "Linear-foot estimating, color and profile selection, protection add-ons, and per-property measurements you keep for the next visit."],
  ["Pools", "Chemical logs, weekly routes, equipment repair history, and seasonal open and close work."],
  ["Painting", "Room and surface takeoffs, color specifications, multi-day crews, and punch lists at walkthrough."],
];

const ART_FAQ = [
  ["What does CRM mean for a home service business?", "In the trades, CRM has stopped meaning a sales database. It means the system that holds the customer, the estimate, the schedule, the job, the invoice and the payment in one record. If a tool only tracks leads, it is a sales tool, not a home service CRM."],
  ["How much should I pay for home service software?", "Most operators land between $50 and $300 per month for a small company, and per-user pricing on larger teams pushes that into the thousands. Judge it against one recovered job per month rather than against the cheapest option."],
  ["Is a CRM worth it for a one-person business?", "Usually yes, but only if it saves evening paperwork. A solo operator should look for fast estimating, invoicing on the phone, and automatic reminders, and can ignore crew scheduling and permissions until there is a crew."],
  ["Can I move my customers from spreadsheets?", "Yes. Almost every platform imports customers, properties and open balances from CSV. Clean the data before you import: duplicates carried in are duplicates forever."],
  ["Do I need scheduling software and a CRM?", "No, and running both is how double bookings happen. Scheduling and the customer record belong in the same system so a schedule change is visible everywhere."],
  ["How long does implementation take?", "Plan on two weeks of real work for a small company and four to eight for a company with multiple crews and existing accounting history. Anyone promising same-day is describing a login, not an implementation."],
  ["Will my crews actually use it?", "Only if the mobile app is genuinely quick. Test the app the way a tech will use it: one hand, in the sun, with gloves on, on a bad connection."],
  ["What about QuickBooks?", "Most operators keep their accountant on QuickBooks and sync invoices, payments and customers. Check the direction of the sync and whether it maps to your chart of accounts before you commit."],
  ["Cloud or desktop?", "Cloud, for any business with a field crew. Desktop makes sense only where internet is unreliable and everyone works from one office."],
  ["Does the customer portal matter?", "It matters most to your office. Every approval, payment and appointment question the portal answers is a phone call nobody has to take."],
  ["How do I price jobs inside the software?", "Build a price book. Flat-rate line items, materials with markup, and labor by crew hour. A price book is what makes two estimators quote the same job the same way."],
  ["Can software help with reviews?", "Yes. An automatic review request sent within an hour of completion, from the job record, outperforms anything sent later by hand."],
  ["What is AI actually useful for right now?", "Drafting, summarizing and sorting. It writes a first-pass estimate description, summarizes a long customer history before a call, and suggests a route order. Treat its scheduling suggestions as a proposal you approve."],
  ["How do I know when to switch systems?", "When your team maintains a workaround. A shared spreadsheet next to the software, a second calendar, or a whiteboard that is the real truth all mean the software lost."],
  ["What should I ask on a demo?", "Make them build your actual job live: your estimate, your crew, your invoice, on your phone. Anyone who will not do that is selling a slideshow."],
];

const ART_COMPARE = [
  ["Jobber", "Broad, mature, strong scheduling and invoicing. Popular with small to mid teams."],
  ["Housecall Pro", "Consumer-friendly, strong on payments and marketing features."],
  ["ServiceTitan", "Enterprise-weight for HVAC, plumbing and electrical. Deep, and priced accordingly."],
  ["SingleOps", "Green industry focus: tree service and landscaping estimating."],
  ["Workiz", "Dispatch and phone-system integration for smaller field teams."],
  ["FieldPulse", "Broad feature set at a lower price point for growing teams."],
  ["Service Fusion", "Flat-rate user pricing, established in HVAC and plumbing."],
];

const ART_LINKS = [
  ["Scheduling and dispatch", "product"], ["AI assistance", "product"], ["Calendar sync", "product"],
  ["Product", "product"], ["Pricing", "plans"], ["Estimates", "product"],
  ["Online payments", "product"], ["Customer portal", "product"], ["Mobile app", "product"],
  ["Reporting", "product"], ["Founding members", "founding"], ["Enterprise", "enterprise"],
];

function BlogPost({ onNav, onJoin }) {
  const go = (k) => (e) => { e.preventDefault(); onNav && onNav(k); };
  React.useEffect(() => {
    window.BackentraSetMeta && window.BackentraSetMeta({
      path: "/blog/crm-guide",
      title: "The Ultimate Guide to Choosing CRM Software for Home Service Businesses (2026) — Backentra Blog",
      description: "Everything to know before choosing software for your business: features, pricing, integrations, AI, scheduling, and the mistakes that cost operators the most.",
    });
    window.BackentraAnalytics && window.BackentraAnalytics.trackEvent("blog_view", { slug: "crm-guide" });
  }, []);
  return (
    <div style={{ background: "var(--surface-card)", minHeight: "100vh" }}>
      <window.BackentraSiteHeader current="blog" onNav={onNav} />

      <section style={{ background: "var(--navy-900)", padding: "56px 0 48px" }}>
        <div style={{ ...bcontainer, maxWidth: 900 }}>
          <a href="/blog" onClick={go("blog")} style={{ display: "inline-flex", alignItems: "center", gap: 8, ...beyebrow, color: "var(--accent-text)", marginBottom: 18 }}>
            <BLIcon name="arrow-left" size={14} /> Backentra Blog
          </a>
          <h1 style={{ color: "#fff", fontSize: 48, lineHeight: 1.06, margin: "0 0 18px", letterSpacing: "-0.02em" }}>
            The Ultimate Guide to Choosing CRM Software for Home Service Businesses (2026)
          </h1>
          <p style={{ color: "var(--gray-300)", fontSize: 19, lineHeight: 1.6, margin: "0 0 26px", maxWidth: 760 }}>
            Everything you need to know before choosing software for your business: features, pricing, integrations, AI, scheduling, and the mistakes to avoid.
          </p>
          <div style={{ display: "flex", alignItems: "center", gap: 18, flexWrap: "wrap", paddingTop: 20, borderTop: "1px solid var(--border-inverse)" }}>
            {[["Written by the Backentra Team", "users"], ["July 2026", "calendar-days"], ["42 min read", "clock"]].map(([t, ic]) => (
              <span key={t} style={{ display: "inline-flex", alignItems: "center", gap: 8, color: "var(--gray-300)", fontSize: 13.5 }}>
                <BLIcon name={ic} size={15} color="var(--accent-text)" /> {t}
              </span>
            ))}
          </div>
        </div>
      </section>

      {/* table of contents */}
      <section style={{ padding: "40px 0 0" }}>
        <div style={artWrap}>
          <div style={{ padding: "24px 26px", border: "1px solid var(--border)", borderRadius: "var(--radius-lg)", background: "var(--gray-50)" }}>
            <div style={{ ...beyebrow, marginBottom: 14 }}>Table of contents</div>
            <ol style={{ margin: 0, padding: 0, listStyle: "none", display: "grid", gridTemplateColumns: "1fr 1fr", gap: "9px 26px", counterReset: "toc" }}>
              {ART_TOC.map(([id, label], i) => (
                <li key={id} style={{ display: "flex", gap: 10, fontSize: 15, lineHeight: 1.45 }}>
                  <span style={{ fontFamily: "var(--font-mono)", fontSize: 12.5, color: "var(--text-subtle)", flex: "none", paddingTop: 2 }}>{String(i + 1).padStart(2, "0")}</span>
                  <a href={"#" + id} style={{ color: "var(--text-body)", fontWeight: 500 }}>{label}</a>
                </li>
              ))}
            </ol>
          </div>
        </div>
      </section>

      <article style={{ padding: "8px 0 64px" }}>
        <div style={artWrap}>
          <P>
            Choosing software is one of the few decisions an owner makes that touches every job for the next five years. Get it right and the office runs quieter, invoices go out the day the work finishes, and you can see which services actually make money. Get it wrong and you have paid for a second set of paperwork.
          </P>
          <P>
            This guide is written for the person making that call: a gutter company at three crews, a cleaning company at forty recurring accounts, a tree service quoting six figures a month off a whiteboard. It covers what these systems do, what to look for by trade, what the pricing models really cost, and where buyers most often go wrong. We build Backentra, so we have a view, but the aim here is to be the most useful page you read on the subject.
          </P>

          <H2 id="what-is">What is a home service CRM?</H2>
          <P>
            A home service CRM is the system of record for everything that happens between a customer's first call and the money landing in your account. In other industries CRM means a sales pipeline. In the trades it has to mean more, because the sale, the schedule, the work and the invoice are all the same event seen at different times.
          </P>
          <P>
            A real home service CRM holds four things together: the customer and their property, the estimate and what was agreed, the schedule and who is doing the work, and the invoice with its payment. When those four live in one record, nothing gets retyped and no one has to ask the office what was promised. When they live in four tools, the gaps between them are where jobs and money go missing.
          </P>
          <Pull>If a tool only tracks leads, it is a sales tool. A home service CRM has to survive the job, not just the sale.</Pull>

          <H2 id="outgrow">Why most companies outgrow spreadsheets</H2>
          <P>
            Spreadsheets and a whiteboard work fine at one truck. They stop working at the exact moment two things happen at once. The failure is never dramatic. It shows up as small leaks that nobody adds up.
          </P>
          <UL items={[
            "Missed calls that never make it into a follow-up list, because the note lived on a phone.",
            "Estimates sent and forgotten. Most operators never follow up on an unanswered quote, and that is the cheapest revenue in the business.",
            "Invoices that go out a week late, or not at all, because the crew's notes never reached the office.",
            "Scheduling chaos: two crews sent to one address, or a day that looks full and is actually four hours of driving.",
            "Customer communication scattered across a personal cell, a shared inbox and a texting app.",
            "Paperwork done twice, once in the field and once again at a desk at night.",
          ]} />
          <P>
            The cost of all this is real but invisible, which is why owners tolerate it far past the point where it pays for software. One recovered job a month covers most platforms on the market.
          </P>

          <H2 id="signs">Signs you need a CRM</H2>
          <UL items={[
            "You run more than one crew, or you are about to.",
            "You have more than fifty customers you would want to sell to again.",
            "Follow-ups on open estimates happen when you remember them, which is rarely.",
            "You have double booked, or sent a crew without the right equipment, in the last month.",
            "An invoice has gone missing, or you are not sure what is currently unpaid.",
            "Customers ask a question and no one in the office can answer it without calling you.",
            "You are doing paperwork after dinner.",
          ]} />
          <P>Two or more of those, and the question is no longer whether. It is which one, and how fast you can get your team onto it.</P>

          <H2 id="features">Features every CRM should have</H2>
          <P>
            Every vendor has a feature list. What matters is depth in the two or three features you touch every day. Below is what each area should actually do, in the order most operators use them.
          </P>
          <div style={{ display: "grid", gap: 12, margin: "24px 0 8px" }}>
            {ART_FEATURES.map(([ic, t, d]) => (
              <div key={t} style={{ display: "flex", gap: 14, padding: "16px 18px", border: "1px solid var(--border)", borderRadius: "var(--radius-lg)", background: "var(--surface-card)" }}>
                <span style={{ width: 38, height: 38, borderRadius: "var(--radius-md)", background: "var(--orange-50)", color: "var(--accent-text)", display: "inline-flex", alignItems: "center", justifyContent: "center", flex: "none" }}><BLIcon name={ic} size={19} /></span>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 16, color: "var(--text-strong)", marginBottom: 3 }}>{t}</div>
                  <div style={{ fontSize: 15, lineHeight: 1.6, color: "var(--text-muted)" }}>{d}</div>
                </div>
              </div>
            ))}
          </div>

          <H2 id="missing">Features most CRMs do not have</H2>
          <P>
            The list above is the table stakes. The gaps below are where most platforms still leave work on the owner, and they are worth asking about directly on a demo because nobody volunteers them.
          </P>
          <UL items={[
            "Duplicate detection. Two records for the same address is how a customer gets two calls and one invoice never gets paid.",
            "AI scheduling that proposes a day built around travel time, crew skill and promised windows, instead of an empty grid you fill by hand.",
            "A unified timeline: the call, the text, the estimate, the visit, the photo and the payment on one thread per customer.",
            "Automatic conflict detection that catches an overlap, a missing certification or a truck already committed elsewhere.",
            "Workflow automation you can configure yourself, not a support ticket for every rule change.",
            "Integrated approvals: a change order approved in the field that updates the price, the schedule and the invoice at once.",
            "Internal documentation. Your SOPs, price book notes and training material where the work happens.",
            "Department dashboards so sales, operations and accounting each see their own numbers.",
          ]} />

          <H2 id="industries">Choosing software by industry</H2>
          <P>
            Generic field service software fits every trade equally badly. The specifics below are what to test in a demo, using your own jobs.
          </P>
          {ART_INDUSTRIES.map(([t, d]) => (
            <div key={t}>
              <H3>{t}</H3>
              <P>{d}</P>
            </div>
          ))}

          <H2 id="ai">How AI is changing home services</H2>
          <P>
            AI has arrived in this category faster than any feature since mobile. Most of the value today is unglamorous: it drafts, it summarizes, and it sorts. It writes a first-pass scope from a few bullet points and photos. It summarizes eighteen months of history before you call a customer back. It proposes a route order that saves a truck an hour a day.
          </P>
          <P>
            What it is not yet is unsupervised. Treat AI scheduling as a proposal your dispatcher approves, and AI-written customer copy as a draft a human reads. The right question for a vendor is not whether they have AI. It is which decision it makes for you, and where you get to say no.
          </P>

          <H2 id="cloud">Cloud vs desktop, mobile and customer portals</H2>
          <H3>Cloud vs desktop</H3>
          <P>
            If you have anyone in a truck, choose cloud. The field and the office need the same record at the same second, and a desktop install cannot do that without a sync step somebody forgets. Desktop still makes sense for a single-office operation with unreliable internet, and that is about it.
          </P>
          <H3>Mobile apps</H3>
          <P>
            The mobile app is the product for most of your team. Test it the way a tech will use it: one hand, bright sun, gloves, poor signal. It has to work offline and sync later, take photos into the job in two taps, and let a tech collect a payment without calling the office. A mobile app that is a shrunken web page will be abandoned in a month.
          </P>
          <H3>Customer portals</H3>
          <P>
            A portal is often sold as a customer benefit and bought for the office. Every estimate approved, invoice paid and appointment confirmed without a phone call is time back. The bar is low: it has to work without a password reset every visit.
          </P>

          <H2 id="payments">Payment processing</H2>
          <P>
            Getting paid in the field is the single fastest improvement most operators make. Card and ACH from the invoice, saved payment methods for recurring accounts, deposits taken with the signed estimate, and automatic reminders on anything past due. Ask three questions before you sign: what is the effective rate including per-transaction fees, how many days to deposit, and can you pass fees on where it is legal to do so.
          </P>

          <H2 id="integrations">Integrations that matter</H2>
          <P>
            No system does everything. What matters is whether the handful you already depend on connect cleanly, and in which direction data flows.
          </P>
          <UL items={[
            "Google Calendar for two-way schedule visibility.",
            "QuickBooks for invoices, payments and the chart of accounts your bookkeeper already uses.",
            "Stripe or a processor of your choice for card and ACH.",
            "CompanyCam or equivalent for field photography, if your team already lives there.",
            "RingCentral or a phone system that logs calls against the customer.",
            "Paychex or your payroll provider, fed by real tracked hours.",
            "Google Ads and Facebook for lead source attribution that survives to the closed job.",
          ]} />

          <H2 id="pricing">How this software is priced</H2>
          <UL items={[
            "Per user, per month. Simple, and the model that punishes growth hardest. Ask whether field techs count as users.",
            "Flat rate per company, sometimes with a seat band. Predictable, easier to budget as crews change.",
            "Enterprise or quoted. Common above roughly twenty users, usually annual, usually with an implementation fee.",
            "Freemium or trial-led. Fine for evaluating, rarely fine for running a business. Check what disappears at the paid line.",
          ]} />
          <P>
            Look past the sticker. Onboarding fees, payment processing spread, per-text charges, extra cost per additional location, and the annual increase after year one are where the real number lives. Ask for the total first-year cost in writing.
          </P>

          <H2 id="mistakes">Common mistakes when buying</H2>
          <UL items={[
            "Choosing the cheapest. The cost of software is small next to the cost of a team that will not use it.",
            "Ignoring support. Ask what happens at 6am on a Saturday when dispatch will not load.",
            "Skipping onboarding. Data migration and a price book take real hours. Budget them or the rollout stalls.",
            "Accepting a weak mobile app because the office screens looked good.",
            "Buying on the estimate feature and discovering the scheduler cannot handle multi-day work.",
            "Not checking integrations until after signing.",
            "Rolling it out to everyone at once with no pilot crew and no internal owner.",
          ]} />

          <H2 id="faq">Frequently asked questions</H2>
          <div style={{ display: "grid", gap: 10, margin: "22px 0 0" }}>
            {ART_FAQ.map(([q, a]) => (
              <details key={q} style={{ padding: "16px 20px", border: "1px solid var(--border)", borderRadius: "var(--radius-lg)", background: "var(--gray-50)" }}>
                <summary style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 16, color: "var(--text-strong)", cursor: "pointer", lineHeight: 1.4 }}>{q}</summary>
                <p style={{ margin: "10px 0 0", fontSize: 15.5, lineHeight: 1.65, color: "var(--text-body)" }}>{a}</p>
              </details>
            ))}
          </div>

          <H2 id="compare">The main options on the market</H2>
          <P>
            Every platform here has customers who are happy and customers who left. The honest summary is that they are built around different shapes of business, so the useful exercise is matching the shape, not ranking them.
          </P>
          <div style={{ border: "1px solid var(--border)", borderRadius: "var(--radius-lg)", overflow: "hidden", margin: "22px 0 8px" }}>
            {ART_COMPARE.map(([n, d], i) => (
              <div key={n} style={{ display: "grid", gridTemplateColumns: "180px 1fr", gap: 18, padding: "14px 18px", borderTop: i ? "1px solid var(--border)" : "none", background: i % 2 ? "var(--gray-50)" : "var(--surface-card)" }}>
                <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 15.5, color: "var(--text-strong)" }}>{n}</div>
                <div style={{ fontSize: 15, lineHeight: 1.6, color: "var(--text-muted)" }}>{d}</div>
              </div>
            ))}
          </div>
          <P>
            Backentra is being built for the operator who wants one connected system with published pricing and no per-seat penalty for hiring. We are in development and we say so, on every page of this site.
          </P>

          <H2 id="conclusion">Conclusion: how to actually decide</H2>
          <P>
            Write down the three things that cost you the most this year. Late invoices, a schedule nobody trusts, estimates that never got followed up. Then run two demos and make each vendor do those three things live with your own job, your own crew and your own phone. The platform that handles your worst week is the right one, whatever the feature grid says.
          </P>
          <P>
            Every business is shaped differently, and the best software is the one your team opens without being told to. If what you have read here describes your operation, we would like to show you what we are building.
          </P>

          <div style={{ margin: "36px 0 0", padding: "28px 30px", borderRadius: "var(--radius-xl)", background: "var(--navy-900)" }}>
            <div style={{ ...beyebrow, color: "var(--accent-text)", marginBottom: 10 }}>Built for business</div>
            <h3 style={{ color: "#fff", fontSize: 26, margin: "0 0 10px", lineHeight: 1.2 }}>See it before launch.</h3>
            <p style={{ color: "var(--gray-300)", fontSize: 15.5, lineHeight: 1.6, margin: "0 0 20px", maxWidth: 560 }}>
              Founding members lock their rate and help shape what gets built next. No card, no sales sequence.
            </p>
            <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
              <BLBtn variant="primary" onClick={onJoin} iconRight={<BLIcon name="arrow-right" size={18} />}>Join the list</BLBtn>
              <button type="button" onClick={() => onNav && onNav("plans")} style={{ display: "inline-flex", alignItems: "center", height: 44, padding: "0 20px", borderRadius: "var(--radius-md)", background: "transparent", border: "1px solid var(--border-inverse)", color: "#fff", fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 15, cursor: "pointer" }}>See pricing</button>
            </div>
          </div>

          <div style={{ marginTop: 34, paddingTop: 24, borderTop: "1px solid var(--border)" }}>
            <div style={{ ...beyebrow, marginBottom: 12 }}>Keep reading</div>
            <div style={{ display: "flex", flexWrap: "wrap", gap: 9 }}>
              {ART_LINKS.map(([label, key], i) => (
                <a key={label + i} href={bP2P(key)} onClick={go(key)} style={{ display: "inline-flex", alignItems: "center", height: 34, padding: "0 14px", borderRadius: "var(--radius-pill)", background: "var(--gray-50)", border: "1px solid var(--border)", fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 13.5, color: "var(--text-body)" }}>{label}</a>
              ))}
            </div>
          </div>
        </div>
      </article>

      <window.BackentraSiteFooter onNav={onNav} />
    </div>
  );
}

/* ---------- article 2: the vision ---------- */
const V_TOC = [
  ["v-intro", "Introduction"],
  ["v-why", "Why we started with home services"],
  ["v-grow", "A platform built to grow"],
  ["v-more", "More than a CRM"],
  ["v-integrations", "Why integrations matter today"],
  ["v-before", "Building before replacing"],
  ["v-real", "Built around real operations"],
  ["v-ahead", "Looking ahead"],
  ["v-journey", "Why we are sharing the journey"],
];

const V_INDUSTRIES = [
  ["building", "Property management", "Units, tenants, work orders and recurring maintenance."],
  ["utensils", "Restaurants", "Shifts, vendors, prep schedules and store-level reporting."],
  ["shopping-bag", "Retail", "Staffing, inventory and multi-location numbers in one view."],
  ["briefcase", "Professional services", "Clients, engagements, time and billing."],
  ["truck", "Field services", "Anything dispatched: security, inspection, install, repair."],
  ["bed", "Hospitality", "Housekeeping rounds, maintenance tickets and vendor work."],
  ["heart-pulse", "Healthcare support", "Non-clinical operations: transport, facilities, staffing."],
  ["factory", "Manufacturing", "Work orders, job costing and shop-floor scheduling."],
  ["package", "Logistics", "Routes, drivers, assets and proof of delivery."],
  ["hand-heart", "Nonprofits", "Programs, volunteers, grants and donor records."],
];

const V_STACK = [
  "Customer management", "Scheduling", "Team management", "Internal communication",
  "Financial tracking", "Reporting", "Marketing", "Documents", "HR", "Operations",
];

const V_LINKS = [
  ["Product", "product"], ["Pricing", "plans"], ["Product", "product"],
  ["Founding members", "founding"], ["Enterprise", "enterprise"], ["About", "about"],
  ["Blog", "blog"],
];

function BlogPostVision({ onNav, onJoin }) {
  const go = (k) => (e) => { e.preventDefault(); onNav && onNav(k); };
  React.useEffect(() => {
    window.BackentraSetMeta && window.BackentraSetMeta({
      path: "/blog/vision",
      title: "From Home Services to a Complete Business Operating System: The Vision Behind Backentra — Backentra Blog",
      description: "Why we are starting with home service businesses, how the platform is built to grow beyond them, and why we integrate with the tools you already run before replacing anything.",
    });
    window.BackentraAnalytics && window.BackentraAnalytics.trackEvent("blog_view", { slug: "vision" });
  }, []);
  return (
    <div style={{ background: "var(--surface-card)", minHeight: "100vh" }}>
      <window.BackentraSiteHeader current="blog" onNav={onNav} />

      <section style={{ background: "var(--navy-900)", padding: "56px 0 48px" }}>
        <div style={{ ...bcontainer, maxWidth: 900 }}>
          <a href="/blog" onClick={go("blog")} style={{ display: "inline-flex", alignItems: "center", gap: 8, ...beyebrow, color: "var(--accent-text)", marginBottom: 18 }}>
            <BLIcon name="arrow-left" size={14} /> Backentra Blog
          </a>
          <h1 style={{ color: "#fff", fontSize: 46, lineHeight: 1.07, margin: "0 0 18px", letterSpacing: "-0.02em" }}>
            From Home Services to a Complete Business Operating System: The Vision Behind Backentra
          </h1>
          <p style={{ color: "var(--gray-300)", fontSize: 19, lineHeight: 1.6, margin: "0 0 26px", maxWidth: 760 }}>
            Why we are starting with home service businesses, how the platform is built to grow beyond them, and why we integrate with the tools you already run before we replace anything.
          </p>
          <div style={{ display: "flex", alignItems: "center", gap: 18, flexWrap: "wrap", paddingTop: 20, borderTop: "1px solid var(--border-inverse)" }}>
            {[["Written by the Backentra Team", "users"], ["July 2026", "calendar-days"], ["14 min read", "clock"]].map(([t, ic]) => (
              <span key={t} style={{ display: "inline-flex", alignItems: "center", gap: 8, color: "var(--gray-300)", fontSize: 13.5 }}>
                <BLIcon name={ic} size={15} color="var(--accent-text)" /> {t}
              </span>
            ))}
          </div>
        </div>
      </section>

      <section style={{ padding: "40px 0 0" }}>
        <div style={artWrap}>
          <div style={{ padding: "24px 26px", border: "1px solid var(--border)", borderRadius: "var(--radius-lg)", background: "var(--gray-50)" }}>
            <div style={{ ...beyebrow, marginBottom: 14 }}>Table of contents</div>
            <ol style={{ margin: 0, padding: 0, listStyle: "none", display: "grid", gridTemplateColumns: "1fr 1fr", gap: "9px 26px" }}>
              {V_TOC.map(([id, label], i) => (
                <li key={id} style={{ display: "flex", gap: 10, fontSize: 15, lineHeight: 1.45 }}>
                  <span style={{ fontFamily: "var(--font-mono)", fontSize: 12.5, color: "var(--text-subtle)", flex: "none", paddingTop: 2 }}>{String(i + 1).padStart(2, "0")}</span>
                  <a href={"#" + id} style={{ color: "var(--text-body)", fontWeight: 500 }}>{label}</a>
                </li>
              ))}
            </ol>
          </div>
        </div>
      </section>

      <article style={{ padding: "8px 0 64px" }}>
        <div style={artWrap}>
          <H2 id="v-intro">Introduction</H2>
          <P>
            Backentra is launching with home service businesses, and that is a deliberate choice rather than a limit. It is the work we know from the inside: the phone ringing while a crew waits on a gate code, the estimate that sat unanswered for three weeks, the invoice that went out late because the notes never made it back to the office.
          </P>
          <P>
            Those businesses are the foundation of what we are building. They are not the finish line. What we are actually building is a business operating system: one connected place where the customer, the schedule, the work, the team and the money live together. Home services is where we prove it.
          </P>

          <H2 id="v-why">Why we started with home services</H2>
          <P>
            We did not pick this market off a chart. We came out of it. Years of office operations: booking jobs into a calendar that three people were editing, dispatching crews who needed to know what equipment was already committed, chasing estimates, handling the customer who called back nine months later expecting you to remember the property.
          </P>
          <UL items={[
            "Years spent inside office operations, not observing them from a distance.",
            "Scheduling jobs around crews, travel, weather and promised windows.",
            "Dispatching crews with the right people, skills and equipment on the truck.",
            "Building and chasing estimates, and learning that follow-up is the cheapest revenue there is.",
            "Handling customer communication across a cell phone, a shared inbox and a notepad.",
            "Running the day-to-day: payroll hours, materials, invoices, collections.",
          ]} />
          <P>
            The decision was simple: solve the problems we understand firsthand before expanding into industries where we would be guessing. Software built by people who have not done the work always shows, usually in the one screen you use forty times a day.
          </P>
          <Pull>Start where you have scar tissue. Expand where the same structure fits.</Pull>

          <H2 id="v-grow">A platform built to grow</H2>
          <P>
            Today's focus is home services. The architecture underneath is being designed for expansion, because the shape of the problem repeats: a customer or account, work to be scheduled, people to assign, a record of what happened, money in and out, and numbers the owner needs on Monday morning.
          </P>
          <P>
            Industries where that same structure fits, and where we can see Backentra going over time:
          </P>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, margin: "22px 0 8px" }}>
            {V_INDUSTRIES.map(([ic, t, d]) => (
              <div key={t} style={{ display: "flex", gap: 12, padding: "14px 16px", border: "1px solid var(--border)", borderRadius: "var(--radius-lg)", background: "var(--surface-card)" }}>
                <span style={{ width: 34, height: 34, borderRadius: "var(--radius-md)", background: "var(--orange-50)", color: "var(--accent-text)", display: "inline-flex", alignItems: "center", justifyContent: "center", flex: "none" }}><BLIcon name={ic} size={17} /></span>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 15, color: "var(--text-strong)", marginBottom: 2 }}>{t}</div>
                  <div style={{ fontSize: 14, lineHeight: 1.55, color: "var(--text-muted)" }}>{d}</div>
                </div>
              </div>
            ))}
          </div>
          <P>
            To be clear about where we are: none of those are available today, and we will not announce one before it works. The point is that the underlying system is being built to support many types of business over time, rather than being bent into shape later.
          </P>

          <H2 id="v-more">More than a CRM</H2>
          <P>
            A traditional CRM tracks relationships. It knows who your customers are and where they sit in a pipeline. That is useful, and it is a fraction of what running a company requires. Most operators we talk to are paying for five to nine separate tools:
          </P>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 9, margin: "20px 0 18px" }}>
            {V_STACK.map((t) => (
              <span key={t} style={{ display: "inline-flex", alignItems: "center", height: 34, padding: "0 14px", borderRadius: "var(--radius-pill)", background: "var(--gray-100)", border: "1px solid var(--border)", fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 13.5, color: "var(--text-body)" }}>{t}</span>
            ))}
          </div>
          <P>
            Each one is fine on its own. The cost is in the seams: a schedule change that does not reach accounting, a customer complaint nobody in the office can see, hours entered twice, reports that need a spreadsheet to reconcile. The long-term goal is to bring those functions into one connected platform where information is captured once and carries the rest of the way.
          </P>
          <P>
            That will be built incrementally. We are not claiming a finished operating system, and anyone who tells you they have one in a category this broad is selling a roadmap as a product. We would rather ship it a module at a time and say plainly which ones are ready.
          </P>

          <H2 id="v-integrations">Why integrations matter today</H2>
          <P>
            Integrations are not a checklist on a comparison page. They are the reason a business can adopt new software without a shutdown week. Your bookkeeper already has a chart of accounts. Your team already has a calendar. Your processor already handles your deposits. None of that has to move for Backentra to be useful.
          </P>
          <UL items={[
            "Accounting software, so invoices and payments land where your bookkeeper already works.",
            "Payroll platforms, fed by hours that were tracked on the job instead of retyped.",
            "Payment processing, so money moves the way it does today.",
            "Communication tools, so calls and texts attach to the customer record.",
            "Calendar systems, so the schedule is visible where your team already looks.",
            "Fleet management, so vehicles and equipment are part of the plan.",
            "Marketing platforms, so lead source survives all the way to the closed job.",
          ]} />
          <P>
            Done properly, these remove duplicate work rather than adding a sync you have to babysit. That is the test we hold ourselves to: after connecting, is there anything a person still types twice?
          </P>

          <H2 id="v-before">Building before replacing</H2>
          <P>
            Our long-term vision is not to force businesses to abandon the software they rely on overnight. We are starting by integrating with industry-leading platforms so companies can keep working the way they do today while gradually adopting more of Backentra's capabilities. Over time we will keep expanding our own native features, giving businesses the freedom to choose what works best for them.
          </P>
          <P>
            In practice that means you should be able to run Backentra alongside what you have, move one function at a time, and stop wherever it makes sense for your company. Some operators will end up using our native tools for everything. Others will keep their accountant on the platform they have used for fifteen years, and that is a legitimate outcome, not a failure of the product.
          </P>
          <Pull>Integrate first. Replace only when ours is genuinely better for you.</Pull>

          <H2 id="v-real">Built around real operations</H2>
          <P>
            Every design decision gets measured against the same question: does this reduce the amount of work between the job happening and the business knowing about it? The principles we hold to:
          </P>
          <UL items={[
            "Reduce repetitive work. If a person does the same three clicks daily, that is our problem to fix.",
            "Minimize duplicate data entry. Capture information at the earliest point that knows it.",
            "Make information easier to find. One customer, one timeline, no archaeology.",
            "Improve communication between the field and the office, so both see the same record at the same second.",
            "Keep operations organized without adding process for its own sake.",
            "Help businesses scale without adding complexity, so hiring a fourth crew does not require a new system.",
          ]} />

          <H2 id="v-ahead">Looking ahead</H2>
          <P>
            These are areas of active development, not guaranteed timelines. We publish what is built, what is being built, and what is only planned, and we would rather be slow than dishonest about it.
          </P>
          <UL items={[
            "Artificial intelligence used for drafting, summarizing and proposing, with a human approving.",
            "Workflow automation you can configure yourself.",
            "Industry-specific capabilities, starting with the trades we know best.",
            "Expanded integrations across accounting, payroll, communications and marketing.",
            "Better analytics: job costing, service-line margin, and the numbers an owner actually acts on.",
            "Smarter scheduling that accounts for travel, skill, equipment and promised windows.",
            "Additional business management tools as the platform widens.",
          ]} />

          <H2 id="v-journey">Why we are sharing the journey</H2>
          <P>
            We want this blog to document how Backentra evolves: the decisions behind new features, the tradeoffs we make, and the lessons we learn along the way. Whether you are a customer, a business owner, or simply interested in how modern software gets built, we hope these articles are useful about where we are headed and why we are building the platform the way we are.
          </P>
          <P>
            If you run a service business and any of this sounds like your week, we would rather hear from you early than launch and guess. Founding members help decide what gets built next.
          </P>

          <div style={{ margin: "36px 0 0", padding: "28px 30px", borderRadius: "var(--radius-xl)", background: "var(--navy-900)" }}>
            <div style={{ ...beyebrow, color: "var(--accent-text)", marginBottom: 10 }}>Built for business</div>
            <h3 style={{ color: "#fff", fontSize: 26, margin: "0 0 10px", lineHeight: 1.2 }}>Help shape what gets built next.</h3>
            <p style={{ color: "var(--gray-300)", fontSize: 15.5, lineHeight: 1.6, margin: "0 0 20px", maxWidth: 560 }}>
              Founding members lock their rate and get a direct line to the team building it. No card, no sales sequence.
            </p>
            <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
              <BLBtn variant="primary" onClick={onJoin} iconRight={<BLIcon name="arrow-right" size={18} />}>Join the list</BLBtn>
              <button type="button" onClick={() => onNav && onNav("product")} style={{ display: "inline-flex", alignItems: "center", height: 44, padding: "0 20px", borderRadius: "var(--radius-md)", background: "transparent", border: "1px solid var(--border-inverse)", color: "#fff", fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 15, cursor: "pointer" }}>See the product</button>
            </div>
          </div>

          <div style={{ marginTop: 34, paddingTop: 24, borderTop: "1px solid var(--border)" }}>
            <div style={{ ...beyebrow, marginBottom: 12 }}>Keep reading</div>
            <div style={{ display: "flex", flexWrap: "wrap", gap: 9 }}>
              <a href={bP2P("blog-crm-guide")} onClick={go("blog-crm-guide")} style={{ display: "inline-flex", alignItems: "center", height: 34, padding: "0 14px", borderRadius: "var(--radius-pill)", background: "var(--orange-50)", border: "1px solid var(--orange-200)", fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 13.5, color: "var(--accent-text)" }}>The Ultimate Guide to CRM Software</a>
              {V_LINKS.map(([label, key]) => (
                <a key={label} href={bP2P(key)} onClick={go(key)} style={{ display: "inline-flex", alignItems: "center", height: 34, padding: "0 14px", borderRadius: "var(--radius-pill)", background: "var(--gray-50)", border: "1px solid var(--border)", fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 13.5, color: "var(--text-body)" }}>{label}</a>
              ))}
            </div>
          </div>
        </div>
      </article>

      <window.BackentraSiteFooter onNav={onNav} />
    </div>
  );
}

window.BackentraBlogPostVision = BlogPostVision;

/* Can `onNav` actually reach this blog key? Everything in the registry can, and so can the
   two articles site.jsx routes to their own bespoke components — those are not in BL_ARTICLES
   because they are not data-driven, which used to mean a "Keep reading" link to either of
   them was silently dropped. That was invisible while every `related` list was hand-written
   next to the article; it stopped being invisible when the console started offering
   "blog-<address>" as something an author can type. */
const BL_BESPOKE = ["vision", "crm-guide"];
const blogKeyExists = (key) => {
  const k = String(key).replace(/^blog-/, "");
  return !!BL_ARTICLES[k] || BL_BESPOKE.indexOf(k) >= 0;
};

/* ---------- generic data-driven article ---------- */
function BlogArticle({ post, onNav, onJoin }) {
  const go = (k) => (e) => { e.preventDefault(); onNav && onNav(k); };
  React.useEffect(() => {
    window.BackentraSetMeta && window.BackentraSetMeta({
      path: "/blog/" + post.key,
      title: post.title + " — Backentra Blog",
      description: post.dek,
    });
    window.BackentraAnalytics && window.BackentraAnalytics.trackEvent("blog_view", { slug: post.key });
  }, [post.key]);
  const toc = (post.blocks || []).filter((b) => b[0] === "h2");
  /* A post missing its related list should lose a section, not unmount the site. Links whose
     target does not exist are dropped rather than rendered: a dead link is worse than one
     fewer, and slugs do get renamed. */
  const related = (post.related || []).filter(([, key]) => key.indexOf("blog-") !== 0 || blogKeyExists(key));
  return (
    <div style={{ background: "var(--surface-card)", minHeight: "100vh" }}>
      <window.BackentraSiteHeader current="blog" onNav={onNav} />

      <section style={{ background: "var(--navy-900)", padding: "56px 0 48px" }}>
        <div style={{ ...bcontainer, maxWidth: 900 }}>
          <a href="/blog" onClick={go("blog")} style={{ display: "inline-flex", alignItems: "center", gap: 8, ...beyebrow, color: "var(--accent-text)", marginBottom: 18 }}>
            <BLIcon name="arrow-left" size={14} /> Backentra Blog
          </a>
          <h1 style={{ color: "#fff", fontSize: 44, lineHeight: 1.08, margin: "0 0 18px", letterSpacing: "-0.02em" }}>{post.title}</h1>
          <p style={{ color: "var(--gray-300)", fontSize: 19, lineHeight: 1.6, margin: "0 0 26px", maxWidth: 760 }}>{post.dek}</p>
          <div style={{ display: "flex", alignItems: "center", gap: 18, flexWrap: "wrap", paddingTop: 20, borderTop: "1px solid var(--border-inverse)" }}>
            {[["Written by the Backentra Team", "users"], [post.date, "calendar-days"], [post.read, "clock"]].map(([t, ic]) => (
              <span key={t} style={{ display: "inline-flex", alignItems: "center", gap: 8, color: "var(--gray-300)", fontSize: 13.5 }}>
                <BLIcon name={ic} size={15} color="var(--accent-text)" /> {t}
              </span>
            ))}
          </div>
        </div>
      </section>

      <section style={{ padding: "40px 0 0" }}>
        <div style={artWrap}>
          <div style={{ padding: "24px 26px", border: "1px solid var(--border)", borderRadius: "var(--radius-lg)", background: "var(--gray-50)" }}>
            <div style={{ ...beyebrow, marginBottom: 14 }}>Table of contents</div>
            <ol style={{ margin: 0, padding: 0, listStyle: "none", display: "grid", gridTemplateColumns: "1fr 1fr", gap: "9px 26px" }}>
              {toc.map(([, id, label], i) => (
                <li key={id} style={{ display: "flex", gap: 10, fontSize: 15, lineHeight: 1.45 }}>
                  <span style={{ fontFamily: "var(--font-mono)", fontSize: 12.5, color: "var(--text-subtle)", flex: "none", paddingTop: 2 }}>{String(i + 1).padStart(2, "0")}</span>
                  <a href={"#" + id} style={{ color: "var(--text-body)", fontWeight: 500 }}>{label}</a>
                </li>
              ))}
            </ol>
          </div>
        </div>
      </section>

      <article style={{ padding: "8px 0 64px" }}>
        <div style={artWrap}>
          {post.blocks.map((b, i) => {
            const [kind, a, c] = b;
            if (kind === "h2") return <H2 key={i} id={a}>{c}</H2>;
            if (kind === "h3") return <H3 key={i}>{a}</H3>;
            if (kind === "p") return <P key={i}>{a}</P>;
            if (kind === "ul") return <UL key={i} items={a} />;
            if (kind === "pull") return <Pull key={i}>{a}</Pull>;
            if (kind === "links") return (
              <div key={i} style={{ display: "grid", gap: 8, margin: "4px 0 20px" }}>
                {a.filter(([, key]) => key.indexOf("blog-") !== 0 || blogKeyExists(key)).map(([label, key]) => (
                  <a key={key} href={bP2P(key)} onClick={(e) => { e.preventDefault(); onNav && onNav(key); }}
                    style={{ display: "flex", alignItems: "center", gap: 10, padding: "12px 15px", borderRadius: "var(--radius-md)",
                      background: "var(--gray-50)", border: "1px solid var(--border)", textDecoration: "none",
                      fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 15, color: "var(--text-strong)" }}>
                    <BLIcon name="arrow-right" size={16} color="var(--accent-text)" />
                    <span style={{ minWidth: 0 }}>{label}</span>
                  </a>
                ))}
              </div>
            );
            if (kind === "chips") return (
              <div key={i} style={{ display: "flex", flexWrap: "wrap", gap: 9, margin: "20px 0 18px" }}>
                {a.map((t) => <span key={t} style={{ display: "inline-flex", alignItems: "center", height: 34, padding: "0 14px", borderRadius: "var(--radius-pill)", background: "var(--gray-100)", border: "1px solid var(--border)", fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 13.5, color: "var(--text-body)" }}>{t}</span>)}
              </div>
            );
            if (kind === "cards") return (
              <div key={i} style={{ display: "grid", gap: 12, margin: "22px 0 8px" }}>
                {a.map(([ic, t, d]) => (
                  <div key={t} style={{ display: "flex", gap: 14, padding: "16px 18px", border: "1px solid var(--border)", borderRadius: "var(--radius-lg)", background: "var(--surface-card)" }}>
                    <span style={{ width: 38, height: 38, borderRadius: "var(--radius-md)", background: "var(--orange-50)", color: "var(--accent-text)", display: "inline-flex", alignItems: "center", justifyContent: "center", flex: "none" }}><BLIcon name={ic} size={19} /></span>
                    <div style={{ minWidth: 0 }}>
                      <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 16, color: "var(--text-strong)", marginBottom: 3 }}>{t}</div>
                      <div style={{ fontSize: 15, lineHeight: 1.6, color: "var(--text-muted)" }}>{d}</div>
                    </div>
                  </div>
                ))}
              </div>
            );
            return null;
          })}

          <div style={{ margin: "36px 0 0", padding: "28px 30px", borderRadius: "var(--radius-xl)", background: "var(--navy-900)" }}>
            <div style={{ ...beyebrow, color: "var(--accent-text)", marginBottom: 10 }}>Built for business</div>
            <h3 style={{ color: "#fff", fontSize: 26, margin: "0 0 10px", lineHeight: 1.2 }}>{post.ctaTitle}</h3>
            <p style={{ color: "var(--gray-300)", fontSize: 15.5, lineHeight: 1.6, margin: "0 0 20px", maxWidth: 560 }}>{post.ctaBody}</p>
            <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
              <BLBtn variant="primary" onClick={onJoin} iconRight={<BLIcon name="arrow-right" size={18} />}>Join the list</BLBtn>
              <button type="button" onClick={() => onNav && onNav(post.ctaKey)} style={{ display: "inline-flex", alignItems: "center", height: 44, padding: "0 20px", borderRadius: "var(--radius-md)", background: "transparent", border: "1px solid var(--border-inverse)", color: "#fff", fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 15, cursor: "pointer" }}>{post.ctaLabel}</button>
            </div>
          </div>

          <div style={{ marginTop: 34, paddingTop: 24, borderTop: "1px solid var(--border)" }}>
            <div style={{ ...beyebrow, marginBottom: 12 }}>Keep reading</div>
            <div style={{ display: "flex", flexWrap: "wrap", gap: 9 }}>
              {related.map(([label, key], i) => (
                <a key={label + i} href={bP2P(key)} onClick={go(key)} style={{ display: "inline-flex", alignItems: "center", height: 34, padding: "0 14px", borderRadius: "var(--radius-pill)", background: key.indexOf("blog-") === 0 ? "var(--orange-50)" : "var(--gray-50)", border: "1px solid " + (key.indexOf("blog-") === 0 ? "var(--orange-200)" : "var(--border)"), fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 13.5, color: key.indexOf("blog-") === 0 ? "var(--accent)" : "var(--text-body)" }}>{label}</a>
              ))}
            </div>
          </div>
        </div>
      </article>

      <window.BackentraSiteFooter onNav={onNav} />
    </div>
  );
}

const BL_ARTICLES = {
  "hidden-cost": {
    title: "The Hidden Cost of Running a Business Across Too Many Platforms",
    dek: "Duplicate entry, five logins, stale information in the field and a subscription bill nobody audits. What disconnected systems actually cost a service business, and how to close the gaps.",
    date: "July 2026", read: "16 min read",
    ctaTitle: "See how the pieces connect.",
    ctaBody: "Backentra is being built so the schedule, the job, the invoice and the books stop being four separate systems. Founding members see it first.",
    ctaKey: "product", ctaLabel: "See the product",
    related: [["Why Backentra Is Starting With Integrations", "blog-integrations-first"], ["The Ultimate Guide to CRM Software", "blog-crm-guide"], ["Product", "product"], ["Product", "product"], ["Pricing", "plans"], ["Founding members", "founding"]],
    blocks: [
      ["p", "Nobody chooses a disconnected stack. It accumulates. A scheduling tool because the whiteboard failed, a spreadsheet because the scheduler could not do estimates, an accounting package because the bookkeeper needed one, a texting app because customers stopped answering the phone. Each was the right call at the time. Together they cost more than any of them individually."],
      ["p", "This article is not an argument for replacing everything with one platform. It is an honest accounting of what the seams between systems cost, because most owners have never added it up."],
      ["h2", "duplicate", "Entering the same information multiple times"],
      ["p", "Start with the most measurable cost. A single job in a typical five-tool operation gets typed in three to five times: into the scheduler, into the estimate, into the invoice, into accounting, and into a payroll timesheet. None of those keystrokes create value. They exist because the systems do not talk."],
      ["p", "Put a number on it. If an office manager spends ninety seconds per job re-entering what already exists, and you run twenty jobs a day, that is thirty hours a month of pure retyping. At any wage, that is more than the software costs."],
      ["pull", "Duplicate entry is not just slow. Every retype is a chance to be wrong, and the wrong version is the one the crew sees."],
      ["h2", "switching", "Switching between calendars, accounting, messaging and job systems"],
      ["p", "Context switching is the cost nobody bills for. Answering one customer question can mean four tabs: the calendar for the appointment, the job system for the scope, accounting for the balance, and the messaging app for what was actually promised. The customer hears hold music while someone assembles an answer that should have been on one screen."],
      ["ul", ["Five logins, five password resets, five places to check when something looks wrong.", "New hires trained on a stack instead of a system, which stretches onboarding by weeks.", "No single place to answer the question every owner asks: what is the status of this job?"]],
      ["h2", "scattered", "Customer information stored in different places"],
      ["p", "The customer record is where fragmentation hurts most. The phone number lives in one tool, the property notes in another, the history of what you did in 2024 in a folder on someone's desktop, and the complaint from last spring in a text thread on a personal cell."],
      ["p", "When a customer calls back, whoever picks up either knows all of it or none of it. Usually none of it. That is the moment a repeat customer decides whether you are the company that remembers them."],
      ["h2", "stale", "Employees working from outdated information"],
      ["p", "A crew that left the yard with yesterday's schedule is a crew doing yesterday's work. Every manual sync step, an exported PDF, a screenshot in a group text, a printed route, is a point where the field and the office fall out of agreement."],
      ["ul", ["Crews arriving for work that was rescheduled or canceled that morning.", "Two crews dispatched to one address because two people edited two calendars.", "Change orders agreed in the field that never reached the invoice.", "Techs calling the office for information they should be able to see."]],
      ["h2", "followups", "Missed follow-ups and incomplete records"],
      ["p", "Follow-up is the cheapest revenue a service business has, and it is the first thing a disconnected stack drops. An estimate sent from one tool has no memory of itself. If nobody sets a reminder somewhere else, the quote simply expires in silence."],
      ["p", "The same gap swallows maintenance renewals, seasonal work, warranty visits and every customer who said call me in the spring. None of it is lost on purpose. It is lost because no single system was responsible for remembering."],
      ["h2", "subscriptions", "Subscription costs that quietly accumulate"],
      ["p", "Pull your card statement and list every business software charge. Most operators are surprised twice: by the total, and by the two or three tools nobody uses anymore. Per-seat pricing compounds this every time you hire, and each tool has its own annual increase."],
      ["p", "The real number is not the sum of the subscriptions. It is the subscriptions plus the labor hours the gaps between them consume."],
      ["h2", "reporting", "Reporting problems caused by disconnected data"],
      ["p", "This is the cost that limits the business rather than annoying it. Simple questions become projects: which service line actually made money last quarter, what did that job cost against what we quoted, which lead source produced work instead of noise."],
      ["p", "When revenue lives in accounting, hours in payroll, materials in a spreadsheet and job outcomes in a scheduler, the only way to answer is to build a spreadsheet by hand. So it gets built once a year, or never, and pricing decisions get made on instinct."],
      ["h2", "integrations", "How integrations help connect existing tools"],
      ["p", "The fastest relief is usually not replacement. It is connection. A properly built integration means information is captured once and flows outward: hours tracked on the job feed payroll, invoices land in accounting with the right accounts, calendar changes appear everywhere at once."],
      ["ul", ["Accounting, so invoices and payments reach your bookkeeper without retyping.", "Payroll, fed by tracked hours instead of a transcribed timesheet.", "Payments, so a card taken in the field settles and reconciles itself.", "Calendars, so one schedule is visible wherever your team already looks.", "Communications, so calls and texts attach to the customer record automatically."]],
      ["p", "The test of an integration is simple: after connecting it, is there anything a person still types twice? If yes, it is a link, not an integration."],
      ["h2", "unified", "Where we are going with this"],
      ["p", "Our long-term goal is a more unified system: one connected place where the customer, the schedule, the work, the team and the money live together, so the seams stop being where things go missing. That gets built incrementally, and we say plainly what is ready."],
      ["p", "In the meantime, the practical advice stands on its own. Count your retypes, count your logins, audit your subscriptions, and fix the largest gap first. That is worth doing whatever software you end up running."],
    ],
  },
  "integrations-first": {
    title: "Why Backentra Is Starting With Integrations",
    dek: "Your accountant, your calendar and your processor already work. Here is why we connect to them first, how we decide what becomes native, and what separates a real integration from a link.",
    date: "July 2026", read: "13 min read",
    ctaTitle: "Tell us what you already run.",
    ctaBody: "Founding members tell us which platforms they depend on, and that shapes what we connect next. No card, no sales sequence.",
    ctaKey: "product", ctaLabel: "See the product",
    related: [["The Hidden Cost of Too Many Platforms", "blog-hidden-cost"], ["The Vision Behind Backentra", "blog-vision"], ["Product", "product"], ["Pricing", "plans"], ["Product", "product"], ["Enterprise", "enterprise"]],
    blocks: [
      ["p", "Most new platforms open with a demand: move everything over, and do it this quarter. We are starting from the opposite position. The software you already run works, your team knows it, your bookkeeper has fifteen years of history in it, and none of that has to move for Backentra to be useful."],
      ["h2", "i-established", "Businesses already rely on established platforms"],
      ["p", "A service business at three crews is not a blank slate. There is a chart of accounts somebody set up carefully, a payroll provider that files your taxes, a processor whose deposit timing you have planned cash flow around, and a calendar your whole team checks. Those are not tools. They are habits with money attached."],
      ["p", "Asking an owner to replace all of it at once is asking them to accept a bad month in exchange for a promise. Almost nobody should take that trade."],
      ["h2", "i-disruption", "Changing every system at once creates unnecessary disruption"],
      ["p", "Migrations fail in predictable ways: data lands wrong and nobody notices for a quarter, the crew keeps using the old thing, the office runs both systems in parallel out of fear, and by week three the new platform is the extra work rather than the fix."],
      ["pull", "A rollout that can be done one function at a time is a rollout that finishes."],
      ["h2", "i-familiar", "Integrations let companies keep using familiar tools"],
      ["p", "Connecting instead of replacing changes the decision from a bet to a trial. Put your schedule and jobs in Backentra while accounting stays exactly where it is. If that works, move the next thing. If it does not, you have lost a week rather than a quarter."],
      ["h2", "i-manual", "Connected data reduces manual entry"],
      ["p", "The point of every connection is the same: capture information at the earliest place that knows it, then let it flow. Hours tracked on the job become payroll. A signed estimate becomes a scheduled job becomes an invoice becomes a payment in accounting. Nobody transcribes a step."],
      ["ul", ["One customer record that accounting, the office and the field all see.", "Invoices and payments landing in your books mapped to the right accounts.", "Payroll fed by real tracked time rather than a retyped timesheet.", "Calendar changes appearing everywhere the moment dispatch makes them."]],
      ["h2", "i-layer", "Backentra as the central operational layer"],
      ["p", "There is a difference between being another tool in the stack and being the layer the stack reports into. Our aim is the second: the place where a job is planned, executed and closed, with the specialist systems around it fed automatically."],
      ["p", "That is why the job record is the center of the product rather than the pipeline. Accounting is excellent at accounting. Almost nothing on the market is excellent at holding an entire job from first call to final payment."],
      ["h2", "i-why-integration", "Why some capabilities make more sense as integrations first"],
      ["p", "Some categories are deep, regulated, or both. Payroll tax filing, card processing compliance, tax reporting: these are years of work and real liability, and doing them badly is worse than not doing them."],
      ["p", "So the rule we use is straightforward. If a category is deep and well served, we integrate. If the gap is in how a job actually moves through a company, we build it, because that is the part nobody else is treating as one thing."],
      ["h2", "i-native", "How native features may expand over time"],
      ["p", "We will keep expanding our own capabilities, and we will not remove your ability to choose. Some operators will end up using Backentra for everything. Others will keep their accountant on the platform they have always used, and that is a legitimate outcome rather than a product failure."],
      ["h2", "i-real", "Connecting software versus linking out to it"],
      ["p", "Plenty of integration pages are a logo grid. A logo means very little on its own, so here is the vocabulary worth using on any demo, ours included."],
      ["cards", [["link", "A link", "A button that opens the other tool in a new tab. Nothing is shared. This is a bookmark."], ["arrow-right", "One-way push", "Records are sent one direction, usually on a schedule. Useful, but conflicts and edits on the far side are your problem."], ["refresh-cw", "Two-way sync", "Both systems stay current, with defined rules for which one wins a conflict. This is what most people mean by integration."], ["layers", "Native", "One system, one record, no sync to reason about. The best experience, and the most work to build responsibly."]]],
      ["p", "Ask any vendor which of those four they mean, per integration. The answer tells you more than the feature list."],
      ["h2", "i-decide", "How integration decisions are made"],
      ["ul", ["What are founding members already paying for? Those come first.", "How many people does the connection take out of the retyping loop?", "Is the platform's API stable enough to depend on?", "Does it cover a category we should not build ourselves yet?", "Can we support it properly, including when the other side changes?"]],
      ["h2", "i-categories", "The categories we are working through"],
      ["chips", ["Accounting", "Payroll", "Payments", "Calendars", "Communications", "Marketing", "Fleet management", "Jobsite documentation", "Financing"]],
      ["p", "Individual posts on specific platforms will follow, explaining what each connection does and where its limits are. We publish what is built, what is in progress, and what is only planned, and we do not announce a connection before it works."],
    ],
  },
  "philosophy": {
    title: "Why Business Software Should Work the Way Your Company Works",
    dek: "No two service businesses run the same way. Roles, permissions, department views and connected workflows, and why flexibility has to survive contact with simplicity.",
    date: "July 2026", read: "12 min read",
    ctaTitle: "Built around how you already work.",
    ctaBody: "Founding members tell us where the software has to bend to fit their company, and that feedback goes straight into the build.",
    ctaKey: "product", ctaLabel: "See the product",
    related: [["How We Decide What to Build", "blog-how-we-decide"], ["The Vision Behind Backentra", "blog-vision"], ["Product", "product"], ["Pricing", "plans"], ["Founding members", "founding"], ["About", "about"]],
    blocks: [
      ["p", "Every operator who has bought software has had this experience: the demo looked right, and then in week two you discover the system only supports one way of doing something you do three ways. So the team invents a workaround, the workaround becomes the process, and you are now running your company around the software's assumptions instead of your own."],
      ["p", "Our position is that businesses should not have to rebuild their operations around rigid software. Here is what that means in practice, and what we are and are not claiming today."],
      ["h2", "p-different", "Every business operates differently"],
      ["p", "Two gutter companies the same size on paper will run nothing alike. One quotes on site and closes on the spot; the other measures, prices at the office and emails a proposal. One dispatches from a single office; the other has crew leads who build their own week. One collects a deposit on every job; the other bills net thirty to builders."],
      ["p", "None of those is wrong. They are adaptations to a market, a crew and an owner's judgment. Software that recognizes only one of them is telling a profitable company its process is invalid."],
      ["h2", "p-rigid", "The problem with forcing companies into one workflow"],
      ["ul", ["Workarounds appear, and the real process moves back into a spreadsheet next to the software.", "Adoption stalls, because the people doing the work know it fights them.", "Data quality drops, since fields get repurposed to mean things they were not built for.", "Change gets expensive, because every adjustment is a support ticket instead of a setting."]],
      ["pull", "When a team maintains a workaround, the software has already lost. The whiteboard is telling you the truth."],
      ["h2", "p-roles", "Customizable roles and permissions"],
      ["p", "Permissions are usually treated as a security feature. They are really a clarity feature. A tech who sees only today's jobs is not being restricted; they are being spared eleven screens that are not their job. And an owner should be the only person who sees payroll, without that requiring a separate system."],
      ["cards", [["hard-hat", "Field", "Today's work, the scope, photos, time, and payment collection. Nothing else."], ["clipboard-list", "Office and dispatch", "The full schedule, customers, estimates, invoices and communications."], ["calculator", "Accounting", "Invoices, payments, costs and reporting, without touching dispatch."], ["shield", "Owner", "Everything, including payroll, margins and permissions themselves."]]],
      ["p", "The important part is that these are not four fixed tiers. A company with a working foreman who also quotes needs a role that does not exist in most systems, and defining it should take a minute rather than a phone call."],
      ["h2", "p-views", "Why departments need different views of the same information"],
      ["p", "One job record, several honest readings of it. Dispatch sees a time slot and a crew. The field sees a scope and an address. Accounting sees an amount and a term. The owner sees a margin. Same record, different question."],
      ["p", "Most stacks solve this by giving each department its own tool, which is exactly how the record splits into four versions that disagree. The better answer is one record with views shaped to the work in front of each person."],
      ["h2", "p-connected", "How connected workflows reduce duplicate work"],
      ["p", "A workflow is connected when finishing one step starts the next without a human relaying it. The estimate that is approved becomes a schedulable job carrying its own scope and price. The job that is completed produces an invoice from what actually happened, including the change order agreed in the driveway."],
      ["ul", ["Information is captured at the earliest point that knows it.", "Every downstream step inherits rather than re-collects.", "Status is a consequence of work happening, not something somebody remembers to update.", "The field and the office see the same record at the same second."]],
      ["h2", "p-simple", "Why simplicity matters even in a powerful platform"],
      ["p", "Flexibility has an obvious failure mode: a configuration project nobody finishes. We have all seen the platform that can model anything and therefore requires a consultant to model your company."],
      ["p", "So the standard we hold is that the default has to work on day one, and flexibility should be available where a business genuinely differs rather than everywhere at once. If a setting exists, someone has to understand it. That is a real cost, and it belongs to us, not to you."],
      ["pull", "Powerful by default, adjustable where it matters. Not a blank canvas with a manual."],
      ["h2", "p-future", "How we plan to support more flexible workflows over time"],
      ["p", "These are directions of active development, not shipped guarantees. We are working toward configurable roles and permissions, department dashboards, per-company workflow rules you can set yourself, and industry-shaped defaults so a tree service does not start from a blank template built for a plumber."],
      ["p", "We would rather ship one of those properly than announce all four. If your company runs a way the software cannot yet describe, tell us. That is the most useful thing a founding member can do."],
    ],
  },
  "how-we-decide": {
    title: "How We Decide What to Build Into Backentra",
    dek: "Problems before features, the gaps between departments, native versus integration, and why plenty of good ideas do not belong in the platform.",
    date: "July 2026", read: "11 min read",
    ctaTitle: "Put a problem in front of us.",
    ctaBody: "Founding members get a direct line to the team and help set what gets built next. Tell us the part of your week that hurts most.",
    ctaKey: "founding", ctaLabel: "Founding members",
    related: [["Why Software Should Work Your Way", "blog-philosophy"], ["Why We Are Starting With Integrations", "blog-integrations-first"], ["The Vision Behind Backentra", "blog-vision"], ["Product", "product"], ["Pricing", "plans"], ["About", "about"]],
    blocks: [
      ["p", "Feature requests are easy to collect and dangerous to build from. Everyone asks for the thing that would have solved last Tuesday, and a platform built from that list becomes a hundred half-finished screens. This is the process we use instead, written down so you can hold us to it."],
      ["h2", "d-problems", "Start with problems, not feature ideas"],
      ["p", "Every candidate starts as a description of something going wrong: an invoice that went out nine days late, a crew sent without the right equipment, a customer nobody followed up with. The request behind it is usually a solution someone already picked. Our job is to go back to the problem, because the best fix is often not the one requested."],
      ["pull", "A request for a new report is usually a symptom. The disease is that the numbers live in four systems."],
      ["h2", "d-real", "Learn from real office and field operations"],
      ["p", "We came out of this work, and we still watch it. Not surveys: sitting with an office manager on a Monday morning, riding along on a route, watching where somebody switches to a spreadsheet. The workarounds are the specification. Where a team has built one, the software failed and the fix is already documented in what they invented."],
      ["h2", "d-repetitive", "Identify repetitive work"],
      ["p", "Repetition is the clearest signal we have. If a person does the same sequence forty times a day, shaving it is worth more than any new module. We look for the same information typed twice, a status somebody remembers to update by hand, and any report assembled manually every month."],
      ["ul", ["Same data entered in two places: that is an integration or a native field.", "A step someone remembers rather than the system enforcing it: that is automation.", "A monthly manual report: that is a dashboard.", "A question asked repeatedly on the phone: that is a portal or a permission."]],
      ["h2", "d-gaps", "Look for gaps between departments"],
      ["p", "The most expensive failures happen in handoffs, not inside them. Sales to operations. Operations to accounting. Field to office. Each handoff is where information gets retyped, dropped or quietly changed. We prioritize the seams over polishing screens that already work."],
      ["h2", "d-essential", "Separate essential features from unnecessary complexity"],
      ["p", "The question we ask about any feature: if we remove this, does anyone's day get worse? Plenty of good ideas fail that test. A setting that three companies would use costs every other company a decision, and it costs us support and maintenance forever."],
      ["h2", "d-feedback", "Use customer and beta-user feedback"],
      ["p", "Founding members are not a marketing tier. They are the input. What we weigh is not the volume of a request but how central it is: the frequency of the underlying problem, how much manual work it removes, whether it blocks something else, and how many trades share it."],
      ["p", "We also pay attention to what nobody uses. A feature that shipped and went untouched is information, and sometimes the right response is to remove it."],
      ["h2", "d-native", "Native or integration?"],
      ["p", "Every capability gets this question before any code. If the category is deep, regulated and already well served, payroll tax filing and card processing being the clear cases, we integrate. If the gap is in how a job moves through a company, we build it, because that is the part the market treats as five separate tools."],
      ["ul", ["Is this core to the job record, or adjacent to it?", "Is there an established platform doing it well that our customers already pay for?", "Does building it ourselves create liability or compliance we cannot yet carry properly?", "Would a connection remove the duplicate work as effectively as a native build?"]],
      ["h2", "d-stages", "Build features in stages"],
      ["p", "Nothing useful ships complete. We build the narrow version first, put it in front of real operators, and widen it once the shape is right. Scheduling started as assigning a job to a crew on a day. Conflict detection, travel time and multi-day work came after we watched people use the simple version."],
      ["p", "The trade is honesty about what stage a thing is at. We publish what is built, what is being built, and what is only planned, and we do not announce a feature before it works."],
      ["h2", "d-no", "Why not every idea belongs in the platform"],
      ["p", "Saying no is most of this job. A request gets declined when it serves one company's unusual process, when an existing integration already handles it well, when it would add a permanent concept to the interface for a rare case, or when we cannot support it properly yet."],
      ["p", "Declined is not deleted. Requests that keep returning from different companies are the strongest roadmap signal there is, and several things we said no to early are now on the list."],
      ["h2", "d-roadmap", "How the roadmap changes"],
      ["p", "The roadmap is a current best guess, and founding-member feedback reorders it regularly. When something moves, we would rather say so than quietly reshuffle. Every significant feature will get its own post here explaining the problem it solves, what we built, what we deliberately left out, and how it fits the larger platform."],
    ],
  },
};

/* Posts written in later batches live in their own files and merge in here, so the registry
   stays one lookup and BlogArticle stays the only renderer. */
[window.BK_BLOG_PILLARS, window.BK_BLOG_BATCH1, window.BK_BLOG_BATCH2, window.BK_BLOG_BATCH3, window.BK_BLOG_BATCH4].forEach((batch) => {
  if (batch) Object.keys(batch).forEach((k) => { BL_ARTICLES[k] = batch[k]; });
});

/* An article may live in this repo or in the CRM, and the second kind is not here on the
   first render. So a slug that is not in the registry yet is "not loaded" rather than "does
   not exist", and the difference has to be waited out before saying anything.

   Once the feed has answered and the slug is still unknown, this used to render null — a
   white page. That was survivable when every article shipped with the deploy and a bad slug
   could only come from a typo in our own markup. Now a link can outlive its article: a piece
   somebody shared before it was unpublished lands exactly here. A blank page reads as a
   broken site, so it says what happened and offers the way back. */
function BlogPostGeneric(props) {
  const remote = useRemoteArticles();
  const post = BL_ARTICLES[props.slug];
  if (post) return <BlogArticle post={post} onNav={props.onNav} onJoin={props.onJoin} />;

  const waiting = remote.status === "idle" || remote.status === "loading";
  /* Only set the "not found" meta once the remote feed has actually answered — while
     `waiting`, the slug might still resolve, and marking a page noindex that is about to
     become real content is worse than a brief delay in setting the tag. */
  React.useEffect(() => {
    if (waiting) return;
    window.BackentraSetMeta && window.BackentraSetMeta({
      title: "Article not found — Backentra",
      description: "That article could not be found. Everything we have published is on the blog index.",
      robots: "noindex, nofollow",
    });
  }, [waiting]);
  return (
    <div style={{ background: "var(--surface-card)", minHeight: "100vh" }}>
      <window.BackentraSiteHeader current="blog" onNav={props.onNav} />
      <section style={{ padding: "72px 0 96px" }}>
        <div style={{ ...bcontainer, maxWidth: 720 }}>
          {waiting ? (
            <p style={{ fontSize: 16, color: "var(--text-muted)", margin: 0 }}>Loading…</p>
          ) : (
            <React.Fragment>
              <div style={{ ...beyebrow, color: "var(--accent-text)", marginBottom: 14 }}>Not here</div>
              <h1 style={{ fontSize: 34, lineHeight: 1.15, margin: "0 0 14px", letterSpacing: "-0.02em" }}>We cannot find that article.</h1>
              <p style={{ fontSize: 17, lineHeight: 1.65, color: "var(--text-muted)", margin: "0 0 24px" }}>
                It may have been taken down, or the address may have changed. Everything we have published is on the blog index.
              </p>
              <BLBtn variant="primary" onClick={() => props.onNav && props.onNav("blog")} iconRight={<BLIcon name="arrow-right" size={18} />}>Back to the blog</BLBtn>
            </React.Fragment>
          )}
        </div>
      </section>
      <window.BackentraSiteFooter onNav={props.onNav} />
    </div>
  );
}

window.BackentraBlogPostGeneric = BlogPostGeneric;

window.BackentraBlog = BlogIndex;
window.BackentraBlogPost = BlogPost;
