# Cynosure Design System — AGENTS.md

AI coding tool context for the Cynosure Design System. Read this file before
creating, modifying, or reviewing any component in this project.

---

## Project structure

```
cynosure-handoff/
├── src/handoff/                  # Handoff app root
│   ├── blocks/{name}/            # Block components (one folder per block)
│   │   ├── {name}.js             # Handoff config + properties schema + previews
│   │   ├── template.hbs          # Handlebars template
│   │   ├── style.scss            # Component-scoped SCSS
│   │   └── script.js             # JavaScript (imports shared utilities; JSDoc annotated)
│   ├── components/{name}/        # Element-level components (buttons, inputs, etc.)
│   │   ├── {name}.js             # Handoff config
│   │   ├── template.hbs          # Handlebars template
│   │   ├── style.scss            # Component-scoped SCSS
│   │   └── script.js             # JavaScript (if needed)
│   ├── js/
│   │   ├── main.js               # Vite entry point — imports all shared utilities
│   │   ├── components/           # Shared utility modules (one concern each)
│   │   └── utilities/            # Low-level utility helpers
│   ├── sass/
│   │   └── main.scss             # SCSS entry point
│   ├── pages/                    # Markdown documentation pages
│   │   ├── guidelines/           # Developer guidelines
│   │   └── foundations/          # Brand foundations (colors, typography, etc.)
│   ├── public/                   # Static assets served by Handoff
│   │   ├── api/component/        # Built JS + CSS bundle output
│   │   └── llms.txt              # LLM context file
│   ├── exported/                 # Figma token exports
│   │   └── 3GcQn3eA8Kg9kprYXBXksv/tokens/css/  # Token CSS files
│   ├── theme.css                 # Global brand theme (fonts, heading scale)
│   ├── handoff.config.js         # Handoff + Vite build configuration
│   └── AGENTS.md                 # This file
└── examples/
    └── handoff-wordpress/        # WordPress Gutenberg block compiler
```

---

## Component anatomy

Every block component has exactly four files:

```
{name}.js          — Handoff configuration, property schema, previews
template.hbs       — Handlebars template
style.scss         — Component-scoped SCSS
script.js          — JavaScript (annotated with @shared or custom logic)
```

### {name}.js — Handoff configuration

```js
export default {
  id:          'hero-basic',
  title:       'Hero Basic',
  description: 'Standard page hero with headline, body copy, and CTA.',
  figma:       'https://www.figma.com/design/3GcQn3eA8Kg9kprYXBXksv/...',
  type:        'block',
  group:       'Heroes',
  categories:  ['heroes'],
  tags:        ['hero', 'banner'],
  should_do:       ['Use as the first block on interior pages'],
  should_not_do:   ['Use more than one hero per page'],
  properties: {
    headline: {
      title:       'Headline',
      description: 'Page title displayed in the hero.',
      type:        'text',
      default:     'Bringing Intelligent Beauty to Life',
      rules: { required: true, content: { min: 1, max: 200 } },
    },
    body: {
      title:   'Body',
      type:    'richtext',
    },
    cta: {
      title:   'Call to Action',
      type:    'link',
    },
    image: {
      title:   'Background Image',
      type:    'image',
    },
  },
  previews: {
    generic: {
      title:      'Hero Basic',
      description:'Generic preview.',
      properties: {
        headline: 'Bringing Intelligent Beauty to Life',
        body:     'Cynosure delivers innovative laser and energy-based treatments.',
        cta:      { href: '#', label: 'Learn More' },
        image:    { url: 'https://placehold.co/1440x600', alt: '' },
      },
    },
  },
};
```

---

## Handlebars template conventions

```handlebars
{{!-- template.hbs --}}
<section class="c-hero-basic" data-component="hero-basic">
  <div class="c-hero-basic__media">
    {{#if properties.image}}
    <img src="{{properties.image.url}}" alt="{{properties.image.alt}}" class="c-hero-basic__image">
    {{/if}}
  </div>

  <div class="container">
    <div class="c-hero-basic__content">
      {{#if properties.headline}}
      <h1 class="c-hero-basic__headline">{{properties.headline}}</h1>
      {{/if}}

      {{#if properties.body}}
      <div class="c-hero-basic__body">{{{properties.body}}}</div>
      {{/if}}

      {{#if properties.cta}}
      <a href="{{properties.cta.href}}" class="btn btn-primary c-hero-basic__cta">
        {{properties.cta.label}}
      </a>
      {{/if}}
    </div>
  </div>
</section>
```

