# @xenterprises/nuxt-x-marketing

A comprehensive Nuxt layer for building marketing websites with 38+ pre-built components. Features dark mode support, responsive design, accessibility, and seamless integration with Nuxt UI.

## Features

- **38+ Marketing Components** - Hero, Features, Pricing, Testimonials, Blog, Affiliate/Review, and more
- **Consent-aware tracking** - Drop GTM/GA4/Clarity IDs in `app.config`, the cookie banner auto-fires them on accept
- **Zero Required Props** - Every component works out of the box with sensible defaults
- **Dark Mode Support** - All components support light and dark themes
- **Responsive Design** - Mobile-first approach with tablet and desktop breakpoints
- **Accessible** - WCAG 2.0 AA compliant with proper ARIA attributes
- **Customizable** - Props-based configuration with slot overrides
- **Nuxt UI v4 Integration** - Built on top of Nuxt UI v4 components
- **Animations** - Scroll-triggered fade-in animations and parallax effects
- **Two Footer Modes** - Generic white-label (`XMarkLayoutFooter`) and X Enterprises branded (`XFooter`)

## Installation

```bash
npm install @xenterprises/nuxt-x-marketing
```

Add the layer to your `nuxt.config.js`:

```javascript
export default defineNuxtConfig({
  extends: "@xenterprises/nuxt-x-marketing",
});
```

## Quick Start

### What the consumer writes

The layer is batteries-included: it ships a default app shell (navbar + footer + cookie-consent banner) and default pages (`/`, `/blog`, `/blog/[...slug]`). You do **not** write an `app.vue` — configure the shell in `app/app.config.ts` under the `xMarketing` namespace (it must live in `app/`, not the project root):

```javascript
// app/app.config.ts
export default defineAppConfig({
  xMarketing: {
    name: "Acme Inc",
    url: "https://acme.com",
    header: {
      logo: { src: "/logo.svg", srcDark: "/logo-dark.svg", alt: "Acme" },
      nav: {
        links: [
          { label: "Pricing", to: "/#pricing" },
          { label: "Blog", to: "/blog" },
        ],
        buttons: [{ label: "Get Started", to: "/signup", color: "primary" }],
      },
    },
    footer: {
      logo: { src: "/logo.svg", alt: "Acme" },
      body: "Building the future of modern software.",
      socials: [
        { name: "GitHub", url: "https://github.com/acme", icon: "i-lucide-github" },
      ],
      columns: [
        {
          headerLabel: "Product",
          links: [{ label: "Blog", to: "/blog" }],
        },
      ],
    },
    blog: { active: true, title: "Blog" },
    tracking: { gtmId: "GTM-XXXXXXX" }, // optional; fires only after consent
  },
});
```

