// Corda blog — index + reader. Editorial, shareable, brings traffic to landing.

// ─────────────────────────────────────────────────────────────
// Inline markdown helpers — links, bold, italic
// ─────────────────────────────────────────────────────────────
const escapeHtml = (s) => s
  .replace(/&/g, '&amp;')
  .replace(/</g, '&lt;')
  .replace(/>/g, '&gt;');

// Allowlist URL protocols permitted inside markdown links. Anything else
// (javascript:, data:, vbscript:, file:, etc.) is rendered as plain text so
// blog content can't execute scripts via crafted links.
const isSafeUrl = (url) => {
  const trimmed = String(url || '').trim();
  if (!trimmed) return false;
  if (trimmed.startsWith('/') || trimmed.startsWith('#') || trimmed.startsWith('mailto:')) return true;
  return /^https?:\/\//i.test(trimmed);
};

const inlineMd = (raw) => {
  let s = escapeHtml(raw);
  s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text, url) => {
    if (!isSafeUrl(url)) return text;
    const safe = url.replace(/"/g, '%22');
    const isExternal = /^https?:\/\//i.test(url);
    const ext = isExternal ? ' target="_blank" rel="noreferrer noopener"' : '';
    return `<a href="${safe}"${ext} style="color: var(--sage-deep); text-decoration: underline; text-decoration-color: var(--sage-soft); text-underline-offset: 3px;">${text}</a>`;
  });
  s = s.replace(/\*\*([^*]+)\*\*/g, '<strong style="font-weight:600;color:var(--ink);">$1</strong>');
  s = s.replace(/(^|[^*])\*([^*]+)\*/g, '$1<em>$2</em>');
  return s;
};