**Template rules:**
- Use Bootstrap 5 utility classes (d-flex, gap-3, fw-semibold, text-muted, py-5, etc.)
- Use the `c-` BEM prefix for all component-specific class names
- Minimise custom SCSS — Bootstrap utilities should handle layout and spacing
- Use `class="js-*"` not `id="*"` on elements that JavaScript selects
- Wrap richtext output with triple braces `{{{properties.body}}}` to allow HTML rendering
- Property references: `{{properties.field_name}}` for text, `{{{properties.body}}}` for richtext

---

## JavaScript conventions

### Two-layer architecture

**Layer 1 — Shared bundle** (`js/main.js`): imports all shared utilities. Built once by Vite.

**Layer 2 — Per-block scripts** (`blocks/{name}/script.js`): imports the Layer 1 utilities each block needs. Vite deduplicates at build time — each utility appears once in the final bundle.

### The `@shared` annotation (required on all delegating scripts)

```js
/**
 * Accordion Simple Block
 *
 * Expandable content panels using jQuery slide animations.
 *
 * @shared accordion
 *   Trigger: .c-accordion__toggle
 *   Behavior: Expands clicked panel; collapses siblings
 *   Library: jQuery (slideToggle / slideUp)
 *
 * @see /guidelines/javascript/shared-utilities
 */
import '../../js/components/accordion.js';
```

### Multi-instance pattern (required for all custom interactive components)

```js
/**
 * My Custom Block
 *
 * Description of what this block does.
 *
 * @requires bootstrap  Bootstrap 5 Collapse
 * @instance  Multiple [data-component="my-block"] blocks per page.
 *
 * @see /guidelines/javascript/component-scripts
 */
document.addEventListener('DOMContentLoaded', () => {
  document.querySelectorAll('[data-component="my-block"]').forEach(block => {
    const trigger = block.querySelector('.js-trigger');
    const panel   = block.querySelector('.js-panel');

    if (!trigger || !panel) return;

    trigger.addEventListener('click', () => {
      const isOpen = panel.classList.contains('is-open');
      panel.classList.toggle('is-open', !isOpen);
      trigger.setAttribute('aria-expanded', String(!isOpen));
    });
  });
});
```

**JS rules:**
- Use `const` and `let` — never `var` in new code
- Prefer vanilla `document.querySelector` over jQuery `$()` for new scripts
- Never use `document.getElementById` — scope all queries to the `block` element
- Use `class="js-*"` selectors for JavaScript hooks — not `id="*"`
- Remove all `console.log` before committing
- Always include a JSDoc header with `@shared` or `@requires` and `@see` links

---

## SCSS conventions

```scss
/* style.scss */
/* Scope all rules with the c-{component-name} BEM prefix */

.c-hero-basic {
  position: relative;
  overflow: hidden;
}

.c-hero-basic__media {
  /* Use CSS custom properties for brand tokens */
  background: var(--color-gradient-hero-dark);
}

.c-hero-basic__headline {
  color: var(--color-text-hard-invert);
  font-family: var(--font-family-juana);
}

.c-hero-basic--dark {
  /* Modifier — background variant */
  background-color: var(--color-neutral-gray-900);
}
```

**SCSS rules:**
- Do NOT `@import 'main.scss'` — Handoff injects it automatically
- Use `var(--color-*)` not hardcoded hex
- Class names: `.c-{block-name}` (block), `.c-{block-name}__element` (element), `.c-{block-name}--modifier` (modifier)
- Prefer Bootstrap utilities; write custom SCSS only for things Bootstrap cannot express
- Never use `!important` unless overriding a vendor library (Slick, Select2, Magnific Popup)
- Path aliases: `@public` → `public/`, `@export` → `exported/3GcQn3eA8Kg9kprYXBXksv/`

---

## Design tokens

Tokens are exported from Figma and compiled to CSS custom properties. Token files are at `/api/component/tokens/css/`.

### Key color tokens

```css
/* Brand */
--color-primary-orange:         #ff4713
--color-primary-orange-light:   #ff6c42
--color-primary-orange-dark:    #cc390f
--color-secondary-purple:       #6f2eff
--color-secondary-purple-light: #8c58ff
--color-tertiary-pink:          #db3381
--color-tertiary-plumb:         #361f50

/* Neutrals */
--color-neutral-gray-100:  #f4f4f4
--color-neutral-gray-300:  #d0d2d3
--color-neutral-gray-600:  #696969
--color-neutral-gray-900:  #231f20
--color-neutral-white:     #ffffff
--color-neutral-black:     #000000

/* Text */
--color-text-hard:         #000000
--color-text-base:         rgba(0, 0, 0, .8)
--color-text-soft:         #555555
--color-text-muted:        rgba(85, 85, 85, .8)
--color-text-hard-invert:  #ffffff
--color-text-base-invert:  rgba(255, 255, 255, .85)
--color-text-muted-invert: rgba(255, 255, 255, .45)

/* Borders */
--color-border-base:  rgba(105, 105, 105, .15)
--color-border-dark:  rgba(105, 105, 105, .3)

/* Gradients */
--color-gradient-primary:       linear-gradient(90deg, #ff4713 0%, #dc001b 100%)
--color-gradient-energy-blends: linear-gradient(90deg, #db3381 0%, #361f50 100%)
--color-gradient-hero-dark:     linear-gradient(90deg, #434a50 0%, #231f20 100%)

/* Status */
--color-extra-success: #49c781
--color-extra-warning: #e6e448
--color-extra-danger:  #f25454
--color-extra-info:    #c0e1f0
```