Blog posts are markdown in `content/blog/*.md` — re-declare the `blog` collection in your own `content.config.ts` so Nuxt Content binds it to your content dir (the layer's `.playground/content.config.ts` is the reference).

### Opting out of the defaults

Every shipped default has a switch or a standard Nuxt override:

- `xMarketing.header.active: false` — removes the default navbar from the shell
- `xMarketing.footer.active: false` — removes the default footer
- `xMarketing.consent.active: false` — removes the cookie-consent banner (tracking scripts then never fire)
- `xMarketing.blog.active: false` — the shipped `/blog` pages 404
- Replace the whole shell by shipping your own `app/app.vue`; replace any page by shipping the same path under `app/pages/` (standard Nuxt layer overriding)

### Example Landing Page

All components work with zero required props — just drop them in and customize as needed:

```vue
<template>
  <div>
    <!-- Hero — works with zero props, or fully customized -->
    <XMarkHero
      :img="{ src: 'https://images.unsplash.com/photo-1551434678-e076c223a692', alt: 'Hero' }"
      eyebrow="Welcome to the future"
      title="Build something amazing today"
      subtitle="The modern platform for teams who want to ship faster."
      align="left"
      overlay="gradient"
      :buttons="[
        { label: 'Get Started Free', color: 'primary' },
        { label: 'Watch Demo', variant: 'outline' }
      ]"
    />

    <!-- Features — works with zero props, or pass your own -->
    <XMarkSection id="features" bg="default" padding="xl">
      <header class="text-center max-w-3xl mx-auto mb-16">
        <h2 class="xText-headline">Everything you need to succeed</h2>
      </header>
      <XMarkFeatures :features="features" layout="grid" :columns="3" />
    </XMarkSection>

    <!-- Testimonials -->
    <XMarkSection bg="subtle" padding="xl">
      <XMarkTestimonials :testimonials="testimonials" layout="grid" />
    </XMarkSection>

    <!-- Pricing -->
    <XMarkSection bg="default" padding="xl">
      <XMarkPricingPlans :plans="pricingPlans" />
    </XMarkSection>
  </div>
</template>

<script setup>
const features = [
  { icon: 'i-lucide-zap', title: 'Lightning Fast', description: 'Built for speed.' },
  { icon: 'i-lucide-shield-check', title: 'Secure by Default', description: 'Enterprise-grade security.' },
  { icon: 'i-lucide-users', title: 'Team Collaboration', description: 'Real-time collaboration tools.' },
]

const testimonials = [
  { quote: 'This platform transformed how our team works.', name: 'Sarah Chen', title: 'CTO', company: 'TechStart', rating: 5 },
]

const pricingPlans = [
  { name: 'Starter', price: '$19', period: '/month', features: ['5 team members', 'Basic analytics'], button: { label: 'Start Free Trial' } },
  { name: 'Pro', price: '$49', period: '/month', features: ['25 team members', 'Advanced analytics'], button: { label: 'Start Free Trial' }, popular: true },
]
</script>
```

---

## Components

### Core Layout

#### XMarkLayoutNavbar

Fixed navigation header with transparent-to-solid scroll transition. Falls back to `xMarketing.header` (logo, nav links, nav buttons) when props are omitted.

```vue
<XMarkLayoutNavbar
  :links="[{ label: 'Features', to: '/#features' }]"
  :logo="{ src: '/logo.svg', srcDark: '/logo-dark.svg', alt: 'Company' }"
  :buttons="[
    { label: 'Sign In', to: '/login', variant: 'ghost' },
    { label: 'Get Started', to: '/signup', color: 'primary' },
  ]"
  :transparent="true"
  :scroll-threshold="100"
/>
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `links` | `Array` | `xMarketing.header.nav.links` | Navigation links `{ label, to }` |
| `logo` | `Object` | `xMarketing.header.logo` | Logo `{ src, srcDark?, alt? }` |
| `buttons` | `Array` | `xMarketing.header.nav.buttons` | Action buttons `{ label, to, color?, variant?, icon? }` |
| `transparent` | `Boolean` | `true` | Start transparent over hero |
| `scrollThreshold` | `Number` | `100` | Pixels before transition |

#### XMarkSection

Section wrapper with background variants and optional patterns.

```vue
<XMarkSection bg="subtle" padding="xl" pattern="dots">
  <h2>Section Content</h2>
</XMarkSection>
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `bg` | `String` | `'default'` | `default`, `subtle`, `elevated`, `bold`, `transparent` |
| `padding` | `String` | `'lg'` | `none`, `sm`, `md`, `lg`, `xl` |
| `container` | `String` | `'lg'` | `sm`, `md`, `lg`, `xl`, `full`, `none` |
| `pattern` | `String` | `''` | `dots`, `grid`, `diagonal`, `topography`, `circuit`, `waves` |
| `patternOpacity` | `Number` | `0.05` | Pattern opacity (0-1) |
| `bgImage` | `String` | `''` | Background image URL |
| `parallax` | `Boolean` | `false` | Enable parallax on bg image |

#### XMarkLayoutFooter

Full footer with brand, link columns, social icons, and newsletter.

```vue
<XMarkLayoutFooter
  :logo="{ src: '/logo.svg', alt: 'Company' }"
  description="Building the future of modern software."
  :social="[{ name: 'Twitter', href: 'https://twitter.com', icon: 'i-lucide-twitter' }]"
  :columns="[{ title: 'Product', links: [{ label: 'Features', to: '/features' }] }]"
  :legal-links="[{ label: 'Privacy', to: '/privacy' }]"
  :has-newsletter="true"
  @newsletter-submit="handleSubmit"
/>
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `logo` | `Object` | `null` | Logo `{ src, alt? }` |
| `description` | `String` | `''` | Brand description |
| `social` | `Array` | `[]` | Social links `{ name, href, icon }` |
| `columns` | `Array` | `[]` | Link columns `{ title, links: [{ label, to }] }` |
| `legalLinks` | `Array` | `[]` | Legal links `{ label, to }` |
| `copyright` | `String` | auto | Copyright text |
| `hasNewsletter` | `Boolean` | `false` | Show newsletter section |

#### XMarkLayoutFooterLegal

Minimal footer with copyright and legal links only.

```vue
<XMarkLayoutFooterLegal
  copyright="© 2026 Acme Inc. All rights reserved."
  :links="[{ label: 'Privacy', to: '/privacy' }]"
/>
```

---

### Hero & Landing

#### XMarkHero

Full-screen hero with image/video background and overlay.

```vue
<XMarkHero
  image-src="https://example.com/hero.jpg"
  video-src="https://example.com/hero.mp4"
  eyebrow="Welcome"
  title="Build something amazing"
  subtitle="The modern platform for teams."
  align="left"
  vertical-align="center"
  overlay="gradient"
  :show-scroll-indicator="true"
>
  <template #actions>
    <UButton size="xl" color="white">Get Started</UButton>
  </template>
</XMarkHero>
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `imageSrc` | `String` | `''` | Background image URL |
| `videoSrc` | `String` | `''` | Background video URL |
| `eyebrow` | `String` | `''` | Small text above title |
| `title` | `String` | required | Main headline |
| `subtitle` | `String` | `''` | Supporting text |
| `align` | `String` | `'left'` | `left`, `center`, `right` |
| `verticalAlign` | `String` | `'center'` | `center`, `bottom` |
| `overlay` | `String` | `'gradient'` | `light`, `heavy`, `gradient`, `none` |
| `showScrollIndicator` | `Boolean` | `true` | Show scroll arrow |

---

### Content Sections

#### XMarkFeatures

Feature grid with icons, images, and optional links.

```vue
<XMarkFeatures
  :features="[
    { icon: 'i-lucide-zap', title: 'Fast', description: 'Lightning fast performance.' },
    { icon: 'i-lucide-shield', title: 'Secure', description: 'Enterprise security.' },
  ]"
  layout="grid"
  :columns="3"
/>
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `features` | `Array` | required | Features `{ icon?, image?, title, description, link? }` |
| `layout` | `String` | `'grid'` | `grid`, `list`, `alternating` |
| `columns` | `Number` | `3` | Grid columns (2, 3, or 4) |

#### XMarkTestimonials

Testimonial cards in grid or carousel layout.

```vue
<XMarkTestimonials
  :testimonials="[
    {
      quote: 'Amazing product!',
      name: 'John Doe',
      title: 'CEO',
      company: 'Acme Inc',
      avatar: 'https://example.com/avatar.jpg',
    },
  ]"
  layout="grid"
