# @paywithflex/marketing-blocks

Drop-in HSA/FSA marketing blocks for any storefront or site — importable
**React components**, prerendered **HTML snippets**, and a shared stylesheet, so
eligible customers learn they can check out with their HSA/FSA card.

**Blocks:** Badge (with an explainer modal), Announcement Banner, Hero, Checkout
Walkthrough, Subscriptions, and FAQ. The same blocks power Flex's Shopify app,
so what you render matches the Shopify storefront experience.

## Install

```bash
npm install @paywithflex/marketing-blocks
```

## React

Just render the component — interactions, banner layout, and consent-gated
tracking wire themselves on mount and release on unmount (so SPA route changes
are safe). The package ships `"use client"`, so it works in Next.js App Router
without a wrapper.

```tsx
import { FlexBadge } from "@paywithflex/marketing-blocks";

import "@paywithflex/marketing-blocks/styles.css";

<FlexBadge shopName="Acme Wellness" eligibility="lomn" showSubscriptionTab />;
```

Anonymous usage counts are on by default once delivery is enabled (currently
disabled — see Privacy below). To configure analytics without touching any
global, mount `FlexMarketingProvider` near your app root — wire `consent` to
your consent banner to enable the per-session id, or pass `analytics={false}`
to silence everything:

```tsx
import { FlexMarketingProvider } from "@paywithflex/marketing-blocks";

<FlexMarketingProvider consent={cookieConsent.analytics}>
  <App />
</FlexMarketingProvider>;
```

Need to read the active config in a component? Use the
`useFlexMarketingConfig()` hook (reactive and SSR-safe) rather than reaching for
a global.

## Any stack (HTML)

Copy a file from `dist/snippets/` and load the behavior bundle. Assets and the
script are served by **jsDelivr** straight from the published npm package — no
separate hosting. (`initFlexMarketingBlocks()` is also exported for hosts that
inject markup after load.)

```html
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@paywithflex/marketing-blocks@0/dist/bundle/styles.css"
/>
<script
  src="https://cdn.jsdelivr.net/npm/@paywithflex/marketing-blocks@0/dist/bundle/flex-marketing-blocks.js"
  defer
></script>
```

### Environment notes

- **Pass a stable `blockId` to dismissible banners in SPAs.** Dismissal
  persists in localStorage keyed by the block's id; without an explicit
  `blockId`, the auto-generated id can change across remounts and a dismissed
  banner can reappear after route changes.
- **`blockId` is also the fix for multiple React roots.** React's `useId` is
  unique within one root; two separate roots (micro-frontends) can collide.
- **The fixed banner needs an untransformed ancestor chain.** `position: fixed`
  resolves to the nearest transformed ancestor (a CSS platform rule, not ours)
  — inside a `transform`ed container, use `placement="inline"`.

## Custom CSS hooks

Every element carries a stable, BEM-style class (`flex-<block>__<element>`,
modifiers as `--<modifier>`) so you can target anything with custom CSS. **These
class names are a public API** — renaming one is a breaking change. Blocks also
expose CSS custom properties (`--flex-font`, `--flex-accent`, `--flex-align`,
`--flex-badge-size`, `--flex-hero-radius`, `--flex-faq-card`, `--flex-faq-bg`,
`--flex-faq-width`, `--flex-banner-z`) for the most common overrides. Every TEXT
role additionally exposes a token pair — `--flex-<role>-size` /
`--flex-<role>-color` (roles: `title`, `subtitle`, `eyebrow`, `step`,
`question`, `answer`) — read with precedence token > setting/prop > default, so
custom CSS can retheme text globally (`:root { --flex-title-color: navy }`) or
per block (`.flex-faq { --flex-title-size: 28px }`). Size tokens include the
unit.

| Block         | Hooks                                                                                                                                                                                                                                                                                                                                        |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Badge         | `flex-badge-wrapper`, `flex-badge` (`--light/--dark`), `flex-badge__open`, `__text`, `__pill`, `__pill-mark`, `__info`                                                                                                                                                                                                                       |
| Modal         | `flex-dialog`, `flex-dialog__close`, `flex-modal`, `flex-modal__logo-link`, `__logo`, `__heading`, `__title`, `__subtitle`, `__tabs`, `__tab`, `__panel`, `__steps`, `__step`, `__step-row`, `__step-number`, `__step-text`, `__illustration`, `__footer`, `__footer-row`, `__powered`, `__powered-mark`, `__footer-divider`, `__disclaimer` |
| Banner        | `flex-banner` (`--light/--dark/--inline`), `flex-banner__logo`, `__divider`, `__text`, `__chip`, `__dismiss`                                                                                                                                                                                                                                 |
| Hero          | `flex-hero` (`--light/--dark`), `flex-hero__content`, `__inner`, `__pill` (`--navy/--black/--white`), `__pill-mark`, `__title`, `__subtitle`, `__media`, `__img`                                                                                                                                                                             |
| Walkthrough   | `flex-walkthrough`, `flex-walkthrough__heading`, `__eyebrow`, `__title`, `__subtitle`, `__steps`, `__step`, `__illustration`, `__step-row`, `__step-number`, `__step-text`                                                                                                                                                                   |
| Subscriptions | `flex-subscription`, `flex-subscription__heading`, `__eyebrow`, `__title`, `__subtitle`, `__content`, `__media`, `__img`, `__steps`, `__step`, `__step-number`, `__step-text`                                                                                                                                                                |
| FAQ           | `flex-faq`, `flex-faq__inner`, `__heading`, `__title`, `__group-heading`, `__item`, `__question`, `__question-text`, `__toggle`, `__icon`, `__answer`, `__answer-inner`                                                                                                                                                                      |

```css
/* example: a merchant rounding the FAQ rows and recoloring step numbers */
.flex-faq__item {
  border-radius: 14px;
}
.flex-walkthrough__step-number {
  background: #2a7d4f;
}
```

## Typography

Blocks **inherit the host page's font** by default so they blend into your site
— except the badge **modal**, a Flex-branded overlay that defaults to **Inter**.
Everything is driven by one token, `--flex-font`. To render every block in Inter
on the web (opt-in, ~100KB once):

```tsx
import "@paywithflex/marketing-blocks/inter.css";
```

```css
:root {
  --flex-font: "Inter Variable", sans-serif;
}
```

To override the font (including the modal's Inter default), set `--flex-font` to
anything else.

## Privacy

> **Analytics delivery is not yet active in this release** — no tracking
> beacons are sent. The consent options below exist but are inert until a future
> release turns delivery on. The contract here is what applies once it is.

When enabled, blocks send anonymous `rendered` / `clicked` counts (block type,
variant, shop domain, action) to a Flex endpoint that discards IPs. Two tiers:

- **Anonymous counts (default on, off-Shopify):** nothing is stored on the
  visitor's device and nothing identifies them. Silence everything with
  `analytics: false` / `<FlexMarketingProvider analytics={false}>`.
- **Uniques (opt-in, off-Shopify):** a random per-tab-session id, created only
  once allowed and gone when the tab closes. Enable via `consent: true` /
  `<FlexMarketingProvider consent>` wired to your consent banner.

On Shopify, the shop's own **Customer Privacy API governs both tiers**. Events
never carry user identifiers, URLs, or product data.

## License

[Apache-2.0](./LICENSE) © Flex Technology Co.