// Convert markdown body → block list.
const mdToBlocks = (md) => {
  // Strip YAML frontmatter if present
  if (md.startsWith('---')) {
    const end = md.indexOf('\n---', 3);
    if (end !== -1) md = md.slice(end + 4);
  }
  // Drop H1 (already shown in header)
  md = md.replace(/^[\s\n]*# .*\n/, '');

  const lines = md.split('\n');
  const blocks = [];
  let buf = [];
  const flushP = () => {
    if (buf.length) {
      blocks.push({ kind: 'p', html: inlineMd(buf.join(' ')) });
      buf = [];
    }
  };

  let i = 0;
  while (i < lines.length) {
    const line = lines[i];
    const trimmed = line.trim();

    if (trimmed.startsWith('## ')) {
      flushP();
      blocks.push({ kind: 'h2', text: trimmed.slice(3).trim() });
      i++; continue;
    }
    if (trimmed.startsWith('### ')) {
      flushP();
      blocks.push({ kind: 'h3', text: trimmed.slice(4).trim() });
      i++; continue;
    }
    if (trimmed === '---') {
      flushP();
      blocks.push({ kind: 'divider' });
      i++; continue;
    }
    if (trimmed.startsWith('> ')) {
      flushP();
      blocks.push({ kind: 'quote', html: inlineMd(trimmed.slice(2).trim()) });
      i++; continue;
    }
    if (/^[-*] /.test(trimmed)) {
      flushP();
      const items = [];
      while (i < lines.length && /^[-*] /.test(lines[i].trim())) {
        items.push(inlineMd(lines[i].trim().slice(2).trim()));
        i++;
      }
      blocks.push({ kind: 'list', items, html: true });
      continue;
    }
    if (/^!!!\s*cta\s*$/i.test(trimmed)) {
      flushP();
      i++;
      const buf2 = [];
      while (i < lines.length && lines[i].trim() !== '' && !/^!!!\s*$/.test(lines[i].trim())) {
        buf2.push(lines[i].trim());
        i++;
      }
      // consume closing `!!!` if present
      if (i < lines.length && /^!!!\s*$/.test(lines[i].trim())) i++;
      blocks.push({ kind: 'callout', text: buf2.join(' ') });
      continue;
    }
    if (trimmed === '') {
      flushP();
      i++; continue;
    }
    buf.push(trimmed);
    i++;
  }
  flushP();
  return blocks;
};

// Re-point off-site CTA links inside user posts to in-site routes.
const localizeLinks = (md) => md
  // Legacy posts (DB or cached) still link [Mental Load Audit](...) — relabel to Quiz + point to /quiz.html.
  .replace(/\[Mental Load Audit\]\(https:\/\/meetcorda\.com\)/gi, '[Mental Load Quiz](/quiz.html)')
  .replace(/\[(Mental Load Quiz)\]\(https:\/\/meetcorda\.com\)/gi, '[$1](/quiz.html)')
  .replace(/\[(\*\*Join the early access list →\*\*)\]\(https:\/\/meetcorda\.com\)/gi, '[$1](/#waitlist)')
  .replace(/\[(Join the early access list →)\]\(https:\/\/meetcorda\.com\)/gi, '[$1](/#waitlist)');

// ─────────────────────────────────────────────────────────────
// User-written posts (markdown)
// ─────────────────────────────────────────────────────────────
const USER_POST_1_MD = `---
title: "The 2 A.M. List: Why Caregiving Breaks Your Brain (Even When You're 'Handling It')"
---

# The 2 A.M. List: Why Caregiving Breaks Your Brain (Even When You're "Handling It")

You wake up at 2:04 a.m. because you suddenly remembered that the cardiologist's office said they'd call back about the medication adjustment, and they didn't, and you need to call them in the morning, and also you forgot to pick up the prescription, and also tomorrow is the day the home health aide is coming earlier than usual, and also did you tell your sister about the appointment, and also —

You stare at the ceiling. You can't go back to sleep. You can't write it down because if you turn on your phone, you'll be up for an hour. You try to memorize the list. You fail. You memorize it again.

This is not a sleep problem. This is not a stress problem. This is not, despite what your partner gently suggested last week, a "you need to relax" problem.

This is a **cognitive load** problem. And it's doing something to your brain that you should probably know about.

---

## What "cognitive load" actually means

Cognitive load is the amount of information your working memory is holding at once. Your working memory is small — most people can hold about [four to seven items at a time](https://en.wikipedia.org/wiki/Working_memory) before performance degrades. It's the mental equivalent of a kitchen counter: useful, finite, and if you pile too much on it, things start falling off.

Caregivers, on average, are holding *dozens* of things at once.

Not "thinking about" dozens of things — actively *holding* them. Tracking them. Updating them. Watching for changes. A typical adult child caring for an aging parent might simultaneously be tracking:

- 3–7 medications (names, doses, timing, refill dates)
- 4–10 medical providers (and their office staff, fax numbers, scheduling quirks)
- 6–15 upcoming or pending appointments
- Insurance claims, denials, appeals
- A mental baseline of how the person is doing — sleep, appetite, mood, balance, cognition — against which any deviation is a five-alarm fire
- The things only you know (which side they sleep on, who they trust, what time they take it best)
- Family relay duties (group chat updates, sibling coordination, the call to the cousin)
- The list of things you said you'd follow up on
- Logistics: rides, meals, in-home help schedules

If you've ever taken our [Mental Load Quiz](https://meetcorda.com), you might have walked away with a number in the 40s, 60s, or higher. And that's not a personal failing. That's a structural mismatch between the size of your working memory and the size of your responsibilities.

Your brain wasn't designed for this. Nobody's was.

---

## What this does to you, neurologically

Sustained high cognitive load doesn't just make you tired. It changes how your brain operates.

**1. Your prefrontal cortex gets exhausted.**
The prefrontal cortex is the part of your brain responsible for executive function — planning, prioritizing, decision-making, impulse control. Holding many active items in working memory is metabolically expensive, and the prefrontal cortex runs out of fuel. This is why caregivers often describe themselves as feeling [foggy, indecisive, or unable to focus](https://www.health.harvard.edu/blog/can-stress-cause-memory-loss-202105262471). It's not in your head. It's literally a depleted brain region.

**2. Your default mode network won't turn off.**
There's a network in your brain called the default mode network — it's what's active when you're "at rest," letting your mind wander, processing things in the background. In healthy brains, it cycles on and off. In brains under sustained vigilance — which is what caregiving is — it stays on. This is why you can't actually rest. Even when you're watching TV. Even when you're trying to sleep.

**3. Your memory consolidation suffers.**
Sleep is when your brain transfers things from short-term to long-term memory. When you're waking up at 2 a.m. running the list, you're sabotaging that process. Which means you'll forget more things during the day. Which means more 2 a.m. wake-ups. It's a closed loop.

**4. Your decision-making degrades.**
This is the one nobody talks about. Decision fatigue is real, and caregivers are making *hundreds* of micro-decisions a day that other people don't have to make. Should I call the doctor now or wait? Is this cough new? Should I tell my brother about this? Is this enough sleep? Is this fall a fall, or *a fall*? By the time you get to your actual job, or your kids, or your partner, your decision-making budget is gone.

---

## Why "just write it down" doesn't work

You've already tried this. We know you've already tried this.

You've tried the notebook. The notes app. The shared Google Doc with your sibling. The whiteboard on the fridge. The reminder app. The medication tracker. The folder of insurance paperwork that is now three folders.

Here's why those tools don't fix the problem:

**They're inert.** A list doesn't move with you. It doesn't keep the follow-up you meant to do from sliding to the bottom. It doesn't connect the note you wrote to the appointment it came from. So you still have to hold the *meta-list* — the list of things to check on the list. Which means the list didn't actually leave your head.

**They're scattered.** The pharmacy app has the meds. The patient portal has the appointments. The notes app has the questions for the doctor. Your text thread with your sister has the schedule. To pull a coherent picture of "how is mom right now," you have to assemble it yourself. Every time.

**They don't speak your language.** Most of these tools were built for the person being cared for, not the person doing the caring. The patient portal is for the patient. The medication tracker is for the patient. There's almost no software in the world that is built for *you*.

So the load goes back into your head, because your head is the only place all of it can actually live together.

---

## What actually helps

Three things, in order of impact.

**1. Get it out of your head, into something that watches it for you.**

The thing that separates relief from frustration here isn't *storage* — it's *keeping it together in one place*. You need a system that doesn't just hold information but keeps the loose ends visible. Something that keeps the follow-up you meant to make from disappearing. Something that knows the difference between "the appointment is tomorrow" and "the appointment was last Tuesday and we never rebooked."

This is the thing nothing in your life currently does. This is the thing we're building.

**2. Make the invisible visible to the people around you.**

A lot of caregiver friction is downstream of *other people not knowing what you're carrying*. Your partner doesn't see it. Your siblings don't see it. Your friends don't see it. So when you say "I can't, I'm exhausted," it lands as moodiness, not the truth.

If you do nothing else after reading this, do this: take 10 minutes and write down — anywhere — every item you are currently tracking. Every doctor. Every medication. Every pending call. Every unanswered question. Every thing only you know. Show it to one person who matters.

This is sometimes the moment a marriage gets saved.

**3. Lower the threshold for asking for help.**

The reason caregivers don't ask for help isn't pride. It's cognitive load. *Asking* for help requires holding even more in your head: who would I ask, what would I ask them to do, when do they need to do it, how do I explain enough context for them to do it right. The cost of asking exceeds the benefit of receiving.

The trick is to make help-able tasks small, specific, and pre-defined. "Can you call the clinic on Tuesday and ask if the referral went through?" is askable. "Can you help me with mom's care?" is not.

---

## What we're building

We started Corda because we lived this. The 2 a.m. lists. The forgetting. The feeling that you are the system, and the system is failing.

Corda is being built to be the thing that holds what you're carrying — appointments, visit notes, follow-up tasks, providers, the small things only you know — and keeps it in one place. Not another app to check. Something built for the person doing the caring, so the load doesn't have to live in your head anymore.

We're opening early access soon, and we want the first people in to be people who've actually been carrying this. People who'd take the quiz and not be shocked at the number.

### Put it down.

If anything in this post described your life, take a minute. Take the 60-second [Mental Load Quiz](https://meetcorda.com) — find out what you're actually holding — and get on the early access list for Corda.

You shouldn't be the only place all of this lives.

[**Join the early access list →**](https://meetcorda.com)

---

*If this post hit, send it to someone else who's carrying a lot. They probably need someone to have said it out loud, too.*
`;

const USER_POST_2_MD = `---
title: "Caregiver Burnout Isn't Like Work Burnout. That's Why Nothing You're Trying Is Working."
---

# Caregiver Burnout Isn't Like Work Burnout. That's Why Nothing You're Trying Is Working.

A friend takes you to coffee. She's been watching you. She's worried. She says, kindly, "I think you're burned out. You need to take a weekend for yourself."

You nod. You agree. You don't tell her that you already took a weekend for yourself, two months ago. You drove four hours to a hotel. You ordered room service. You stared at the ceiling for two days. You came back exactly as exhausted as when you left, except now you also felt guilty.

You don't tell her that you've tried the spa day, the long walk, the meditation app, the early bedtime, the boundaries conversation, the therapist, the wine, the not-wine, the morning routine, the journaling, the gratitude practice, the cold plunge.

You don't tell her that none of it touched the thing.

You don't tell her this because she's trying to help and you don't have the energy to explain why her help isn't going to work. So you say "you're right, I should do that," and you go home, and that night at 2 a.m. you're awake again, running the list.

The reason none of it is working is that **caregiver burnout is structurally different from the burnout everyone else is talking about**. And until you understand the difference, you'll keep trying solutions that were designed for a different problem.

---

## What "burnout" usually means

When most people talk about burnout, they're talking about a specific thing, and the research is actually pretty good on it.

The classical definition, from [Christina Maslach's foundational work](https://en.wikipedia.org/wiki/Occupational_burnout), describes burnout as a syndrome with three components: emotional exhaustion, depersonalization (feeling cynical or detached from the work), and reduced sense of accomplishment. It was originally studied in helping professions — nurses, social workers, teachers — and has since been extended to office workers, executives, parents, everyone.

The standard prescriptions follow from the diagnosis:

- **Emotional exhaustion?** Rest. Disengage. Sleep more. Take a vacation.
- **Depersonalization?** Reconnect with meaning. Remember why you started.
- **Reduced accomplishment?** Take on a project you can complete. Get a win.

These are good prescriptions. They work. For a certain kind of burnout.

That kind of burnout has a critical feature: **the source of the burnout can be turned off**. You can leave the office. You can go on vacation. You can quit the job. The thing that's draining you is something you can, in principle, walk away from. Recovery is possible because *cessation* is possible.

Caregiver burnout doesn't have that feature.

---

## Why caregiver burnout is its own thing

Caregiver burnout looks a lot like work burnout on the surface — exhaustion, detachment, futility. But the underlying mechanics are different in three ways that matter enormously.

**1. You can't turn it off.**

You cannot leave the office. You cannot go on vacation. You cannot quit. The person you are caring for does not stop needing care because you are tired. Even when you are physically away from them, you are not *mentally* away from them. The phone is in your pocket. The list is in your head. The catastrophe is one missed call away.

This is why the weekend at the hotel didn't work. You changed your location. You didn't change your load.

**2. The "purpose" framing makes it worse, not better.**

When a workplace burns you out, well-meaning advice says "reconnect with your why." Find the meaning. Remember the mission.

For caregivers, the *why* is the most painful part. You are caring for someone you love. The purpose is not lost. The purpose is *crushingly present*. The harder you love them, the harder it is. Telling a caregiver to "reconnect with meaning" is like telling someone bleeding to think about the heart that's pumping the blood. Yes. Exactly. That is the problem.

This is why your therapist's reframing exercises feel hollow. They're solving for a meaning deficit you do not have.

**3. The load is invisible, which means nobody around you can size it correctly.**

A burned-out lawyer has a 70-hour week and a calendar full of meetings. Their burnout is *legible* to other people. Their spouse can see it. Their friends can see it. The firm can see it.

A burned-out caregiver has… what? An ordinary-looking life with one or two visible duties (taking mom to the appointment, picking up the prescription). The 95% of the work — the tracking, the watching, the remembering, the worrying, the coordinating, the deciding — is happening *in your head*. There is no calendar. There is no project plan. There is no manager who would look at your workload and say "this is too much."

So when you say "I'm exhausted," it lands as a feeling, not a fact. People don't dismiss you on purpose — they just literally cannot see what you're doing. And the gap between what you're carrying and what other people perceive you to be carrying *itself becomes part of the load*. You're not just exhausted. You're exhausted *and uncorroborated*, which is a particularly lonely flavor of tired.

---

## The four signs you're not stressed — you're in caregiver burnout

There's a clinical version of this checklist from [Cleveland Clinic and other major caregiver resources](https://my.clevelandclinic.org/health/diseases/9225-caregiver-burnout), but here it is in the language of how it actually feels.

**1. You can't remember the last time you wanted something for yourself.**

Not "had time for yourself" — *wanted* something for yourself. The category has gone quiet. People ask what you'd like for your birthday and you genuinely don't have an answer. Your appetite for your own life has gone flat.

**2. You feel a low, constant anger that has no clear target.**

It's not at the person you're caring for. It's not at your siblings (well, sometimes). It's not at your spouse. It's at no one. It's at the situation. And it sits in your chest all day and flares at small provocations — a slow driver, a misplaced thing, a question asked at the wrong moment.

**3. You've stopped being able to feel pleasure at things you used to.**

This is anhedonia, and it is one of the most reliable signs that you've crossed from stress into something more serious. The food doesn't taste as good. The show isn't as fun. The friend's company isn't as restorative. The world has gone slightly gray.

**4. You're catastrophizing on a loop.**

Every cough is pneumonia. Every missed call is the call. Every quiet hour is a quiet hour because *something is wrong*. Your nervous system has decided that vigilance is the safer setting, and it has stopped letting you turn it off.

If three or four of these are true, you're not just stressed. You're burning out in the specific way caregivers burn out. And the things that work for stress will not be enough.

---

## What actually helps (and what doesn't)

Let's be honest about what most "self-care" advice does and doesn't do for this kind of burnout.

**Things that don't really help:**

- *Solo vacations* — change your location, not your load. The mental burden travels.
- *Generic meditation apps* — they assume the problem is your relationship to your thoughts. Your problem is the volume of *real, important* things you're tracking.
- *"Self-care" framed as activities* — bubble baths and face masks do not address structural overload.
- *"Just ask for help"* — without infrastructure for delegating, asking for help adds load instead of removing it.

**Things that actually help:**

**1. Offload the cognitive layer — not just the physical layer.**

A respite care provider can do tasks for you. They can't carry the *tracking* for you, which is where most of the load lives. The first real relief most caregivers experience comes when something *outside their head* is reliably holding the moving parts — appointments, visit notes, follow-ups, providers. Not another tool you check. Something built for the person doing the caring.

This is what we're building Corda to do.

**2. Make the invisible visible to your people.**

Once a week, write down everything you're currently tracking. Show it to your partner, your sibling, one trusted friend. Not as a complaint. As information. *"This is what I'm holding right now."* Most people in your life are not refusing to help; they literally cannot see the work to offer to help with it. Visibility unlocks the help that was already available.

**3. Build a specific list of things people can do — before you need them.**

The reason you can't accept help in the moment is that you don't have a list ready. So when someone says "let me know how I can help," you say "I'll let you know" and never let them know, because constructing the ask is its own job.

Make the list now, while it's quiet. Twenty specific things, broken down small. Call the pharmacy. Drop off a meal Thursday. Sit with mom Saturday afternoon so I can sleep. Then when someone offers, you hand them the list and let them pick.

**4. Get the load out of your head.**

This is the one. This is the foundation that everything else sits on. As long as the load lives in your head, the load is *with you* — at dinner, at the movies, at the hotel, at 2 a.m.

The first night you sleep without running the list is a night you'll remember. We want to help build that night.

---

## What we're building, and why we're building it

Corda exists because the people building it have lived this. The lists. The waking. The hotel weekend that didn't work. The friend at coffee who didn't understand.

We're building a system that holds what you're carrying — appointments, visit notes, follow-up tasks, providers, the small things only you know — and keeps it in one place, so the load doesn't have to live in your head. Not another app to check. Something built specifically for the person doing the caring, in language that finally sounds like your life.

We're opening early access soon. We want the first people in to be people who actually recognized themselves in this post.

### Put it down.

If this post described your life, take 60 seconds to find out what you're actually carrying. We built a short [Mental Load Quiz](https://meetcorda.com) for this. It will probably surprise you. Then get on the early access list.

You shouldn't be the only place all of this lives.

[**Join the early access list →**](https://meetcorda.com)

---

*If this post named something you've been feeling, send it to one other person who needs to read it. They've been trying to explain this to themselves, too.*
`;

// ─────────────────────────────────────────────────────────────
// Post data
// ─────────────────────────────────────────────────────────────
const POSTS = [
  {
    slug: 'the-2am-list-caregiving-cognitive-load',
    category: 'Essay',
    title: 'The 2 a.m. list: why caregiving breaks your brain (even when you\'re "handling it").',
    dek: 'Caregiving doesn\'t just exhaust you. It changes the way your brain works. Here\'s the science of why — and what to do about it.',
    author: 'Corda',
    role: 'The Corda team',
    date: 'May 23, 2026',
    readMin: 6,
    cover: 'moon',
    feature: true,
    bodyMd: USER_POST_1_MD,
  },
  {
    slug: 'caregiver-burnout-different',
    category: 'Essay',
    title: 'Caregiver burnout isn\'t like work burnout. That\'s why nothing you\'re trying is working.',
    dek: 'Everyone keeps telling you to rest. To set boundaries. To practice self-care. None of it is working — and there\'s a reason.',
    author: 'Corda',
    role: 'The Corda team',
    date: 'May 23, 2026',
    readMin: 7,
    cover: 'thread',
    feature: false,
    bodyMd: USER_POST_2_MD,
  },
  {
    slug: 'the-notebook-i-kept-for-my-mother',
    category: 'Essay',
    title: 'The notebook I kept for my mother.',
    dek: 'For three years I tracked my mother\'s care in a yellow legal pad. Then one Tuesday I lost it on the F train.',
    author: 'Anya Reyes',
    role: 'Co-founder, Corda',
    date: 'May 12, 2026',
    readMin: 6,
    cover: 'notebook',
    feature: false,
    bodyMd: `My mother was diagnosed in the autumn. By the spring I owned a yellow legal pad — the cheap kind from a Duane Reade — and that pad became the most important object in my life.

It held what Dr. Patel said on a Tuesday in November. It held the name of the home aide my brother liked and the one I didn't. It held the question I forgot to ask the neurologist twice in a row, written in the margin in pen so I would remember the third time.

It held everything I was carrying. And in March, on a crowded F train going home from Mount Sinai, I lost it.

## What you lose when you lose the pad

You don't lose information, exactly. Most of what was in there I could reconstruct from memory — a phone call to my brother, a portal login, a pharmacist who took pity on me. What you lose is the feeling that someone, somewhere, is keeping track.

When you are the person keeping track, that someone is you. The pad isn't an organizational tool. The pad is the part of your brain you offloaded so the rest of your brain could continue being a daughter.

> The pad isn't an organizational tool. The pad is the part of your brain you offloaded so the rest of your brain could continue being a daughter.

## Why we built Corda

I lost the pad in March. By May I had built a janky spreadsheet. By July the spreadsheet was sixteen tabs and I had stopped opening it because opening it made me cry.

So we built it. Not a CRM for your mother. Not a medical record. Something quieter than that — something that holds the things you're carrying so you can put them down for a minute and be present for the moments that actually matter.

!!! cta
Corda is in private beta. We launch when 50 families have joined the waitlist — together, at once. You can join below.
`,
  },
  {
    slug: 'twelve-questions-before-a-specialist-visit',
    category: 'Guide',
    title: 'Twelve questions to ask before your next specialist appointment.',
    dek: 'A practical, copy-pasteable list — built from interviews with 40 caregivers and three geriatricians.',
    author: 'Mira Okonkwo',
    role: 'Co-founder, Corda',
    date: 'April 28, 2026',
    readMin: 5,
    cover: 'list',
    feature: false,
    bodyMd: `You have, on a good day, fifteen minutes with the specialist. On a bad day, eight. Here are the twelve questions that — in our interviews with families and clinicians — consistently changed how an appointment went.

Print them. Screenshot them. Copy them into your notes app. The point is to walk in already knowing what you want to leave with.

## Before you walk in

- What is the one decision we are trying to make in this appointment?
- What changed since the last visit — symptoms, medications, mood, sleep?
- What did the last provider promise to send over, and did it arrive?

## During the visit

- Can you say that again, plainer? (You are allowed to ask this. Use it.)
- What does "watch for" look like in real life? At what point do we call?
- Are any of these new medications going to interact with what they're already taking?
- What's the realistic timeline before we know if this is working?
- Who on your team should we contact between visits, and how?

## Before you leave

- Can you send the visit summary to the primary care doctor as well?
- What's the soonest you can see us if something gets worse?
- Is there anything I, as the caregiver, should be doing differently?
- What did I forget to ask?

The last one is the one that earns its keep. Specialists know what families usually forget. Giving them permission to volunteer that information turns a fifteen-minute appointment into a real conversation.

!!! cta
Corda automatically organizes visit notes by provider, so the answers don't get lost between appointments. Join the waitlist to be part of the first 50 families.
`,
  },
  {
    slug: 'a-parents-medication-list-before-the-next-visit',
    category: 'Guide',
    title: 'Getting a parent\'s medication list straight before the next visit.',
    dek: 'The "one source of truth" rule, the brown-bag trick every geriatric nurse we interviewed mentioned, and how to walk into the appointment ready.',
    author: 'Anya Reyes',
    role: 'Co-founder, Corda',
    date: 'April 14, 2026',
    readMin: 7,
    cover: 'list',
    feature: false,
    bodyMd: `Most of the confusion at home isn't about the wrong pill — it's about the wrong information. Three providers, three slightly different lists, and a caregiver caught in the middle trying to figure out which one is current to bring to the next appointment.

## The "one source of truth" rule

Pick one place where the canonical list lives. Not three places. One. Every other list — the pharmacy's, the cardiologist's, the one taped to the fridge — defers to it.

> Most of the confusion at home isn't about the wrong pill — it's about the wrong information.

## The trick every nurse mentioned

Bring the bottles to every appointment. Not a list. The actual bottles, in a gallon Ziploc bag. The technical term is "brown bag review." Pharmacists and geriatricians love it because they can see the prescribing physician's name, the fill date, and whether the bottle is half-full when it should be nearly empty.

!!! cta
Corda keeps your visit notes and the questions you want to ask in one place, so the answers from each appointment don't get lost between visits. (Medication management isn't part of Corda's launch.) Join the waitlist.
`,
  },
  {
    slug: 'the-3am-checklist',
    category: 'Short',
    title: 'The 3 a.m. checklist.',
    dek: 'What to write down when you wake up worrying — so you can put it down and sleep.',
    author: 'Mira Okonkwo',
    role: 'Co-founder, Corda',
    date: 'April 2, 2026',
    readMin: 2,
    cover: 'moon',
    feature: false,
    bodyMd: `If you're reading this at 3 a.m., put the phone down for sixty seconds and write these things on a piece of paper or in a note. Don't solve them. Just put them somewhere outside your head.

- The one thing I'm most worried about right now.
- The next person I need to call, and what I need to ask them.
- The next appointment, and what I want it to accomplish.
- The thing I keep meaning to do but haven't.
- One thing I did this week that mattered.

The last one is not sentimental. It's the one that lets you close the list.

In the morning, the list will still be there. But you might be able to sleep now.

!!! cta
Corda is the place to put the list. Built for caregivers who keep waking up at 3 a.m.
`,
  },
  {
    slug: 'why-caregiving-feels-invisible',
    category: 'Manifesto',
    title: 'Why caregiving feels invisible — and what we\'re trying to change.',
    dek: 'There are 53 million unpaid caregivers in the United States. Most of the software they use was built for hospitals, not for them.',
    author: 'Anya Reyes & Mira Okonkwo',
    role: 'Founders, Corda',
    date: 'March 19, 2026',
    readMin: 4,
    cover: 'thread',
    feature: false,
    bodyMd: `There are 53 million unpaid family caregivers in the United States. That is roughly one in five adults. They spend, on average, 24 hours a week on caregiving — a second job, with no salary, no training, and almost no support.

And yet: almost every piece of software in the healthcare industry is built for the hospital, the clinic, or the insurer. Not for the daughter sitting in the waiting room with the legal pad on her knee.

## What hospitals built

Patient portals are designed to message your doctor and pay your bill. They are not designed to help you remember which of your mother's three cardiologists changed her diuretic last month.

## What we're building

Corda is built for the caregiver as the user — not as an afterthought. It assumes you are coordinating across three to seven providers, that the medication list changes every couple of months, that nothing is ever final, that you're tired.

!!! cta
We launch when 50 families have joined. If this resonates, please join us — and forward it to someone who needs it.
`,
  },
];

// Expose defaults so admin page can seed an empty table.
const DEFAULT_POSTS = POSTS;
window.CORDA_DEFAULT_POSTS = DEFAULT_POSTS;

// API origin — same alias the landing waitlist forms use. The alias bypasses
// Vercel deployment protection.
const CORDA_API_BASE = (window.__CORDA_API_BASE__) || 'https://api-nine-xi-52.vercel.app';
window.CORDA_API_BASE = CORDA_API_BASE;

function usePosts() {
  const [posts, setPosts] = React.useState(DEFAULT_POSTS);
  // 'idle' before first attempt; 'live' if API returned; 'stale' if the fetch
  // failed or returned no live posts and we're falling back to defaults.
  const [source, setSource] = React.useState('idle');

  const refetch = React.useCallback(() => {
    fetch(`${CORDA_API_BASE}/api/blog/posts`, { cache: 'no-cache' })
      .then(async (r) => {
        if (!r.ok) throw new Error(`API responded ${r.status}`);
        return r.json();
      })
      .then(json => {
        const list = json && json.data && Array.isArray(json.data.posts) ? json.data.posts : null;
        if (list && list.length) {
          setPosts(list);
          setSource('live');
        } else {
          // API reachable but empty — keep defaults, but flag as stale so the
          // reader can show a hint instead of silently rendering placeholder
          // content as if it were canonical.
          setSource('stale');
        }
      })
      .catch((err) => {
        console.warn('[corda blog] live posts unavailable, using defaults:', err && err.message);
        setSource('stale');
      });
  }, []);

  React.useEffect(() => {
    refetch();
    const onCustom = () => refetch();
    window.addEventListener('corda-posts-changed', onCustom);
    return () => window.removeEventListener('corda-posts-changed', onCustom);
  }, [refetch]);

  return { posts, source };
}

// Resolve a post's body — prefer bodyMd (markdown) over legacy body[] blocks.
const resolveBlocks = (post) => {
  if (post.bodyMd && typeof post.bodyMd === 'string') {
    return mdToBlocks(localizeLinks(post.bodyMd));
  }
  return post.body || [];
};

// ─────────────────────────────────────────────────────────────
// Cover artwork
// ─────────────────────────────────────────────────────────────
const PostCover = ({ kind, imageUrl, size = 'card' }) => {
  const isHero = size === 'hero';
  const h = isHero ? 460 : 240;
  return (
    <div style={{
      width: '100%', height: h, borderRadius: isHero ? 'var(--r-lg)' : 'var(--r-md)',
      background: 'var(--sage-tint)', position: 'relative', overflow: 'hidden',
      border: '0.5px solid var(--hairline-soft)',
    }}>
      {imageUrl
        ? <img src={imageUrl} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
        : <CoverArt kind={kind} />}
      <div style={{
        position: 'absolute', inset: 0,
        background: 'linear-gradient(135deg, transparent 55%, rgba(110, 139, 123, 0.06))'
      }} />
    </div>
  );
};

const CoverArt = ({ kind }) => {
  if (kind === 'notebook') {
    return (
      <svg viewBox="0 0 600 240" preserveAspectRatio="xMidYMid slice" style={{ width: '100%', height: '100%' }}>
        <defs>
          <pattern id="lines-nb" width="100%" height="22" patternUnits="userSpaceOnUse">
            <line x1="0" y1="22" x2="600" y2="22" stroke="var(--sage)" strokeWidth="0.4" opacity="0.35" />
          </pattern>
        </defs>
        <rect width="600" height="240" fill="var(--sage-tint)" />
        <rect x="80" y="30" width="440" height="200" rx="6" fill="var(--surface)" stroke="var(--hairline)" strokeWidth="0.6" />
        <rect x="80" y="30" width="440" height="200" fill="url(#lines-nb)" />
        <line x1="120" y1="30" x2="120" y2="230" stroke="var(--clay)" strokeWidth="0.7" opacity="0.5" />
        <g fontFamily="var(--font-serif)" fontStyle="italic" fontSize="14" fill="var(--ink-2)" opacity="0.65">
          <text x="135" y="62">Tuesday — Dr. Patel</text>
          <text x="135" y="84">balance better than last visit</text>
          <text x="135" y="106">call clinic re: referral</text>
          <text x="135" y="128">ask about sleep</text>
          <text x="135" y="172">"the question I keep</text>
          <text x="135" y="194">forgetting to ask"</text>
        </g>
      </svg>
    );
  }
  if (kind === 'list') {
    return (
      <svg viewBox="0 0 600 240" preserveAspectRatio="xMidYMid slice" style={{ width: '100%', height: '100%' }}>
        <rect width="600" height="240" fill="var(--sage-tint)" />
        {[0,1,2,3,4,5,6,7,8,9,10,11].map(i => {
          const col = i % 2;
          const row = Math.floor(i / 2);
          const x = 90 + col * 230;
          const y = 50 + row * 28;
          return (
            <g key={i}>
              <circle cx={x + 8} cy={y - 5} r="7" fill="none" stroke="var(--sage-deep)" strokeWidth="0.8" />
              <line x1={x + 4} y1={y - 5} x2={x + 7} y2={y - 2} stroke="var(--sage-deep)" strokeWidth="0.8" strokeLinecap="round" />
              <line x1={x + 7} y1={y - 2} x2={x + 12} y2={y - 9} stroke="var(--sage-deep)" strokeWidth="0.8" strokeLinecap="round" />
              <rect x={x + 24} y={y - 9} width={140 + (i*7)%60} height="3" rx="1.5" fill="var(--ink-3)" opacity="0.35" />
            </g>
          );
        })}
      </svg>
    );
  }
  if (kind === 'pills') {
    return (
      <svg viewBox="0 0 600 240" preserveAspectRatio="xMidYMid slice" style={{ width: '100%', height: '100%' }}>
        <rect width="600" height="240" fill="var(--sage-tint)" />
        {[
          { x: 130, y: 80,  c: 'var(--clay)',     r: -15 },
          { x: 220, y: 130, c: 'var(--sage)',     r: 8 },
          { x: 310, y: 70,  c: 'var(--amber)',    r: 32 },
          { x: 400, y: 150, c: 'var(--plum)',     r: -8 },
          { x: 480, y: 90,  c: 'var(--sage-deep)',r: 20 },
        ].map((p, i) => (
          <g key={i} transform={`translate(${p.x} ${p.y}) rotate(${p.r})`}>
            <rect x="-22" y="-10" width="44" height="20" rx="10" fill={p.c} opacity="0.85" />
            <line x1="0" y1="-10" x2="0" y2="10" stroke="var(--surface)" strokeWidth="0.7" opacity="0.5" />
          </g>
        ))}
      </svg>
    );
  }
  if (kind === 'moon') {
    return (
      <svg viewBox="0 0 600 240" preserveAspectRatio="xMidYMid slice" style={{ width: '100%', height: '100%' }}>
        <rect width="600" height="240" fill="#1F2A24" />
        <circle cx="450" cy="100" r="58" fill="#F4F1EA" opacity="0.92" />
        <circle cx="430" cy="92" r="58" fill="#1F2A24" />
        {[[80,40],[140,180],[200,60],[280,160],[340,30],[420,200],[510,180],[560,60],[180,110],[260,40],[80,170]].map(([x,y],i)=>(
          <circle key={i} cx={x} cy={y} r={0.7 + (i%3)*0.5} fill="#F4F1EA" opacity={0.4 + (i%3)*0.2} />
        ))}
        <g fontFamily="var(--font-serif)" fontStyle="italic" fontSize="16" fill="#F4F1EA" opacity="0.55">
          <text x="60" y="220">2:04 a.m.</text>
        </g>
      </svg>
    );
  }
  if (kind === 'thread') {
    return (
      <svg viewBox="0 0 600 240" preserveAspectRatio="xMidYMid slice" style={{ width: '100%', height: '100%' }}>
        <rect width="600" height="240" fill="var(--sage-tint)" />
        {[0,1,2,3,4,5,6].map(i => (
          <path
            key={i}
            d={`M -20 ${40 + i*30} Q 150 ${20 + i*30 + (i%2?40:-20)}, 300 ${40 + i*30} T 620 ${40 + i*30}`}
            fill="none"
            stroke="var(--sage-deep)"
            strokeWidth="0.6"
            opacity={0.35 - i*0.025}
          />
        ))}
        <circle cx="300" cy="120" r="6" fill="var(--clay)" />
      </svg>
    );
  }
  return null;
};

// ─────────────────────────────────────────────────────────────
// Hash routing
// ─────────────────────────────────────────────────────────────
// Read the active post slug from the current URL. Prefer the path-style
// canonical URL `/blog/<slug>` so social crawlers (Facebook, LinkedIn, X,
// iMessage) — which strip URL fragments before fetch — see the right per-
// post meta. Fall back to the legacy `#/<slug>` fragment for backward
// compatibility with already-shared links, and to the server-injected
// `__CORDA_INITIAL_SLUG__` when arriving at `/blog/<slug>` cold.
function readSlugFromLocation() {
  // /blog/<slug> path style (current canonical)
  const m = window.location.pathname.match(/^\/blog\/([a-z0-9-]+)\/?$/i);
  if (m) return m[1];
  // legacy #/<slug>
  const fromHash = window.location.hash.replace(/^#\/?/, '');
  if (fromHash) return fromHash;
  // server-injected fallback
  const injected = window.__CORDA_INITIAL_SLUG__;
  return typeof injected === 'string' ? injected : '';
}

function useHashRoute() {
  const [route, setRoute] = React.useState(readSlugFromLocation);
  React.useEffect(() => {
    const onChange = () => setRoute(readSlugFromLocation());
    window.addEventListener('hashchange', onChange);
    window.addEventListener('popstate', onChange);
    // If we hydrated from __CORDA_INITIAL_SLUG__ but the URL is still
    // /blog (no slug in path or hash), align the URL to the canonical
    // path-style so share buttons + crawlers see the right thing.
    if (route && !/^\/blog\/[a-z0-9-]+/i.test(window.location.pathname)) {
      try {
        history.replaceState({}, '', '/blog/' + route);
      } catch (_e) {}
    }
    return () => {
      window.removeEventListener('hashchange', onChange);
      window.removeEventListener('popstate', onChange);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  return route;
}

// Canonical href for an internal blog link. Path-style (no fragment) so
// shared URLs survive crawler scraping and the per-slug API route
// (`apps/api/app/blog/[slug]/route.ts`) can inject post-specific OG meta.
const postHref = (slug) => `/blog/${encodeURIComponent(slug)}`;

// Click handler used by every internal post link. Intercepts plain-left-
// click navigations and updates history.state in place — same single-page
// app feel as the old hash router, but with a real URL that crawlers can
// follow. Lets cmd/ctrl/middle-click open in new tab as normal.
const onPostLinkClick = (slug) => (e) => {
  if (e.defaultPrevented) return;
  if (e.button !== 0) return;
  if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
  e.preventDefault();
  try {
    history.pushState({}, '', postHref(slug));
    window.dispatchEvent(new PopStateEvent('popstate'));
    window.scrollTo({ top: 0, behavior: 'instant' });
  } catch (_e) {
    window.location.href = postHref(slug);
  }
};

const onBlogHomeClick = (e) => {
  if (e.defaultPrevented) return;
  if (e.button !== 0) return;
  if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
  e.preventDefault();
  try {
    history.pushState({}, '', '/blog');
    window.dispatchEvent(new PopStateEvent('popstate'));
    window.scrollTo({ top: 0, behavior: 'instant' });
  } catch (_e) {
    window.location.href = '/blog';
  }
};

// Canonical share URL for a post — used in the share buttons so visitors
// share the crawler-friendly /blog/<slug> path instead of the legacy
// hash-fragment URL (which scrapers strip before fetch).
const canonicalPostUrl = (slug) =>
  `${window.location.origin}/blog/${encodeURIComponent(slug)}`;

// ─────────────────────────────────────────────────────────────
// Nav
// ─────────────────────────────────────────────────────────────
const BlogNav = () => (
  <nav style={{
    position: 'sticky', top: 0, zIndex: 100,
    padding: '18px 48px',
    display: 'flex', alignItems: 'center', justifyContent: 'space-between',
    background: 'rgba(245, 242, 235, 0.78)',
    backdropFilter: 'saturate(180%) blur(18px)',
    WebkitBackdropFilter: 'saturate(180%) blur(18px)',
    borderBottom: '0.5px solid var(--hairline-soft)'
  }}>
    <a href="/" style={{ display: 'inline-flex', alignItems: 'center', gap: 10, textDecoration: 'none', color: 'var(--ink)' }}>
      <Logomark size={30} />
      <span style={{ fontFamily: 'var(--font-sans)', fontWeight: 600, fontSize: 22, letterSpacing: '-0.04em' }}>corda</span>
      <span style={{ marginLeft: 4, fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontSize: 20, color: 'var(--ink-3)', letterSpacing: '-0.02em' }}>journal</span>
    </a>
    <div className="blog-nav-links" style={{ display: 'flex', alignItems: 'center', gap: 36 }}>
      <a href="/#how" style={{ fontSize: 14, fontWeight: 500, color: 'var(--ink-2)', textDecoration: 'none', letterSpacing: -0.1 }}>How it works</a>
      <a href="/quiz.html" style={{ fontSize: 14, fontWeight: 500, color: 'var(--ink-2)', textDecoration: 'none', letterSpacing: -0.1 }}>Quiz</a>
      <a href="/#pricing" style={{ fontSize: 14, fontWeight: 500, color: 'var(--ink-2)', textDecoration: 'none', letterSpacing: -0.1 }}>Pricing</a>
      <a href="/blog" onClick={onBlogHomeClick} style={{ fontSize: 14, fontWeight: 500, color: 'var(--ink)', textDecoration: 'none', letterSpacing: -0.1 }}>Blog</a>
    </div>
    <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
      <a href="/#waitlist" style={{
        background: 'var(--ink)', color: 'var(--bg)', border: 'none',
        padding: '10px 18px', borderRadius: 999, fontSize: 14, fontWeight: 500,
        fontFamily: 'inherit', cursor: 'pointer', letterSpacing: -0.1,
        textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 6
      }}>
        <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--sage)' }} />
        Join the waitlist
      </a>
    </div>
  </nav>
);

// ─────────────────────────────────────────────────────────────
// Index
// ─────────────────────────────────────────────────────────────
const BlogIndex = ({ posts }) => {
  const FEATURED = posts.find(p => p.feature) || posts[0];
  const OTHERS   = posts.filter(p => p !== FEATURED);
  return (
  <main>
    <section style={{ padding: '72px 48px 24px', background: 'radial-gradient(ellipse 80% 60% at 50% 0%, var(--sage-tint), var(--bg) 70%)' }}>
      <div style={{ maxWidth: 1200, margin: '0 auto' }}>
        <div style={{ fontSize: 12, color: 'var(--sage-deep)', fontWeight: 600, letterSpacing: 1.4, textTransform: 'uppercase' }}>
          The Corda Journal
        </div>
        <h1 className="blog-index-h1" style={{
          fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontWeight: 400,
          fontSize: 72, lineHeight: 1.02, letterSpacing: '-0.03em',
          margin: '14px 0 18px', color: 'var(--ink)', maxWidth: 920, textWrap: 'balance'
        }}>
          Field notes from the<br />work of caring.
        </h1>
        <p style={{ fontSize: 18, lineHeight: 1.55, color: 'var(--ink-2)', maxWidth: 580, margin: 0 }}>
          Essays, guides, and small things we've learned while building Corda — written for the families who are doing the most important work in the world.
        </p>
      </div>
    </section>

    <section style={{ padding: '40px 48px 56px' }}>
      <div style={{ maxWidth: 1200, margin: '0 auto' }}>
        <a href={postHref(FEATURED.slug)} onClick={onPostLinkClick(FEATURED.slug)} className="blog-featured" style={{
          display: 'grid', gridTemplateColumns: '1.1fr 1fr', gap: 56, alignItems: 'center',
          textDecoration: 'none', color: 'inherit'
        }}>
          <PostCover kind={FEATURED.cover} imageUrl={FEATURED.coverImageUrl} size="hero" />
          <div>
            <div style={{ display: 'inline-flex', alignItems: 'center', gap: 10, marginBottom: 16 }}>
              <span style={{
                fontSize: 11, fontWeight: 600, letterSpacing: 1.3, textTransform: 'uppercase',
                color: 'var(--sage-deep)', padding: '4px 10px', borderRadius: 999,
                background: 'var(--sage-soft)'
              }}>
                Featured · {FEATURED.category}
              </span>
            </div>
            <h2 className="blog-featured-h2" style={{
              fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontWeight: 400,
              fontSize: 52, lineHeight: 1.05, letterSpacing: '-0.025em',
              margin: '0 0 20px', color: 'var(--ink)', textWrap: 'balance'
            }}>
              {FEATURED.title}
            </h2>
            <p style={{ fontSize: 17, lineHeight: 1.55, color: 'var(--ink-2)', margin: '0 0 28px' }}>
              {FEATURED.dek}
            </p>
            <div style={{ display: 'flex', alignItems: 'center', gap: 14, fontSize: 13.5, color: 'var(--ink-3)' }}>
              <Avatar name={FEATURED.author} imageUrl={FEATURED.authorAvatarUrl} size={32} tone="sage" />
              <div>
                <div style={{ color: 'var(--ink-2)', fontWeight: 500 }}>{FEATURED.author}</div>
                <div>{FEATURED.date} · {FEATURED.readMin} min read</div>
              </div>
            </div>
          </div>
        </a>
      </div>
    </section>

    <div style={{ maxWidth: 1200, margin: '0 auto', padding: '0 48px' }}>
      <div style={{ height: 1, background: 'var(--hairline-soft)' }} />
    </div>

    <section style={{ padding: '56px 48px 120px' }}>
      <div style={{ maxWidth: 1200, margin: '0 auto' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 36 }}>
          <h3 style={{
            fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontWeight: 400,
            fontSize: 32, letterSpacing: '-0.02em', margin: 0
          }}>
            More from the journal
          </h3>
          <div style={{ fontSize: 13, color: 'var(--ink-3)' }}>
            New posts most Tuesdays
          </div>
        </div>

        <div className="blog-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 32 }}>
          {OTHERS.map(p => (
            <a key={p.slug} href={postHref(p.slug)} onClick={onPostLinkClick(p.slug)} style={{
              textDecoration: 'none', color: 'inherit',
              display: 'flex', flexDirection: 'column', gap: 16
            }}>
              <PostCover kind={p.cover} imageUrl={p.coverImageUrl} />
              <div>
                <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: 1.3, textTransform: 'uppercase', color: 'var(--sage-deep)', marginBottom: 10 }}>
                  {p.category}
                </div>
                <h4 style={{
                  fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontWeight: 400,
                  fontSize: 26, lineHeight: 1.12, letterSpacing: '-0.02em',
                  margin: '0 0 10px', color: 'var(--ink)', textWrap: 'balance'
                }}>
                  {p.title}
                </h4>
                <p style={{ fontSize: 14.5, lineHeight: 1.55, color: 'var(--ink-2)', margin: '0 0 16px' }}>
                  {p.dek}
                </p>
                <div style={{ fontSize: 12.5, color: 'var(--ink-3)' }}>
                  {p.author} · {p.date} · {p.readMin} min read
                </div>
              </div>
            </a>
          ))}
        </div>
      </div>
    </section>

    <section style={{ padding: '0 48px 120px' }}>
      <div style={{ maxWidth: 1200, margin: '0 auto' }}>
        <div className="blog-cta" style={{
          padding: '56px 56px', borderRadius: 'var(--r-xl)',
          background: 'var(--ink)', color: 'var(--bg)',
          display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: 48, alignItems: 'center',
          position: 'relative', overflow: 'hidden'
        }}>
          <div style={{ position: 'absolute', top: -80, right: -80, width: 320, height: 320, borderRadius: '50%',
            background: 'radial-gradient(circle, var(--sage-deep), transparent 65%)', opacity: 0.4 }} />
          <div style={{ position: 'relative' }}>
            <div style={{ fontSize: 12, color: 'var(--sage)', fontWeight: 600, letterSpacing: 1.4, textTransform: 'uppercase' }}>
              The product behind the journal
            </div>
            <h3 style={{
              fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontWeight: 400,
              fontSize: 42, lineHeight: 1.08, letterSpacing: '-0.025em',
              margin: '14px 0 14px', color: 'var(--bg)', textWrap: 'balance'
            }}>
              Corda holds everything you're carrying.
            </h3>
            <p style={{ fontSize: 16, lineHeight: 1.55, opacity: 0.78, margin: 0, maxWidth: 460 }}>
              Appointments, visit notes, follow-up tasks, providers, the small things you remember at 2 a.m. We launch when 50 families have joined the waitlist — together, at once.
            </p>
          </div>
          <div style={{ position: 'relative', display: 'flex', justifyContent: 'flex-end', gap: 12, flexWrap: 'wrap' }}>
            <a href="/quiz.html" style={{
              padding: '16px 22px', borderRadius: 14,
              background: 'transparent', color: 'var(--bg)',
              border: '0.5px solid rgba(244,241,234,0.3)',
              fontSize: 15, fontWeight: 500, fontFamily: 'inherit', textDecoration: 'none',
              display: 'inline-flex', alignItems: 'center', gap: 8, letterSpacing: -0.1
            }} onClick={() => track('blog_cta_clicked', { slug: null, cta: 'index_quiz' })}>
              Take the quiz
            </a>
            <a href="/#waitlist" style={{
              padding: '16px 26px', borderRadius: 14, background: 'var(--bg)', color: 'var(--ink)',
              fontSize: 15, fontWeight: 500, fontFamily: 'inherit', textDecoration: 'none',
              display: 'inline-flex', alignItems: 'center', gap: 8, letterSpacing: -0.1
            }} onClick={() => track('blog_cta_clicked', { slug: null, cta: 'index_waitlist' })}>
              See what we're building
              <span style={{ fontSize: 18, lineHeight: 1 }}>→</span>
            </a>
          </div>
        </div>
      </div>
    </section>
  </main>
  );
};

// ─────────────────────────────────────────────────────────────
// Reader
// ─────────────────────────────────────────────────────────────
// Safe wrapper around the global cordaTrack helper exposed by analytics.js.
// No-ops when analytics is disabled or PostHog isn't loaded yet, so call sites
// never need a guard.
const track = (name, props) => {
  try { if (window.cordaTrack) window.cordaTrack(name, props || {}); } catch (_e) {}
};

const ReadingProgress = ({ slug }) => {
  const [pct, setPct] = React.useState(0);
  React.useEffect(() => {
    // Per-post guards. Local to the effect so they reset cleanly each time
    // `slug` changes — no refs, no global state.
    //
    // `armed` fixes a race noted in PR #22 code review: when a visitor
    // clicks a related-post link from the bottom of a previous article,
    // the new <BlogReader> mounts with the old scrollY still high. The
    // new ReadingProgress effect would compute pct >= 75 from that stale
    // scroll and fire `blog_post_read` for the new post before
    // BlogReader's own useEffect calls scrollTo(0). We arm the milestone
    // only after observing a low scroll position on the new post (< 20%),
    // so it can only fire on genuine read-through scrolling.
    let armed = false;
    let fired = false;
    const onScroll = () => {
      const h = document.documentElement;
      const scrolled = h.scrollTop;
      const total = h.scrollHeight - h.clientHeight;
      const next = total > 0 ? Math.min(scrolled / total, 1) * 100 : 0;
      setPct(next);
      if (!armed && next < 20) armed = true;
      if (armed && !fired && next >= 75 && slug) {
        fired = true;
        track('blog_post_read', { slug });
      }
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener('scroll', onScroll);
  }, [slug]);
  return (
    <div style={{
      position: 'fixed', top: 0, left: 0, right: 0, height: 2,
      background: 'transparent', zIndex: 200, pointerEvents: 'none'
    }}>
      <div style={{ height: '100%', width: `${pct}%`, background: 'var(--sage-deep)', transition: 'width .12s linear' }} />
    </div>
  );
};

const Block = ({ block, postSlug }) => {
  if (block.kind === 'p') {
    const style = {
      fontFamily: 'var(--font-serif)', fontStyle: 'normal', fontWeight: 400,
      fontSize: 21, lineHeight: 1.55, color: 'var(--ink)',
      margin: '0 0 24px', textWrap: 'pretty', letterSpacing: '-0.005em'
    };
    if (block.html) return <p style={style} dangerouslySetInnerHTML={{ __html: block.html }} />;
    return <p style={style}>{block.text}</p>;
  }
  if (block.kind === 'h2') {
    return (
      <h2 style={{
        fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontWeight: 400,
        fontSize: 34, lineHeight: 1.15, letterSpacing: '-0.02em',
        margin: '48px 0 20px', color: 'var(--ink)', textWrap: 'balance'
      }}>{block.text}</h2>
    );
  }
  if (block.kind === 'h3') {
    return (
      <h3 style={{
        fontFamily: 'var(--font-sans)', fontWeight: 600,
        fontSize: 18, lineHeight: 1.3, letterSpacing: '-0.015em',
        margin: '36px 0 12px', color: 'var(--ink)'
      }}>{block.text}</h3>
    );
  }
  if (block.kind === 'quote') {
    const style = {
      margin: '36px -8px', padding: '4px 0 4px 28px',
      borderLeft: '2px solid var(--sage-deep)',
      fontFamily: 'var(--font-serif)', fontStyle: 'italic',
      fontSize: 26, lineHeight: 1.35, color: 'var(--ink)',
      letterSpacing: '-0.015em', textWrap: 'pretty'
    };
    if (block.html) return <blockquote style={style} dangerouslySetInnerHTML={{ __html: block.html }} />;
    return <blockquote style={style}>{block.text}</blockquote>;
  }
  if (block.kind === 'list') {
    return (
      <ul style={{
        listStyle: 'none', padding: 0, margin: '0 0 28px',
        display: 'flex', flexDirection: 'column', gap: 14
      }}>
        {block.items.map((it, i) => (
          <li key={i} style={{
            display: 'flex', gap: 16, alignItems: 'flex-start',
            fontFamily: 'var(--font-serif)', fontSize: 20, lineHeight: 1.5,
            color: 'var(--ink)', letterSpacing: '-0.005em'
          }}>
            <span style={{
              flexShrink: 0, marginTop: 12,
              width: 6, height: 6, borderRadius: '50%', background: 'var(--sage-deep)'
            }} />
            {block.html
              ? <span style={{ textWrap: 'pretty' }} dangerouslySetInnerHTML={{ __html: it }} />
              : <span style={{ textWrap: 'pretty' }}>{it}</span>}
          </li>
        ))}
      </ul>
    );
  }
  if (block.kind === 'callout') {
    return (
      <div style={{
        margin: '36px 0', padding: '24px 28px',
        background: 'var(--sage-tint)', borderRadius: 'var(--r-md)',
        border: '0.5px solid var(--sage-soft)',
        display: 'flex', gap: 16, alignItems: 'flex-start'
      }}>
        <div style={{
          flexShrink: 0, width: 32, height: 32, borderRadius: 10, background: 'var(--sage-deep)',
          display: 'flex', alignItems: 'center', justifyContent: 'center', marginTop: 2
        }}>
          <Icon name="heart" size={16} color="var(--bg)" />
        </div>
        <div style={{ flex: 1 }}>
          <div style={{
            fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontSize: 18,
            lineHeight: 1.45, color: 'var(--sage-ink)', marginBottom: 12
          }}>{block.text}</div>
          <a href="/#waitlist" style={{
            display: 'inline-flex', alignItems: 'center', gap: 6,
            fontSize: 13.5, fontWeight: 600, color: 'var(--sage-deep)',
            textDecoration: 'none', letterSpacing: -0.1
          }} onClick={() => track('blog_cta_clicked', { slug: postSlug || null, cta: 'callout' })}>
            Join the waitlist <span style={{ fontSize: 14 }}>→</span>
          </a>
        </div>
      </div>
    );
  }
  if (block.kind === 'divider') {
    return <div style={{ height: 1, background: 'var(--hairline)', margin: '36px 0' }} />;
  }
  return null;
};

const ShareRow = ({ post }) => {
  // Share the canonical /blog/<slug> URL, not the hash-fragment one. Social
  // crawlers strip fragments before fetch, so /blog.html#/<slug> would never
  // unfurl correctly. The Vercel rewrite + apps/api meta-injector make
  // /blog/<slug> deliver post-specific OG / Twitter / JSON-LD meta.
  const shareHref = canonicalPostUrl(post.slug);
  const url = encodeURIComponent(shareHref);
  const txt = encodeURIComponent(`${post.title} — from the Corda Journal`);
  const btn = {
    display: 'inline-flex', alignItems: 'center', gap: 8,
    padding: '9px 14px', borderRadius: 999,
    border: '0.5px solid var(--hairline)',
    background: 'var(--surface)', color: 'var(--ink-2)',
    fontSize: 13, fontWeight: 500, fontFamily: 'inherit',
    textDecoration: 'none', letterSpacing: -0.1, cursor: 'pointer'
  };
  const shared = (channel) => track('blog_post_shared', { slug: post.slug, channel });
  const copy = (e) => {
    e.preventDefault();
    navigator.clipboard.writeText(shareHref);
    shared('copy_link');
    const t = e.currentTarget;
    const original = t.innerText;
    t.innerText = 'Link copied ✓';
    setTimeout(() => { t.innerText = original; }, 1500);
  };
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
      <span style={{ fontSize: 12, color: 'var(--ink-3)', letterSpacing: 1.2, textTransform: 'uppercase', fontWeight: 600, marginRight: 4 }}>Share</span>
      <a style={btn} href={`https://twitter.com/intent/tweet?text=${txt}&url=${url}`} target="_blank" rel="noreferrer"
         onClick={() => shared('twitter')}>𝕏 / Twitter</a>
      <a style={btn} href={`https://www.linkedin.com/sharing/share-offsite/?url=${url}`} target="_blank" rel="noreferrer"
         onClick={() => shared('linkedin')}>LinkedIn</a>
      <a style={btn} href={`https://www.facebook.com/sharer/sharer.php?u=${url}`} target="_blank" rel="noreferrer"
         onClick={() => shared('facebook')}>Facebook</a>
      <a style={btn} href={`mailto:?subject=${txt}&body=${url}`}
         onClick={() => shared('email')}>Email</a>
      <a style={btn} href="#" onClick={copy}>Copy link</a>
    </div>
  );
};

const RelatedPosts = ({ post, posts }) => {
  const related = posts.filter(p => p.slug !== post.slug).slice(0, 2);
  return (
    <div className="blog-related" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
      {related.map(p => (
        <a key={p.slug} href={postHref(p.slug)} onClick={onPostLinkClick(p.slug)} style={{
          textDecoration: 'none', color: 'inherit',
          padding: 20, borderRadius: 'var(--r-md)',
          border: '0.5px solid var(--hairline)',
          background: 'var(--surface)',
          display: 'flex', flexDirection: 'column', gap: 12
        }}>
          <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: 1.3, textTransform: 'uppercase', color: 'var(--sage-deep)' }}>
            {p.category}
          </div>
          <div style={{
            fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontWeight: 400,
            fontSize: 22, lineHeight: 1.15, letterSpacing: '-0.02em', color: 'var(--ink)',
            textWrap: 'balance'
          }}>{p.title}</div>
          <div style={{ fontSize: 13, color: 'var(--ink-3)' }}>{p.readMin} min read</div>
        </a>
      ))}
    </div>
  );
};