/>
```

#### XMarkPricing

Pricing cards with billing toggle and popular badge.

```vue
<XMarkPricing
  :plans="[
    {
      name: 'Starter',
      price: '$19',
      period: 'month',
      description: 'Perfect for small teams.',
      features: ['5 team members', 'Basic analytics'],
      cta: 'Start Free Trial',
    },
    {
      name: 'Pro',
      price: '$49',
      period: 'month',
      features: ['25 team members', 'Advanced analytics'],
      cta: 'Start Free Trial',
      popular: true,
    },
  ]"
  :show-billing-toggle="true"
  :yearly-discount="20"
  @select="handlePlanSelect"
/>
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `plans` | `Array` | required | Pricing plans |
| `highlighted` | `String` | `''` | Plan name to highlight |
| `showBillingToggle` | `Boolean` | `false` | Show monthly/yearly toggle |
| `yearlyDiscount` | `Number` | `20` | Yearly discount percentage |

#### XMarkComparison

Feature comparison table across plans.

```vue
<XMarkComparison
  :plans="['Starter', 'Pro', 'Enterprise']"
  :highlighted="'Pro'"
  :feature-groups="[
    {
      name: 'Core Features',
      features: [
        { name: 'Users', values: ['5', '25', 'Unlimited'] },
        { name: 'Storage', values: ['1GB', '10GB', 'Unlimited'] },
      ],
    },
  ]"
/>
```