### Key typography tokens

```css
--font-family-juana:                  'Juana'
--font-family-open-sans:              'Open Sans'
--typography-heading-1-font-size:     48px
--typography-heading-2-font-size:     36px
--typography-heading-3-font-size:     28px
--typography-heading-1-font-weight:   400
--typography-heading-1-line-height:   1.4
```

### Key effect tokens

```css
--effect-shadow-base-300: 0px 1px 2px rgba(0, 0, 0, .15)
--effect-shadow-base-500: 0px 2px 8px rgba(0, 0, 0, .1)
--effect-shadow-base-700: 0px 4px 4px rgba(0, 0, 0, .25)
```

---

## Figma MCP workflow

The Figma Desktop MCP is available in Cursor. Use `get_design_context` to pull design
reference and screenshots from the Cynosure Figma file.

**Cynosure Figma file key:** `3GcQn3eA8Kg9kprYXBXksv`

Every component's `.js` config contains the Figma URL. Extract `fileKey` and `nodeId`:

```
"figma": "https://www.figma.com/design/3GcQn3eA8Kg9kprYXBXksv/...?node-id=301-598"
                                         ↑ fileKey                            ↑ nodeId
```

Convert `node-id` dashes to colons for the MCP: `301-598` → `301:598`.

### Adaptation rules

`get_design_context` returns React + Tailwind. Translate to Cynosure conventions:

| Returns | Use instead |
|---------|-------------|
| React/JSX | Handlebars `.hbs` |
| `className="..."` | `class="..."` |
| Tailwind classes | Bootstrap 5 utilities |
| Hardcoded hex | `var(--color-*)` tokens |
| Inline `style={{...}}` | `style.scss` with `c-` prefix |
| React state | `script.js` multi-instance pattern |
| Component props | Handoff `properties` schema |

---

## WordPress integration

The `handoff-wordpress` CLI compiles Handoff components into WordPress Gutenberg blocks.

```bash
# Install
cd examples/handoff-wordpress && npm install && npm run build

# Generate blocks from Handoff API
handoff-wordpress fetch

# Development watch
handoff-wordpress dev
```

Config file: `handoff-wp.config.json` (see `handoff-wp.config.example.json`)

Property type mapping: `text` → string, `image` → MediaUpload, `array` → repeater/query, `boolean` → toggle, `select` → SelectControl.

---

## Build commands

```bash
# Development
npm run start               # Vite dev server with live reload (port 4000)

# Production
npm run build:components    # Build component JS/CSS bundle
npm run build:app           # Build the documentation site (skip components)

# Tokens
npm run fetch               # Pull latest design tokens from Figma via Handoff API

# Validation
npm run validate:components # Validate all component configs

# Bundling
npm run bundle              # Build integration styles
```

---

## Key files

| File | Purpose |
|------|---------|
| `src/handoff/handoff.config.js` | Vite build config, path aliases, validateComponent hook |
| `src/handoff/sass/main.scss` | SCSS entry — import order matters |
| `src/handoff/js/main.js` | JS entry — imports all shared utilities |
| `src/handoff/theme.css` | Global brand styles (Juana font, Open Sans, heading scale) |
| `src/handoff/public/api/component/main.js` | Built JS bundle (do not edit directly) |
| `src/handoff/exported/.../tokens/css/` | Compiled Figma token CSS files |
| `examples/handoff-wordpress/` | WordPress block compiler |

---

## Common mistakes to avoid

- Hardcoding hex colors — use `var(--color-*)` tokens
- Omitting the `c-` prefix on component class names
- Writing Tailwind classes — use Bootstrap 5 utilities
- Writing React/JSX — use Handlebars `.hbs` templates
- Querying `document.getElementById` — use `block.querySelector('.js-*')`
- Initializing a component once globally without `.forEach(block => ...)`
- Leaving a bare import in `script.js` without a JSDoc `@shared` annotation
- Adding `@import 'main.scss'` to component `style.scss`
- Leaving `console.log` statements in committed code
- Using `id="..."` on JavaScript hook elements — use `class="js-*"`