const BlogReader = ({ post, posts }) => {
  const blocks = React.useMemo(() => resolveBlocks(post), [post]);
  React.useEffect(() => {
    window.scrollTo({ top: 0, behavior: 'instant' });
    document.title = `${post.title} — Corda`;
    // Funnel signal: fires on every post open (incl. SPA route changes).
    // All props are public per-post metadata — never visitor PII.
    track('blog_post_viewed', {
      slug: post.slug,
      category: post.category || null,
      read_min: post.readMin || null,
      author: post.author || null,
    });
  }, [post.slug]);

  return (
    <main>
      <ReadingProgress slug={post.slug} />

      <article>
        <header style={{ padding: '64px 48px 40px' }}>
          <div style={{ maxWidth: 760, margin: '0 auto' }}>
            <a href="/blog" onClick={onBlogHomeClick} style={{
              display: 'inline-flex', alignItems: 'center', gap: 6,
              fontSize: 13, color: 'var(--ink-3)', textDecoration: 'none', marginBottom: 24
            }}>
              <span style={{ fontSize: 14 }}>←</span> The Corda Journal
            </a>
            <div style={{
              fontSize: 11, fontWeight: 600, letterSpacing: 1.4, textTransform: 'uppercase',
              color: 'var(--sage-deep)', marginBottom: 18
            }}>
              {post.category}
            </div>
            <h1 className="blog-reader-h1" style={{
              fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontWeight: 400,
              fontSize: 64, lineHeight: 1.05, letterSpacing: '-0.028em',
              margin: '0 0 24px', color: 'var(--ink)', textWrap: 'balance'
            }}>
              {post.title}
            </h1>
            <p style={{
              fontSize: 22, lineHeight: 1.45, color: 'var(--ink-2)', margin: 0,
              textWrap: 'pretty'
            }}>
              {post.dek}
            </p>

            <div style={{ display: 'flex', alignItems: 'center', gap: 16, marginTop: 36, flexWrap: 'wrap' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
                <Avatar name={post.author} imageUrl={post.authorAvatarUrl} size={38} tone="sage" />
                <div>
                  <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--ink)' }}>{post.author}</div>
                  <div style={{ fontSize: 12.5, color: 'var(--ink-3)' }}>{post.role}</div>
                </div>
              </div>
              <div style={{ width: 1, height: 28, background: 'var(--hairline)' }} />
              <div style={{ fontSize: 13.5, color: 'var(--ink-3)' }}>
                {post.date} · {post.readMin} min read
              </div>
            </div>
          </div>
        </header>

        <div style={{ padding: '0 48px 48px' }}>
          <div style={{ maxWidth: 980, margin: '0 auto' }}>
            <PostCover kind={post.cover} imageUrl={post.coverImageUrl} size="hero" />
          </div>
        </div>

        <div style={{ padding: '0 48px' }}>
          <div style={{ maxWidth: 680, margin: '0 auto' }}>
            {blocks.map((b, i) => <Block key={i} block={b} postSlug={post.slug} />)}
          </div>
        </div>

        <div style={{ padding: '64px 48px 0' }}>
          <div style={{ maxWidth: 680, margin: '0 auto' }}>
            <div style={{ height: 1, background: 'var(--hairline)', marginBottom: 32 }} />
            <ShareRow post={post} />
            <div style={{ display: 'flex', gap: 16, alignItems: 'flex-start', marginTop: 40, padding: 24, background: 'var(--surface-2)', borderRadius: 'var(--r-md)' }}>
              <Avatar name={post.author} imageUrl={post.authorAvatarUrl} size={48} tone="sage" />
              <div>
                <div style={{ fontSize: 14.5, fontWeight: 600 }}>{post.author}</div>
                <div style={{ fontSize: 13, color: 'var(--ink-3)', marginBottom: 8 }}>{post.role}</div>
                <p style={{ margin: 0, fontSize: 14, lineHeight: 1.55, color: 'var(--ink-2)' }}>
                  Writing about caregiving, software, and the quiet work of holding things together.
                </p>
              </div>
            </div>
          </div>
        </div>

        <div style={{ padding: '64px 48px 0' }}>
          <div style={{ maxWidth: 980, margin: '0 auto' }}>
            <div className="blog-reader-cta" style={{
              padding: '48px 48px', borderRadius: 'var(--r-xl)',
              background: 'var(--sage-tint)', border: '0.5px solid var(--sage-soft)',
              display: 'grid', gridTemplateColumns: '1fr auto', gap: 32, alignItems: 'center'
            }}>
              <div>
                <div style={{ fontSize: 12, color: 'var(--sage-deep)', fontWeight: 600, letterSpacing: 1.4, textTransform: 'uppercase' }}>
                  Corda
                </div>
                <h3 style={{
                  fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontWeight: 400,
                  fontSize: 36, lineHeight: 1.08, letterSpacing: '-0.025em',
                  margin: '10px 0 12px', color: 'var(--ink)', textWrap: 'balance'
                }}>
                  Everything you're carrying — held.
                </h3>
                <p style={{ fontSize: 16, lineHeight: 1.55, color: 'var(--ink-2)', margin: 0, maxWidth: 520 }}>
                  Take the 60-second Mental Load Quiz, then join the waitlist. We launch when 50 families are on board — together, at once.
                </p>
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                <a href="/quiz.html" style={{
                  padding: '14px 22px', borderRadius: 14,
                  background: 'var(--surface)', color: 'var(--ink)',
                  border: '0.5px solid var(--hairline)',
                  fontSize: 15, fontWeight: 500, fontFamily: 'inherit', textDecoration: 'none',
                  display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8, letterSpacing: -0.1,
                  whiteSpace: 'nowrap'
                }} onClick={() => track('blog_cta_clicked', { slug: post.slug, cta: 'reader_quiz' })}>
                  Take the quiz
                </a>
                <a href="/#waitlist" style={{
                  padding: '14px 22px', borderRadius: 14, background: 'var(--ink)', color: 'var(--bg)',
                  fontSize: 15, fontWeight: 500, fontFamily: 'inherit', textDecoration: 'none',
                  display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8, letterSpacing: -0.1,
                  whiteSpace: 'nowrap'
                }} onClick={() => track('blog_cta_clicked', { slug: post.slug, cta: 'reader_waitlist' })}>
                  <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--sage)' }} />
                  Join the waitlist
                </a>
              </div>
            </div>
          </div>
        </div>

        <div style={{ padding: '64px 48px 120px' }}>
          <div style={{ maxWidth: 980, margin: '0 auto' }}>
            <h3 style={{
              fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontWeight: 400,
              fontSize: 28, letterSpacing: '-0.02em', margin: '0 0 24px'
            }}>
              Keep reading
            </h3>
            <RelatedPosts post={post} posts={posts} />
          </div>
        </div>
      </article>
    </main>
  );
};