#### XMarkNewsletter

Email signup form with variants.

```vue
<XMarkNewsletter
  title="Subscribe to our newsletter"
  description="Get the latest updates."
  :button="{ label: 'Subscribe' }"
  placeholder="Enter your email"
  variant="default"
  @submit="handleSubmit"
/>
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `title` | `String` | `'Subscribe...'` | Form title |
| `description` | `String` | `''` | Form description |
| `button` | `Object` | `{ label: 'Subscribe' }` | Button config { label, color? } |
| `variant` | `String` | `'default'` | `default`, `minimal`, `inline` |

---

### Blog Components

#### XMarkBlogCard

Blog post card with image, title, excerpt, and author.

```vue
<XMarkBlogCard
  :post="{
    title: 'Getting Started with Nuxt',
    slug: 'getting-started',
    excerpt: 'Learn how to build modern web apps.',
    image: '/blog/cover.jpg',
    date: '2024-01-15',
    author: { name: 'John Doe', avatar: '/avatars/john.jpg' },
    category: 'Tutorial',
  }"
  variant="default"
/>
```

#### XMarkBlogList

Blog listing grid with optional featured post.

```vue
<XMarkBlogList
  :posts="posts"
  :columns="3"
  :show-featured="true"
/>
```

#### XMarkBlogDetail

Full blog post view with author, share buttons, and navigation.

```vue
<XMarkBlogDetail
  :post="post"
  :author="author"
  :related-posts="relatedPosts"
/>
```

#### XMarkBlogSidebar

Blog sidebar with search, categories, tags, and recent posts.

```vue
<XMarkBlogSidebar
  :categories="['Tutorials', 'News', 'Updates']"
  :tags="['vue', 'nuxt', 'javascript']"
  :recent-posts="recentPosts"
  :show-search="true"
  @search="handleSearch"
  @category-click="handleCategoryClick"
/>
```

#### XMarkBlogAuthor

Author bio with social links.

```vue
<XMarkBlogAuthor
  :author="{
    name: 'John Doe',
    title: 'Senior Developer',
    avatar: '/avatars/john.jpg',
    bio: 'Passionate about building great software.',
    social: { twitter: 'https://twitter.com/johndoe' },
  }"
  variant="full"
/>
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `author` | `Object` | required | Author data |
| `variant` | `String` | `'inline'` | `inline`, `sm`, `full`, `card` |

#### XMarkBlogNavigation

Previous/next post navigation.

```vue
<XMarkBlogNavigation
  :previous="{ title: 'Previous Post', slug: 'prev-post' }"
  :next="{ title: 'Next Post', slug: 'next-post' }"
  base-path="/blog"
/>
```

#### XMarkBlogCTA

In-article newsletter signup.

```vue
<XMarkBlogCTA
  title="Enjoyed this article?"
  description="Subscribe for more content."
  variant="featured"
  @submit="handleSubmit"
/>
```

---

### UI Elements

#### XMarkBadge

Trust and feature badges with presets.

```vue
<XMarkBadge preset="no-credit-card" variant="subtle" />
<XMarkBadge preset="cancel-anytime" />
<XMarkBadge icon="i-lucide-star" label="5-star rated" />
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `preset` | `String` | `''` | `no-credit-card`, `cancel-anytime`, `money-back`, `free-trial`, `secure` |
| `icon` | `String` | `''` | Custom icon (if no preset) |
| `label` | `String` | `''` | Custom label (if no preset) |
| `variant` | `String` | `'default'` | `default`, `subtle`, `outline` |

#### XMarkGlassCard

Glassmorphism card with blur effect.

```vue
<XMarkGlassCard :blur="10" :opacity="0.1">
  <h3>Card Content</h3>
</XMarkGlassCard>
```

#### XMarkPromoCard

Promotional card with image and CTA.

```vue
<XMarkPromoCard
  title="Special Offer"
  description="Get 50% off your first month."
  image="/promo.jpg"
  cta-text="Claim Offer"
  cta-link="/pricing"
/>
```

#### XMarkGlowDivider

Glowing section divider.

```vue
<XMarkGlowDivider color="primary" :intensity="0.5" />
```

#### XMarkPatternBg

SVG background patterns.

```vue
<XMarkPatternBg pattern="dots" :opacity="0.05" />
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `pattern` | `String` | `'dots'` | `dots`, `grid`, `diagonal`, `topography`, `circuit`, `waves` |
| `opacity` | `Number` | `0.05` | Pattern opacity |

#### XMarkSectionStitch

Section divider shapes.

```vue
<XMarkSectionStitch shape="wave" position="top" :flip="false" />
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `shape` | `String` | `'wave'` | `angle`, `wave`, `curve`, `triangle`, `zigzag` |
| `position` | `String` | `'bottom'` | `top`, `bottom` |
| `flip` | `Boolean` | `false` | Flip horizontally |

---

### Modals & Overlays

#### XMarkVideoModal

Video lightbox supporting YouTube, Vimeo, and direct URLs.

```vue
<XMarkVideoModal
  v-model="showVideo"
  url="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  title="Product Demo"
/>
```

#### XMarkImageLightbox

Image gallery lightbox with keyboard navigation.

```vue
<XMarkImageLightbox
  v-model="showLightbox"
  :images="[
    { src: '/gallery/1.jpg', alt: 'Image 1', caption: 'First image' },
    { src: '/gallery/2.jpg', alt: 'Image 2' },
  ]"
  :start-index="0"
/>
```

#### XMarkFeatureModal

Feature detail modal.

```vue
<XMarkFeatureModal
  v-model="showFeature"
  :feature="{
    icon: 'i-lucide-zap',
    title: 'Lightning Fast',
    description: 'Detailed description...',
    image: '/features/speed.jpg',
  }"
/>
```

---

### Notifications & Banners

#### XMarkAnnouncementBar

Dismissible top announcement banner.

```vue
<XMarkAnnouncementBar
  message="New feature available!"
  link-text="Learn more"
  link-url="/features"
  variant="primary"
  :dismissible="true"
  @dismiss="handleDismiss"
/>
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `message` | `String` | required | Announcement text |
| `linkText` | `String` | `''` | Optional link text |
| `linkUrl` | `String` | `''` | Optional link URL |
| `variant` | `String` | `'primary'` | `primary`, `neutral`, `success`, `warning`, `error` |
| `dismissible` | `Boolean` | `true` | Show dismiss button |

#### XMarkCookieBanner

Cookie consent banner.

```vue
<XMarkCookieBanner
  message="We use cookies to improve your experience."
  accept-text="Accept All"
  decline-text="Decline"
  :show-preferences="true"
  @accept="handleAccept"
  @decline="handleDecline"
  @preferences="handlePreferences"
/>
```

#### XMarkCookieToast

Cookie toast notification.

```vue
<XMarkCookieToast
  message="We use cookies"
  @accept="handleAccept"
/>
```

#### XMarkPrivacyCookieConsent

**Consent-aware cookie banner + preferences modal that auto-fires tracking scripts.** Drop your GTM/GA4/Clarity IDs into `app.config.ts`, drop `<XMarkPrivacyCookieConsent />` into your layout, and the component handles banner display, consent storage, and dynamic script injection.

```vue
<!-- app.vue -->
<template>
  <div>
    <NuxtPage />
    <XMarkPrivacyCookieConsent
      policy-url="/cookies"
      privacy-url="/privacy"
    />
  </div>
</template>
```

```ts
// app.config.ts
export default defineAppConfig({
  xMarketing: {
    tracking: {
      gtmId: 'GTM-XXXXXXX',
      ga4Id: 'G-XXXXXXXX',
      clarityId: 'abc123def4',
      // Optional escape hatch for anything else (Meta Pixel, Hotjar, etc.)
      scripts: [
        {
          id: 'meta-pixel',
          src: 'https://connect.facebook.net/en_US/fbevents.js',
          category: 'marketing',
          attrs: { async: '' },
        },
      ],
      autoInject: true, // default
    },
  },
})
```