// ─────────────────────────────────────────────────────────────
// Footer
// ─────────────────────────────────────────────────────────────
const BlogFooter = () => (
  <footer style={{ padding: '40px 48px', borderTop: '0.5px solid var(--hairline-soft)' }}>
    <div style={{
      maxWidth: 1200, margin: '0 auto',
      display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 48, flexWrap: 'wrap'
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
        <a href="/" style={{ display: 'inline-flex', alignItems: 'center', gap: 10, textDecoration: 'none', color: 'var(--ink)' }}>
          <Logomark size={26} />
          <span style={{ fontFamily: 'var(--font-sans)', fontWeight: 600, fontSize: 19, letterSpacing: '-0.04em' }}>corda</span>
        </a>
        <span style={{ fontSize: 13, color: 'var(--ink-3)', fontFamily: 'var(--font-serif)', fontStyle: 'italic' }}>
          Caring shouldn't feel like surviving.
        </span>
      </div>
      <div style={{ display: 'flex', gap: 18, fontSize: 13, color: 'var(--ink-3)', flexWrap: 'wrap' }}>
        <a href="/" style={{ color: 'inherit', textDecoration: 'none' }}>Product</a>
        <a href="/blog" onClick={onBlogHomeClick} style={{ color: 'inherit', textDecoration: 'none' }}>Journal</a>
        <a href="/quiz.html" style={{ color: 'inherit', textDecoration: 'none' }}>Quiz</a>
        <a href="/#waitlist" style={{ color: 'inherit', textDecoration: 'none' }}>Waitlist</a>
        <a href="/privacy" style={{ color: 'inherit', textDecoration: 'none' }}>Privacy</a>
        <a href="/terms" style={{ color: 'inherit', textDecoration: 'none' }}>Terms</a>
        <span>© 2026 Corda, Inc.</span>
      </div>
    </div>
  </footer>
);

// ─────────────────────────────────────────────────────────────
// App
// ─────────────────────────────────────────────────────────────
function BlogApp() {
  const route = useHashRoute();
  const { posts, source } = usePosts();
  const post = posts.find(p => p.slug === route);

  return (
    <React.Fragment>
      <BlogNav />
      {source === 'stale' && !post && (
        <div role="status" style={{
          background: 'var(--amber-soft)',
          color: 'oklch(0.42 0.08 70)',
          padding: '8px 16px', textAlign: 'center',
          fontSize: 12.5, fontWeight: 500, letterSpacing: 0.1,
          borderBottom: '0.5px solid var(--hairline-soft)',
        }}>
          Showing recent posts — couldn't reach the live publishing service. Refresh in a moment.
        </div>
      )}
      {post ? <BlogReader post={post} posts={posts} /> : <BlogIndex posts={posts} />}
      <BlogFooter />
    </React.Fragment>
  );
}

Object.assign(window, { BlogApp });