Scripts only fire after the visitor grants consent for the matching category (analytics or marketing). On revisit, the client plugin auto-loads stored scripts immediately — no flash of banner.

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `title` | `String` | `'We use cookies'` | Banner heading |
| `message` | `String` | (see source) | Banner body text |
| `acceptLabel` | `String` | `'Accept all'` | Accept button text |
| `rejectLabel` | `String` | `'Reject all'` | Reject button text |
| `saveLabel` | `String` | `'Save preferences'` | Save button text (modal) |
| `policyLabel` | `String` | `'Read our policy'` | Policy link text |
| `policyUrl` | `String` | — | Cookie policy URL |
| `privacyUrl` | `String` | — | Privacy policy URL |
| `prefsTitle` | `String` | `'Cookie preferences'` | Modal title |
| `prefsDescription` | `String` | (see source) | Modal description |
| `showCustomize` | `Boolean` | `true` | Show the Customize button |
| `categories` | `Category[]` | (4 standard) | Categories shown in the modal |
| `storageKey` | `String` | `'xMarketing.consent'` | localStorage key |
| `forceShow` | `Boolean` | `false` | Force the banner open (e.g. from a "Manage cookies" link) |

| Emit | Payload | When |
|------|---------|------|
| `@accept` | `Record<CategoryId, boolean>` | Visitor accepted all |
| `@reject` | `Record<CategoryId, boolean>` | Visitor rejected all |
| `@save` | `Record<CategoryId, boolean>` | Visitor saved preferences |

**Underlying composable** — `useConsentTracking()` is auto-imported. Use it directly if you need to programmatically grant consent or read state:

```ts
const consent = useConsentTracking()
consent.acceptAll()       // grant every category
consent.rejectAll()       // grant only necessary
consent.hasConsent('analytics')
consent.state.value       // reactive { necessary, analytics, marketing, preferences }
```

#### XMarkGDPR

GDPR cookie preference modal.

```vue
<XMarkGDPR
  v-model="showGDPR"
  :categories="[
    { id: 'necessary', name: 'Necessary', description: 'Required for the site to work.', required: true },
    { id: 'analytics', name: 'Analytics', description: 'Help us improve.', default: false },
  ]"
  @save="handleSavePreferences"
/>
```

#### XMarkSocialProofToast

Social proof notification toasts.

```vue
<XMarkSocialProofToast
  :notifications="[
    { name: 'John', location: 'New York', action: 'signed up', time: '2 minutes ago' },
  ]"
  :interval="5000"
  position="bottom-left"
/>
```

---

### Utilities

#### XMarkBackToTop

Scroll to top button.

```vue
<XMarkBackToTop :show-after="300" :smooth="true" />
```

#### XMarkPopupChat

Chat widget trigger.

```vue
<XMarkPopupChat
  provider="intercom"
  :config="{ app_id: 'your-app-id' }"
/>
```

---

### Affiliate & Review Components

Removed in favour of [`@xenterprises/nuxt-x-affiliate`](../nuxt-x-affiliate).

`XMarkAffiliateDisclosure`, `XMarkAffiliateProductCard`, `XMarkAffiliateProductGrid`,
`XMarkAffiliateProductDetail`, and `XMarkAffiliateComparisonTable` duplicated components
the affiliate layer already ships — without its per-merchant tagging
(`xAffiliateContent.merchants`), impression tracking, or Product/Offer structured data.
Use `<XAFDisclosure>`, `<XAFProductCard>`, `<XAFRelatedProducts>`, `<XAFBuyButton>`,
and `<XAFComparisonTable>` instead.

## Configuration

Configure the layer in your `app.config.js`:

```javascript
export default defineAppConfig({
  xMarketing: {
    name: "X Enterprises",
    config: {
      markerProjectId: "your-marker-project-id", // Optional: Marker.io integration
    },
  },
});
```

---

## Testing

### Unit Tests

Run unit tests with Vitest:

```bash
npm run test:unit
```

### E2E Tests

Run end-to-end tests with Playwright:

```bash
npm run test:e2e
```

---

## Composables

| Composable | Description |
|------------|-------------|
| `useScrollReveal(options?)` | Intersection Observer scroll animations. Auto-adds `is-visible` class to `[data-reveal]`, `.xFadeUp`, `.xFadeIn`, `.xFadeLeft`, `.xFadeRight`, `.xScale`, `.xFadeUp-stagger` elements. |
| `useStaggerReveal(selector, delay?)` | Adds incremental `transition-delay` to `[data-reveal]` children inside a container. |
| `useParallax(options?)` | Parallax scroll effect for `.xParallax[data-parallax-speed]` elements. Uses `requestAnimationFrame` for performance. |
| `useElementParallax(speed?)` | Individual element parallax via template ref. Returns `{ elementRef }`. |
| `useXBlog()` | Nuxt Content blog helpers (`getPosts`, `getPostByPath`, `getSurround`, `normalizeBlogPost`). Expects a `blog` collection and markdown under `content/blog/`. |

All composables are SSR-safe — they guard lifecycle hooks with `getCurrentInstance()` and `import.meta.client` checks.

---

## Environment Variables

| Name | Required | Description |
|------|----------|-------------|
| Marker.io Project ID | No | Set via `appConfig.xMarketing.config.markerProjectId` (not env var) |
| Newsletter Org ID | No | Set via `appConfig.xMarketing.config.emailMarketingNewsletters.organizationId` |

The layer itself has no required environment variables. Blog content is **file-based via Nuxt Content** (not Builder.io). All site config is done via `app.config.ts`.

---

## CSS Classes

### Typography

| Class | Description |
|-------|-------------|
| `xText-display` | Display heading — `clamp(3rem, 8vw, 6rem)`, weight 700, tight tracking |
| `xText-headline` | Section headline — `clamp(2rem, 5vw, 3.5rem)`, weight 600 |
| `xText-title` | Card/feature title — `clamp(1.25rem, 3vw, 1.75rem)`, weight 600 |
| `xText-body` | Body text — `1.125rem`, line-height 1.7 |
| `xText-small` | Captions/labels — `0.875rem` |
| `xText-eyebrow` | Uppercase label — `0.75rem`, 0.1em tracking |
| `xText-balance` | Applies `text-wrap: balance` |
| `xText-gradient` | Gradient text using primary color |

### Scroll Animations

| Class | Description |
|-------|-------------|
| `xFadeUp` | Fade in + slide up (24px) on scroll |
| `xFadeIn` | Simple fade in on scroll |
| `xFadeLeft` | Fade in from left (-24px) on scroll |
| `xFadeRight` | Fade in from right (24px) on scroll |
| `xScale` | Scale in (0.95 → 1) on scroll |
| `xFadeUp-stagger` | Stagger children with 100ms delay increments (up to 6 children) |
| `[data-reveal]` | Legacy fade up (40px) on scroll |

### Hover Effects

| Class | Description |
|-------|-------------|
| `xHover-lift` | Lift up 4px + shadow on hover |
| `xHover-grow` | Scale to 1.02 on hover |
| `xHover-glow` | Primary color glow shadow on hover |
| `xHover-glow-neutral` | Neutral glow shadow on hover |
| `xHover-glow-subtle` | Subtle glow shadow on hover |
| `xHover-zoom` | Zoom child `<img>` to 1.08 on hover |

### Glass & Glow

| Class | Description |
|-------|-------------|
| `xGlass` | Glassmorphism — 20px blur, white/70 bg, border, shadow |
| `xGlass-subtle` | Light glassmorphism — 8px blur |
| `xGlass-heavy` | Heavy glassmorphism — 40px blur |
| `xGlow-divider` | Glowing horizontal divider line |
| `xGlow-border` | Glowing gradient border |
| `xGlow-text` | Text glow shadow |

### Overlays

| Class | Description |
|-------|-------------|
| `xOverlay-light` | 30% black overlay |
| `xOverlay-heavy` | 60% black overlay |
| `xOverlay-gradient` | Bottom-to-top gradient overlay |
| `xOverlay-vignette` | Radial vignette overlay |

### Parallax

| Class | Description |
|-------|-------------|
| `xParallax` | JS-driven parallax (use with `data-parallax-speed="0.3"`) |
| `xParallax-bg` | CSS-only parallax (`background-attachment: fixed`) |

All animations respect `prefers-reduced-motion: reduce`.

---

## How It Works

The layer provides a complete marketing website toolkit built on Nuxt UI v4:

1. **Component Registration**: All components in `app/components/X/Mark/` are auto-imported by Nuxt with the `XMark` prefix (e.g., `X/Mark/Hero/index.vue` → `<XMarkHero>`). Branded X Enterprises components live under `X/X/` with the `XX` prefix.

2. **Design System**: `app/assets/css/x-marketing.css` defines CSS custom properties for typography scale, spacing, colors, shadows, and motion. It uses Tailwind CSS v4 with `@theme static` for custom color palettes ("brand" and "deep"). Dark mode overrides use `.dark` selector.

3. **Animations**: A client-side plugin (`marketing.client.ts`) auto-initializes `useScrollReveal()` and `useParallax()` globally on mount. Components that need scroll animations can also call `useScrollReveal()` directly.

4. **Configuration**: `app.config.ts` provides the `xMarketing` namespace with header/footer/blog configuration and Nuxt UI theme overrides. Consumer apps merge their own `app.config.ts` to customize.

5. **Type System**: `app/types/marketing.d.ts` exports interfaces for all data structures (BlogPost, Feature, PricingPlan, Testimonial, etc.) used across components.

---

## Layer Architecture

| Path | Purpose |
|------|---------|
| `nuxt.config.ts` | Registers `@nuxt/ui` + `@nuxt/content`, loads CSS, enables SSR |
| `content.config.ts` | Default Nuxt Content `blog` collection schema |
| `app/app.config.ts` | Default xMarketing config + Nuxt UI theme overrides + type augmentation |
| `app/app.vue` | Default app shell with navbar, newsletter, footer (override in consumer app) |
| `app/assets/css/x-marketing.css` | Full design system: colors, typography, animations, effects |
| `app/components/X/Mark/` | Generic marketing components (auto-imported as `XMark*`) |
| `app/components/X/X/` | X Enterprises branded components (auto-imported as `XX*`) |
| `app/components/X/Footer/` | Legacy footer components |
| `app/components/X/Header/` | Legacy header components |
| `app/composables/` | Composables: useScrollReveal, useParallax, useXBlog |
| `app/plugins/marketing.client.ts` | Client plugin: auto-initializes scroll/parallax globally |
| `app/types/marketing.d.ts` | TypeScript interfaces for all data structures |
| `app/pages/blog/` | Default blog index + slug detail (Nuxt Content, not Builder.io) |

### Blog (Nuxt Content)

**Breaking (Builder.io removed):** default `/blog` pages no longer call the Builder CDN.
Posts come from markdown in the consumer app.

1. Peer-install `@nuxt/content` and `better-sqlite3` (already required by Content v3).
2. Ensure the layer is extended so `content.config.ts` defines the `blog` collection (or copy/adapt it).
3. Add posts under `content/blog/*.md`:

```md
---
title: Getting Started
description: First post on the marketing site.
date: 2025-01-15
author: Tim
image: /blog/cover.jpg
category: Tutorial
tags: [nuxt, marketing]
published: true
readingTime: 5
---

## Hello

Markdown body rendered via `ContentRenderer`.
```

4. `useXBlog().getPosts()` / `getPostByPath()` feed `XMarkBlogList` and `XMarkBlogDetail`.

### Overriding in Consumer Apps

- **nuxt.config.ts**: Consumer config merges with layer config. Add modules, runtime config, etc.
- **app.config.ts**: Deep-merges with layer defaults. Set `xMarketing.header`, `xMarketing.footer`, etc.
- **app.vue**: Override entirely by creating your own `app.vue` in the consumer app.
- **Pages**: Consumer pages take precedence. Override `/blog` by creating `pages/blog/index.vue`.
- **Content**: Own the markdown under `content/blog/`; do not put secrets in frontmatter.

---

## License

UNLICENSED - Proprietary to X Enterprises.
