diff --git a/.cursor/skills/css-to-theme-content-xml/SKILL.md b/.cursor/skills/css-to-theme-content-xml/SKILL.md
new file mode 100644
index 0000000000..b8baf8d50d
--- /dev/null
+++ b/.cursor/skills/css-to-theme-content-xml/SKILL.md
@@ -0,0 +1,509 @@
+---
+name: css-to-theme-content-xml
+description: Reverse-engineer AEM Adaptive Forms _cq_styleConfig and _cq_themeConfig content.xml files from CSS. Use when the user provides a CSS file (or CSS selectors) and wants to generate the corresponding JCR content.xml for styleConfig or themeConfig, or when working with AEM Core Forms Components theming, styling, or theme-to-XML conversion.
+---
+
+# CSS to _cq_styleConfig / _cq_themeConfig Generator
+
+Generate AEM Adaptive Forms `_cq_styleConfig/.content.xml` and `_cq_themeConfig/.content.xml` from CSS.
+
+## Authoritative References
+
+The actual `_cq_styleConfig` and `_cq_themeConfig` files in this repository are the source of truth. **Always read the relevant config file before generating XML.** See [config-file-index.md](config-file-index.md) for the full index of all 35 config files organized by component and pattern type.
+
+See [component-selectors.md](component-selectors.md) for the complete CSS selector to XML node mapping.
+
+## Super Resource Type Inheritance
+
+Components use `sling:resourceSuperType` to inherit `_cq_styleConfig` from parent components. **When a component does not define its own `_cq_styleConfig`, it inherits from the nearest ancestor that does.**
+
+### Full Inheritance Hierarchy
+
+```
+base/v1/base (has _cq_styleConfig)
+├── textinput/v1/textinput (has _cq_styleConfig — overrides base)
+│ ├── telephoneinput/v1/telephoneinput (has _cq_styleConfig — overrides textinput)
+│ └── emailinput/v1/emailinput (has _cq_styleConfig — overrides textinput)
+├── numberinput/v1/numberinput (has _cq_styleConfig)
+├── datepicker/v1/datepicker (has _cq_styleConfig)
+├── datetime/v1/datetime (has _cq_styleConfig)
+├── dropdown/v1/dropdown (has _cq_styleConfig)
+├── checkbox/v1/checkbox (has _cq_styleConfig)
+│ └── switch/v1/switch (has _cq_styleConfig — overrides checkbox entirely)
+├── checkboxgroup/v1/checkboxgroup (has _cq_styleConfig)
+│ ├── checkboxgroup/v2/checkboxgroup (NO own config — inherits v1)
+│ └── toggleablelink/v1/toggleablelink (NO own config — inherits checkboxgroup)
+├── radiobutton/v1/radiobutton (has _cq_styleConfig)
+│ └── radiobutton/v2/radiobutton (NO own config — inherits v1)
+├── button/v1/button (NO own config — inherits base)
+│ └── button/v2/button (has _cq_styleConfig — overrides base chain)
+│ ├── submit/v2/submit (has _cq_styleConfig)
+│ └── reset/v2/reset (has _cq_styleConfig)
+├── recaptcha/v1/recaptcha (has _cq_styleConfig)
+│ ├── hcaptcha/v1/hcaptcha (has _cq_styleConfig — overrides recaptcha)
+│ └── turnstile/v1/turnstile (NO own config — inherits recaptcha)
+├── fileinput/v1/fileinput (NO own config — inherits base)
+│ └── fileinput/v2 → v3 (has _cq_styleConfig) → v4 (has _cq_styleConfig)
+├── image/v1/image (has _cq_styleConfig)
+├── text/v1/text (has _cq_styleConfig)
+└── scribble/v1/scribble (has _cq_styleConfig)
+
+panelcontainer/v1/panelcontainer (has _cq_styleConfig) [super: responsivegrid]
+├── fragment/v1/fragment (NO own config — inherits panelcontainer)
+└── termsandconditions/v1 (has _cq_styleConfig)
+
+container/v1/container [super: responsivegrid]
+└── container/v2/container (has _cq_themeConfig — aggregator)
+ └── fragmentcontainer/v1 (NO own config — inherits container)
+
+accordion/v1/accordion (has _cq_styleConfig) [super: wcm accordion]
+tabsontop/v1/tabsontop (has _cq_styleConfig) [super: wcm tabs]
+verticaltabs/v1/verticaltabs (has _cq_styleConfig) [super: wcm panelcontainer]
+wizard/v1/wizard [super: wcm panelcontainer]
+└── wizard/v2/wizard (has _cq_styleConfig)
+
+title/v1/title [super: wcm title]
+└── title/v2/title (has _cq_styleConfig)
+```
+
+### Config Resolution for CSS Selectors
+
+When a CSS selector targets a component, resolve the config file using this algorithm:
+
+1. **Extract component name** from CSS selector (e.g., `.cmp-adaptiveform-telephoneinput__widget` → `telephoneinput`)
+2. **Check if the component has its own `_cq_styleConfig`** in the config-file-index
+3. **If yes**: use that config as template
+4. **If no**: walk the `sling:resourceSuperType` chain until finding a component with its own `_cq_styleConfig`
+5. **Use the inherited config** but note that CSS selectors in the output should match the ACTUAL component, not the super type
+
+### Override Patterns
+
+Components that define their own `_cq_styleConfig` while having a super type follow one of these patterns:
+
+#### Pattern 1: Same Structure, Different Selectors
+Components like `telephoneinput` and `emailinput` mirror their super type (`textinput`) node-for-node, but:
+- Replace all CSS selectors with their own prefix (e.g., `.cmp-adaptiveform-telephoneinput__*` instead of `.cmp-adaptiveform-textinput__*`)
+- Replace node names with component-specific names (e.g., `telephoneinputLabel` instead of `textinputLabel`)
+- Replace IDs (e.g., `af2_telephoneinputlabel` instead of `af2_textinputlabel`)
+- Use `sling:hideResource="{Boolean}true"` to hide inherited nodes that don't apply (e.g., telephoneinput hides `textAreaWidgetAndText`)
+
+#### Pattern 2: Completely Different Structure
+Components like `switch` (super: `checkbox`) define an entirely different node hierarchy. Switch has `switchContainer`, `switchOption`, `switchWidget`, `switchHandle`, `switchSlider` — none of which exist in checkbox.
+
+#### Pattern 3: Version Upgrade
+Components like `button/v2` (super: `button/v1`) add or modify nodes from the previous version.
+
+### sling:hideResource
+
+When a component inherits from a super type but needs to hide certain inherited nodes, use:
+```xml
+
+```
+This prevents the inherited node from appearing in the resolved config. Common usage:
+- `telephoneinput` and `emailinput` both hide `textAreaWidgetAndText` inherited from `textinput`
+
+### secondarySelectors and themeConfig Resolution
+
+The `secondarySelectors` attribute on a node (e.g., `secondarySelectors="af2_guideContainer:af2_widgetAndText"`) maps component-specific nodes to generic selectors from the `_cq_themeConfig`. This enables the theme editor to apply styles from the generic widget node to the component-specific widget node.
+
+The `_cq_themeConfig` `target` references always point to the **component that owns the `_cq_styleConfig`**, resolved via `/mnt/override/libs/` path. For components that inherit (like `turnstile` from `recaptcha`), the themeConfig references `recaptcha` directly — there is no separate themeConfig entry for `turnstile`.
+
+---
+
+## Two Config Types
+
+### _cq_styleConfig (per-component)
+
+Defines the structural hierarchy of styleable elements for a single component: which CSS selectors map to which parts, their nesting, and available states. One per component, 32 files total.
+
+### _cq_themeConfig (aggregator)
+
+Lives on container/header/footer. Aggregates multiple component styleConfigs via `target` references. 3 files total. Uses `componentId` and `label` instead of `jcr:title` on root.
+
+## Workflow
+
+### Step 1: Parse CSS
+
+For each CSS rule, extract:
+- **Selector**: e.g., `.cmp-adaptiveform-textinput__widget`
+- **Pseudo-class/data-attribute state**: `:focus`, `:hover`, `[data-cmp-valid='false']`, etc.
+- **Media query breakpoint**: `@media (max-width: 768px)` → phone
+- **All CSS property-value pairs**
+
+### Step 2: Identify Components from Selectors
+
+| Selector Pattern | Component Type | Config File to Read | Super Type |
+|-----------------|----------------|-------------------|------------|
+| `.cmp-adaptiveform-textinput*` | Text Input | `textinput/v1/textinput/_cq_styleConfig` | `base/v1/base` |
+| `.cmp-adaptiveform-numberinput*` | Number Input | `numberinput/v1/numberinput/_cq_styleConfig` | `base/v1/base` |
+| `.cmp-adaptiveform-emailinput*` | Email Input | `emailinput/v1/emailinput/_cq_styleConfig` | `textinput/v1/textinput` |
+| `.cmp-adaptiveform-telephoneinput*` | Telephone Input | `telephoneinput/v1/telephoneinput/_cq_styleConfig` | `textinput/v1/textinput` |
+| `.cmp-adaptiveform-datepicker*` | Date Picker | `datepicker/v1/datepicker/_cq_styleConfig` | `base/v1/base` |
+| `.cmp-adaptiveform-datetime*` | DateTime | `datetime/v1/datetime/_cq_styleConfig` | `base/v1/base` |
+| `.cmp-adaptiveform-dropdown*` | Dropdown | `dropdown/v1/dropdown/_cq_styleConfig` | `base/v1/base` |
+| `.cmp-adaptiveform-button*` | Button | `button/v2/button/_cq_styleConfig` | `button/v1/button` → `base/v1/base` |
+| `.cmp-adaptiveform-checkbox*` | Checkbox | `checkbox/v1/checkbox/_cq_styleConfig` | `base/v1/base` |
+| `.cmp-adaptiveform-checkboxgroup*` | CheckboxGroup | `checkboxgroup/v1/checkboxgroup/_cq_styleConfig` | `base/v1/base` |
+| `.cmp-adaptiveform-radiobutton*` | Radio Button | `radiobutton/v1/radiobutton/_cq_styleConfig` | `base/v1/base` |
+| `.cmp-adaptiveform-switch*` | Switch | `switch/v1/switch/_cq_styleConfig` | `checkbox/v1/checkbox` |
+| `.cmp-adaptiveform-container` | Form Container | `container/v2/container/_cq_themeConfig` | `container/v1/container` |
+| `.cmp-container*` | Panel | `panelcontainer/v1/panelcontainer/_cq_styleConfig` | `responsivegrid` |
+| `.cmp-accordion*` | Accordion | `accordion/v1/accordion/_cq_styleConfig` | `wcm accordion` |
+| `.cmp-tabs*` | Tabs on Top | `tabsontop/v1/tabsontop/_cq_styleConfig` | `wcm tabs` |
+| `.cmp-verticaltabs*` | Vertical Tabs | `verticaltabs/v1/verticaltabs/_cq_styleConfig` | `wcm panelcontainer` |
+| `.cmp-adaptiveform-wizard*` | Wizard | `wizard/v2/wizard/_cq_styleConfig` | `wizard/v1/wizard` |
+| `.cmp-image*` | Image | `image/v1/image/_cq_styleConfig` | `base/v1/base` |
+| `.cmp-title*` | Title | `title/v2/title/_cq_styleConfig` | `title/v1/title` → `wcm title` |
+| `.cmp-adaptiveform-text*` | Text | `text/v1/text/_cq_styleConfig` | `base/v1/base` |
+| `.cmp-adaptiveform-fileinput*` | File Input | `fileinput/v3/fileinput/_cq_styleConfig` | `fileinput/v2` → `v1` → `base` |
+| `.cmp-adaptiveform-termsandconditions*` | T&C | `termsandconditions/v1/termsandconditions/_cq_styleConfig` | `responsivegrid` |
+| `.cmp-adaptiveform-scribble*` | Scribble | `scribble/v1/scribble/_cq_styleConfig` | `base/v1/base` |
+| `.cmp-adaptiveform-recaptcha*` | reCAPTCHA | `recaptcha/v1/recaptcha/_cq_styleConfig` | `base/v1/base` |
+| `.cmp-adaptiveform-hcaptcha*` | hCaptcha | `hcaptcha/v1/hcaptcha/_cq_styleConfig` | `recaptcha/v1/recaptcha` |
+
+**Components that inherit config via super type (no own `_cq_styleConfig`):**
+
+| Component | Inherits Config From | Super Type |
+|-----------|---------------------|------------|
+| `turnstile/v1` | `recaptcha/v1/recaptcha/_cq_styleConfig` | `recaptcha/v1/recaptcha` |
+| `toggleablelink/v1` | `checkboxgroup/v1/checkboxgroup/_cq_styleConfig` | `checkboxgroup/v1/checkboxgroup` |
+| `fragment/v1` | `panelcontainer/v1/panelcontainer/_cq_styleConfig` | `panelcontainer/v1/panelcontainer` |
+| `fragmentcontainer/v1` | `container/v2/container/_cq_themeConfig` | `container/v2/container` |
+| `checkboxgroup/v2` | `checkboxgroup/v1/checkboxgroup/_cq_styleConfig` | `checkboxgroup/v1/checkboxgroup` |
+| `radiobutton/v2` | `radiobutton/v1/radiobutton/_cq_styleConfig` | `radiobutton/v1/radiobutton` |
+| `submit/v1` | `button/v1/button` (→ `base/v1/base/_cq_styleConfig`) | `button/v1/button` |
+| `reset/v1` | `button/v1/button` (→ `base/v1/base/_cq_styleConfig`) | `button/v1/button` |
+
+All paths relative to `ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/`.
+
+### Step 3: Read the Matching Config File
+
+**Read the actual file** from the repository. Use it as the authoritative template for:
+- XML node hierarchy and nesting
+- Node names and attribute patterns
+- Which `id`, `cssSelector`, `longTitle`, `propertySheet`, `secondarySelectors` values to use
+- Which states each element supports
+- Namespace declarations
+
+### Step 4: Generate XML
+
+Copy the structure from the template config file. For each CSS rule:
+
+1. **Find the node** whose `cssSelector` matches the CSS selector
+2. **Embed CSS property values** on that node as a bracket-encoded attribute:
+ - Attribute name: `{breakpoint}_x0023_{state}` where `_x0023_` encodes `#`
+ - Attribute value: `[prop1:val1,prop2:val2,...]`
+3. **Add state nodes** if the CSS has pseudo-classes not already in the template
+4. **Preserve all structural attributes** (`jcr:primaryType`, `id`, `cssSelector`, `propertySheet`, etc.)
+
+### Breakpoint and State Encoding
+
+`_x0023_` is JCR encoding for `#`. Combine breakpoint + state:
+
+| CSS context | Attribute name |
+|------------|---------------|
+| No media query, no pseudo-class | `default_x0023_default` |
+| No media query, `:focus` | `default_x0023_focus` |
+| No media query, `:hover` | `default_x0023_hover` |
+| No media query, `:active` | `default_x0023_active` |
+| No media query, `:disabled` | `default_x0023_disabled` |
+| No media query, `:checked` | `default_x0023_checked` |
+| No media query, `[data-cmp-valid='false']` | `default_x0023_error` |
+| No media query, `[data-cmp-valid='true']` | `default_x0023_success` |
+| `@media` phone breakpoint, no pseudo-class | `phone_x0023_default` |
+| `@media` tablet breakpoint, no pseudo-class | `tablet_x0023_default` |
+
+### Bracket Value Format
+
+```
+[prop1:val1,prop2:val2,prop3:val3]
+```
+
+Rules:
+- No spaces around colons or commas (except within values)
+- Commas inside values escaped with `\,` — e.g. `rgba(0\,0\,0\,0.5)`
+- CSS shorthands must be expanded to individual properties (e.g. `padding: 1px` → `padding-right:1px,padding-bottom:1px,padding-left:1px,padding-top:1px`)
+- Special properties: `cssOverride` (raw CSS), `addonCss` (JSON with `"` and `\,`), `beforePseudoElement`, `afterPseudoElement`
+
+### UI Metadata Companion
+
+For every `{breakpoint}_x0023_{state}` attribute, also generate a `{breakpoint}_x0023_{state}_x0023_ui` companion that stores editor metadata. Common UI properties:
+
+- `marginLock` / `paddingLock` / `borderWidthLock` / `borderRadiusLock`: `link` if all sides equal, `linkOff` if sides differ
+- `marginPopover` / `paddingPopover` / `borderWidthPopover` / `borderRadiusPopover`: shorthand display values, `—` for unchanged sides
+- `backgroundColor`: color value for the color picker
+- Use `Set` as placeholder for unset popover values
+
+### Example
+
+CSS input:
+```css
+.cmp-adaptiveform-textinput__widget {
+ padding-right: 1px;
+ padding-bottom: 1px;
+ padding-left: 1px;
+ padding-top: 1px;
+ margin-right: 2px;
+ margin-bottom: 2px;
+ margin-left: 2px;
+ margin-top: 2px;
+ height: 30px;
+ width: 50%
+}
+```
+
+Find node with `cssSelector=".cmp-adaptiveform-textinput__widget"` in textinput _cq_styleConfig → `textinputWidgetAndText`. Add bracket attributes:
+
+```xml
+
+```
+
+---
+
+## XML Structure Rules
+
+### Namespace Declarations
+
+Always include on `` (copy from the template file):
+
+```xml
+xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
+xmlns:granite="http://www.adobe.com/jcr/granite/1.0"
+xmlns:cq="http://www.day.com/jcr/cq/1.0"
+xmlns:jcr="http://www.jcp.org/jcr/1.0"
+xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
+```
+
+### Required Attributes on Styleable Nodes
+
+| Attribute | Value | Required |
+|-----------|-------|----------|
+| `jcr:primaryType` | `"nt:unstructured"` | Always |
+| `jcr:title` | Short display title | Always |
+| `id` | `"af2_{componentpart}"` (lowercase, no separators) | Always |
+| `cssSelector` | The CSS class selector | Always |
+| `longTitle` | Extended display title | Always |
+| `propertySheet` | `"/mnt/overlay/fd/af/components/stylePropertySheet/common"` | Always |
+| `secondarySelectors` | `"af2_guideContainer:af2_{inheritId}"` | Only for widget/help-icon inheritance |
+
+### Containers
+
+- Children wrapped in ``
+- States wrapped in ``
+
+### ID Convention
+
+`af2_` prefix + lowercase component part, no separators:
+- `af2_textbox`, `af2_textboxwidgetandtext`, `af2_textinputlabel`
+- `af2_button`, `af2_buttontext`, `af2_buttonhelpicon`
+- `af2_panel`, `af2_accordionpanel`, `af2_accordionpanelheader`
+
+---
+
+## State Mapping
+
+| CSS Pattern | State Node Name | `jcr:title` | Outer wrapper selector | Inner element selector |
+|-------------|----------------|-------------|----------------------|----------------------|
+| `:focus` | `focus` | Focus | `.cmp-...{comp}:focus` | `.cmp-...{el}:focus` |
+| `:hover` | `hover` | Hover | `.cmp-...{comp}:hover` | `.cmp-...{el}:hover` |
+| `:disabled` | `disabled` | Disabled | `[data-cmp-enabled='false']` | `:disabled` |
+| `:active` | `active` | Down / Active | `:active` | `:active` |
+| `:required` | `mandatory` | Mandatory | `[data-cmp-required='true']` | `:required` |
+| `:checked` | `checked` | Selected | — | `:checked` |
+| `[data-cmp-valid='false']` | `error` | Error | `[data-cmp-valid='false']` | — |
+| `[data-cmp-valid='true']` | `success` | Success | `[data-cmp-valid='true']` | — |
+
+**Outer wrapper** = the main `.cmp-adaptiveform-{component}` element (uses data-attribute selectors for disabled/mandatory).
+**Inner element** = sub-elements like `__widget`, `__label` (uses pseudo-class selectors).
+
+---
+
+## themeConfig Target References
+
+The container `_cq_themeConfig` aggregates component styleConfigs via `target`:
+
+```xml
+
+```
+
+Target path: `/mnt/override/libs/core/fd/components/form/{component}/{version}/{component}/cq:styleConfig/items/{itemName}`
+
+Read `container/v2/container/_cq_themeConfig/.content.xml` for the complete set of existing target references.
+
+---
+
+## File Placement
+
+```
+ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/
+├── {component}/{version}/{component}/
+│ ├── _cq_styleConfig/
+│ │ └── .content.xml ← per-component style config
+│ └── _cq_themeConfig/
+│ └── .content.xml ← only on container, header, footer
+```
+
+---
+
+## theme.structure Output Format (Single Unified content.xml)
+
+When the user asks for a **single unified content.xml** (e.g., "generate content.xml", "theme.structure format", "one file for all components"), use the `theme.structure` format instead of per-component `_cq_styleConfig` files.
+
+### Structure
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Rules for theme.structure Output
+
+1. **Root**: `jcr:primaryType="sling:Folder"`, child `jcr:content` with `rendition.handler.id="theme.structure"`
+2. **Container**: `af2_guideContainer` with `component="core/fd/components/form/container/v2/container"`
+3. **Node names**: Use the **id** from the matching config node (e.g., `af2_numberinputwidgetandtext`, `af2_form`, `af2_textinputwidgetandtext`) as the XML element name
+4. **Selector → ID mapping**: Resolve each CSS selector to its config node, then use that node's `id` as the child element name under `af2_guideContainer`
+5. **assetLibrary**: Include empty `` at end of `jcr:content`
+
+### Color Conversion for theme.structure
+
+Hex colors in bracket values must be converted to `rgb(r,g,b)` with commas escaped:
+
+| CSS | Bracket value |
+|-----|---------------|
+| `#ec7f7f` | `rgb(236\,127\,127)` |
+| `#7f7fe1` | `rgb(127\,127\,225)` |
+| `#ffffff` | `rgb(255\,255\,255)` |
+
+Conversion: `#RRGGBB` → `rgb(R,G,B)` where R,G,B are decimal (0–255). Commas inside `rgb()` escaped as `\,`.
+
+### UI Metadata for theme.structure (Simplified)
+
+Use popover values only when applicable; omit Lock values if not needed:
+- `backgroundColor`: color value (use rgb format for hex)
+- `borderWidthPopover`, `borderRadiusPopover`: when border props present
+- `paddingPopover`, `marginPopover`: when padding/margin present
+
+---
+
+## Round-Trip Fidelity and Missing Styles
+
+When CSS → content.xml → exported CSS produces different output, styles are lost or transformed. Document these cases and handle them to improve round-trip fidelity.
+
+### Selector Equivalence (Map Alternate Selectors)
+
+The theme editor uses specific selectors; input CSS may use equivalent but different patterns. **Map these to the same state:**
+
+| Input CSS Pattern | Theme Config Selector | State | Notes |
+|-------------------|------------------------|-------|-------|
+| `[data-cmp-enabled]:not([data-cmp-enabled=true]) .cmp-adaptiveform-button__widget` | `.cmp-adaptiveform-button__widget:disabled` | `default_x0023_disabled` | Both target disabled button |
+| `[data-cmp-valid=false]` | `[data-cmp-valid='false']` | `default_x0023_error` | Quote style may differ |
+| `[data-cmp-valid=true]` | `[data-cmp-valid='true']` | `default_x0023_success` | Quote style may differ |
+| `.cmp-container.cmp-adaptiveform-container` | `.cmp-adaptiveform-container` | `af2_form` | Compound selector targets same form element |
+
+### Padding and Margin Shorthand Expansion
+
+**Expand shorthands correctly** — wrong expansion produces invalid or different CSS on export:
+
+| Shorthand | Correct Expansion |
+|-----------|-------------------|
+| `padding: 10px` | `padding-top:10px,padding-right:10px,padding-bottom:10px,padding-left:10px` |
+| `padding: 0 20px` | `padding-top:0,padding-right:20px,padding-bottom:0,padding-left:20px` |
+| `padding: .5rem 1rem` | `padding-top:.5rem,padding-right:1rem,padding-bottom:.5rem,padding-left:1rem` |
+| `padding: 1rem 2rem 3rem` | `padding-top:1rem,padding-right:2rem,padding-bottom:3rem,padding-left:2rem` |
+| `margin: .25rem 0` | `margin-top:.25rem,margin-right:0,margin-bottom:.25rem,margin-left:0` |
+
+**Incorrect:** `padding-top: 0 20px` (invalid — two values in one property). Always expand to four longhand properties.
+
+### CheckboxGroup Selector Mapping
+
+CheckboxGroup has distinct nodes; the theme export uses different selectors than the root:
+
+| Config Node ID | cssSelector | Theme Export Selector |
+|----------------|-------------|------------------------|
+| `af2_checkboxgroupfieldwidgetandtext` | `.cmp-adaptiveform-checkboxgroup` | `.cmp-adaptiveform-checkboxgroup` |
+| `af2_checkboxgroupitemswidget` | `.cmp-adaptiveform-checkboxgroup__widget` | `.cmp-adaptiveform-checkboxgroup__widget` |
+| `af2_checkboxgroupwidgetandtext` | `.cmp-adaptiveform-checkboxgroup__option__widget` | `.cmp-adaptiveform-checkboxgroup__option__widget` |
+
+**Important:** `.cmp-adaptiveform-checkboxgroup__widget` maps to `af2_checkboxgroupitemswidget` (container). `.cmp-adaptiveform-checkboxgroup__option__widget` maps to `af2_checkboxgroupwidgetandtext` (individual option input). Do not conflate `__widget` with `__option__widget`.
+
+### Properties That Cannot Be Stored in Bracket Format
+
+These are typically **dropped** or require `cssOverride`:
+
+| Property Pattern | Reason | Alternative |
+|------------------|--------|-------------|
+| `background: url(...)` | URL values not supported in standard bracket props | Use `cssOverride` with raw CSS, or `addonCss` |
+| `background: url(...) 50%/cover no-repeat,#969696` | Complex value with URL | Split: store `background-color` in bracket; put URL in `cssOverride` |
+| Compound selectors (e.g. `.help-container .questionmark`) | Theme config has flat nodes per element | Apply to the inner element node, or use `cssOverride` |
+| `@media` rules | Breakpoint handling differs | Use `phone_x0023_*` / `tablet_x0023_*` attributes |
+
+### Root Element Styles Without Config Nodes
+
+Some root selectors (e.g. `.cmp-adaptiveform-button` with only `margin`) have no direct config node — the button config starts at `__widget`. Options:
+
+- Merge root-only styles into the primary child node (e.g. `af2_button`) when semantically appropriate
+- Use `cssOverride` on the nearest config node if the theme supports it
+- Document as "not round-trippable" if no suitable node exists
+
+### Border Shorthand Expansion
+
+| Shorthand | Expansion |
+|-----------|-----------|
+| `border: 2px solid #666` | `border-style:solid,border-top-width:2px,border-right-width:2px,border-bottom-width:2px,border-left-width:2px,border-color:rgb(102\,102\,102)` |
+| `border: 0 solid #ca1b15` | `border-style:solid,border-top-width:0,...` (expand all sides) |
+
+### cssOverride for Complex Values
+
+When a property cannot be expressed in bracket format, use `cssOverride`:
+
+```xml
+default_x0023_default="[cssOverride:background\: url(resources/images/question.svg) 50%/cover no-repeat\, #969696]"
+```
+
+Escape colons and commas in the raw CSS value.
+
+---
+
+## Validation Checklist
+
+- [ ] Read the actual matching config file from the repository before generating
+- [ ] If component has no own `_cq_styleConfig`, resolve via `sling:resourceSuperType` chain
+- [ ] If component overrides super type, check for `sling:hideResource` nodes that hide inherited elements
+- [ ] Every node has `jcr:primaryType="nt:unstructured"`
+- [ ] Every styleable node has `id`, `cssSelector`, `longTitle`, `propertySheet`
+- [ ] States use correct selector suffix (pseudo-class for inner, data-attr for outer)
+- [ ] `` wrapper around all child collections
+- [ ] `` wrapper around all state nodes
+- [ ] IDs use `af2_` prefix and are unique within the file
+- [ ] XML well-formed with proper encoding declaration and namespace declarations
+- [ ] Structure matches the pattern from the actual template file
diff --git a/.cursor/skills/css-to-theme-content-xml/component-selectors.md b/.cursor/skills/css-to-theme-content-xml/component-selectors.md
new file mode 100644
index 0000000000..12f4d66afb
--- /dev/null
+++ b/.cursor/skills/css-to-theme-content-xml/component-selectors.md
@@ -0,0 +1,316 @@
+# Component Selector Reference
+
+Complete mapping of CSS selectors to _cq_styleConfig XML nodes for all AEM Core Forms Components.
+
+## Table of Contents
+
+- [Input Fields (Pattern A)](#input-fields-pattern-a)
+- [Button (Pattern B)](#button-pattern-b)
+- [Checkbox / Radio / CheckboxGroup (Pattern C)](#checkbox--radio--checkboxgroup-pattern-c)
+- [Switch](#switch)
+- [Panel Types (Pattern D)](#panel-types-pattern-d)
+- [Simple Components (Pattern E)](#simple-components-pattern-e)
+- [Container / ThemeConfig](#container--themeconfig)
+- [Common Sub-Element Suffixes](#common-sub-element-suffixes)
+
+---
+
+## Input Fields (Pattern A)
+
+Components: `textinput`, `numberinput`, `emailinput`, `telephoneinput`, `datepicker`, `datetime`, `dropdown`
+
+All follow the same structure. Replace `{comp}` with the component name.
+
+**Inheritance notes:**
+- `emailinput` and `telephoneinput` extend `textinput` via `sling:resourceSuperType` — they mirror textinput's structure but use their own CSS selectors and hide `textAreaWidgetAndText` with `sling:hideResource="{Boolean}true"`
+- `numberinput`, `datepicker`, `datetime`, `dropdown` extend `base/v1/base` directly — each defines its own `_cq_styleConfig`
+
+| XML Node | cssSelector | id pattern | secondarySelectors |
+|----------|-------------|------------|-------------------|
+| widgetAndText (root wrapper) | `.cmp-adaptiveform-{comp}` | `af2_{comp}widgetandtext` | `af2_guideContainer:af2_widgetAndText` |
+| labelContainer | `.cmp-adaptiveform-{comp}__label-container` | `af2_{comp}labelcontainer` | — |
+| label | `.cmp-adaptiveform-{comp}__label` | `af2_{comp}label` | — |
+| helpIcon | `.cmp-adaptiveform-{comp}__questionmark` | `af2_{comp}helpicon` | `af2_guideContainer:af2_helpicon` |
+| widget | `.cmp-adaptiveform-{comp}__widget` | `af2_{comp}widgetandtext` | `af2_guideContainer:af2_widgetAndText` |
+| shortDescription | `.cmp-adaptiveform-{comp}__shortdescription` | `af2_{comp}descriptionshort` | — |
+| longDescription | `.cmp-adaptiveform-{comp}__longdescription` | `af2_{comp}descriptionlong` | — |
+| errorMessage | `.cmp-adaptiveform-{comp}__errormessage` | `af2_{comp}errormessage` | — |
+
+### Outer states (on widgetAndText):
+- focus: `.cmp-adaptiveform-{comp}:focus`
+- disabled: `.cmp-adaptiveform-{comp}[data-cmp-enabled='false']`
+- hover: `.cmp-adaptiveform-{comp}:hover`
+- mandatory: `.cmp-adaptiveform-{comp}[data-cmp-required='true']`
+- error: `.cmp-adaptiveform-{comp}[data-cmp-valid='false']`
+- success: `.cmp-adaptiveform-{comp}[data-cmp-valid='true']`
+
+### Inner widget states:
+- focus: `.cmp-adaptiveform-{comp}__widget:focus`
+- hover: `.cmp-adaptiveform-{comp}__widget:hover`
+- disabled: `.cmp-adaptiveform-{comp}__widget:disabled`
+- mandatory: `.cmp-adaptiveform-{comp}__widget:required`
+
+### Specific component names:
+
+| Component | {comp} value | Root id | jcr:title |
+|-----------|-------------|---------|-----------|
+| Text Input | `textinput` | `af2_textbox` | `Base` |
+| Number Input | `numberinput` | `af2_numericbox` | `Base` |
+| Email Input | `emailinput` | `af2_emailinput` | `Base` |
+| Telephone Input | `telephoneinput` | `af2_telephoneinput` | `Base` |
+| Date Picker | `datepicker` | `af2_datepicker` | `Base` |
+| DateTime | `datetime` | `af2_datetime` | `Base` |
+| Dropdown | `dropdown` | `af2_dropdownlist` | `Base` |
+
+### Text Input special: textarea variant
+
+Text Input has an additional `textAreaWidgetAndText` node:
+
+| XML Node | cssSelector | id |
+|----------|-------------|-----|
+| textAreaWidgetAndText | `textarea.cmp-adaptiveform-textinput__widget` | `af2_textareawidgetandtext` |
+
+With states: focus, disabled, hover, mandatory (all on `textarea.cmp-adaptiveform-textinput__widget:{state}`).
+
+---
+
+## Button (Pattern B)
+
+Path: `button/v2/button/_cq_styleConfig`
+
+| XML Node | cssSelector | id |
+|----------|-------------|-----|
+| button (root) | `.cmp-adaptiveform-button__widget` | `af2_button` |
+| buttonText | `.cmp-adaptiveform-button__text` | `af2_buttontext` |
+| buttonDescriptionShort | `.cmp-adaptiveform-button__shortdescription` | `af2_buttondescriptionshort` |
+| buttonDescriptionLong | `.cmp-adaptiveform-button__longdescription` | `af2_buttondescriptionlong` |
+| buttonHelpContainer | `.cmp-adaptiveform-button__help-container` | `af2_buttonhelpcontainer` |
+| buttonHelpIcon | `.cmp-adaptiveform-button__questionmark` | `af2_buttonhelpicon` |
+
+Root states: focus, active (Down), disabled, hover, error, success.
+Button text states: focus, active (Down), disabled, hover.
+
+Submit and Reset buttons reuse the same pattern at:
+- `actions/submit/v2/submit/_cq_styleConfig` (super: `button/v2/button`)
+- `actions/reset/v2/reset/_cq_styleConfig` (super: `button/v2/button`)
+- v1 submit/reset have no own config — they inherit from `button/v1/button` → `base/v1/base`
+
+---
+
+## Checkbox / Radio / CheckboxGroup (Pattern C)
+
+### Checkbox (`checkbox/v1/checkbox`)
+
+| XML Node | cssSelector | id |
+|----------|-------------|-----|
+| widgetAndText | `.cmp-adaptiveform-checkbox` | `af2_checkboxfieldwidgetandtext` |
+| checkBoxItem | `.cmp-adaptiveform-checkbox__widget-container` | `af2_checkboxitem` |
+| checkBoxLabel | `.cmp-adaptiveform-checkbox__label` | `af2_checkboxlabel` |
+| checkBoxWidgetAndText | `.cmp-adaptiveform-checkbox__widget` | `af2_checkboxwidgetandtext` |
+| checkBoxDescriptionShort | `.cmp-adaptiveform-checkbox__shortdescription` | `af2_checkboxdescriptionshort` |
+| checkBoxDescriptionLong | `.cmp-adaptiveform-checkbox__longdescription` | `af2_checkboxdescriptionlong` |
+| checkBoxHelpContainer | `.cmp-adaptiveform-checkbox__help-container` | `af2_checkboxhelpcontainer` |
+| checkBoxHelpIcon | `.cmp-adaptiveform-checkbox__questionmark` | `af2_checkboxhelpicon` |
+| checkBoxErrorMessage | `.cmp-adaptiveform-checkbox__errormessage` | `af2_checkboxerrormessage` |
+
+Widget states include `checked` (`:checked`).
+Outer states: hover, focus, disabled (`[data-cmp-enabled='false']`), error, success.
+
+### CheckboxGroup (`checkboxgroup/v1/checkboxgroup`)
+
+CheckboxGroup has a more complex structure than Checkbox. Key nodes:
+
+| XML Node | cssSelector | id |
+|----------|-------------|-----|
+| widgetAndText (root) | `.cmp-adaptiveform-checkboxgroup` | `af2_checkboxgroupfieldwidgetandtext` |
+| checkBoxGroupLabelContainer | `.cmp-adaptiveform-checkboxgroup__label-container` | `af2_checkboxgrouplabelcontainer` |
+| checkBoxGroupWidget (items container) | `.cmp-adaptiveform-checkboxgroup__widget` | `af2_checkboxgroupitemswidget` |
+| checkBoxGroupItem | `.cmp-adaptiveform-checkboxgroup-item` | `af2_checkboxgroupitem` |
+| checkBoxGroupWidgetAndText (option input) | `.cmp-adaptiveform-checkboxgroup__option__widget` | `af2_checkboxgroupwidgetandtext` |
+
+**Important:** `.cmp-adaptiveform-checkboxgroup__widget` is the container; `.cmp-adaptiveform-checkboxgroup__option__widget` is the individual checkbox input. Theme export outputs to `__option__widget` for `af2_checkboxgroupwidgetandtext`.
+
+- `checkboxgroup/v2` has no own config — inherits from `checkboxgroup/v1`
+- `toggleablelink/v1` also inherits from `checkboxgroup/v1` (no own config)
+
+### RadioButton (`radiobutton/v1/radiobutton`)
+
+Same pattern as Checkbox, replace `checkbox` with `radiobutton` in selectors and ids.
+- `radiobutton/v2` has no own config — inherits from `radiobutton/v1`
+
+---
+
+## Switch
+
+Path: `switch/v1/switch/_cq_styleConfig` (super: `checkbox/v1/checkbox` — but **completely different structure**)
+
+Unique elements beyond the standard pattern:
+
+| XML Node | cssSelector | id |
+|----------|-------------|-----|
+| widgetAndText (root) | `.cmp-adaptiveform-switch` | `af2_switchwidgetandtext` |
+| switchContainer | `.cmp-adaptiveform-switch__container` | `af2_switchwidgetcontainer` |
+| switchOption | `.cmp-adaptiveform-switch__option` | `af2_switchoption` |
+| switchWidget | `.adaptiveform-switch__widget-label` | `af2_switchwidget` |
+| switchHandle | `.cmp-adaptiveform-switch__circle-indicator` | `af2_switchhandle` |
+| switchSlider | `.cmp-adaptiveform-switch__widget-slider` | `af2_switchonlabel` |
+
+Switch handle and slider use adjacent sibling selectors for states:
+- `.cmp-adaptiveform-switch__widget:focus + .cmp-adaptiveform-switch__widget-slider .cmp-adaptiveform-switch__circle-indicator`
+- `.cmp-adaptiveform-switch__widget:checked + .cmp-adaptiveform-switch__widget-slider`
+
+---
+
+## Panel Types (Pattern D)
+
+### Responsive Panel (`panelcontainer/v1/panelcontainer`)
+
+| XML Node | cssSelector | id |
+|----------|-------------|-----|
+| responsivePanel | `.cmp-container` | `af2_panel` |
+| panelLabelContainer | `.cmp-container__label-container` | `af2_panellabelcontainer` |
+| panelLabel | `.cmp-container__label` | `af2_panellabel` |
+| panelHelpIcon | `.cmp-container__questionmark` | `af2_panelhelpicon` |
+| panelDescriptionShort | `.cmp-container__shortdescription` | `af2_paneldescriptionshort` |
+| panelDescriptionLong | `.cmp-container__longdescription` | `af2_paneldescriptionlong` |
+
+### Accordion (`accordion/v1/accordion`)
+
+Root selector: `.cmp-accordion` (id: `af2_accordionpanel`)
+
+Additional accordion-specific items:
+
+| XML Node | cssSelector | id |
+|----------|-------------|-----|
+| accordionPanelItem | `.cmp-accordion__item` | `af2_accordionpanelitem` |
+| accordionHeader | `.cmp-accordion__header` | `af2_accordionpanelheader` |
+| accordionHeaderButton | `.cmp-accordion__button` | `af2_accordionheaderbutton` |
+| accordionHeaderTitle | `.cmp-accordion__title` | `af2_accordionheadertitle` |
+| accordionHeaderIcon | `.cmp-accordion__icon` | `af2_accordionheadericon` |
+| accordionPanelWidget | `.cmp-accordion__panel` | `af2_accordionpanelwidget` |
+
+Panel item states use `:active` and `:hover` (not `:focus`/`:disabled`).
+
+### Tabs On Top (`tabsontop/v1/tabsontop`)
+
+Root selector: `.cmp-tabs` (id: `af2_tabsontoppanel`)
+
+### Vertical Tabs (`verticaltabs/v1/verticaltabs`)
+
+Root selector: `.cmp-verticaltabs` (id: `af2_tabsonleftpanel`)
+
+### Wizard (`wizard/v2/wizard`)
+
+Root selector: `.cmp-adaptiveform-wizard` (id: `af2_wizardpanel`)
+
+---
+
+## Simple Components (Pattern E)
+
+### Image (`image/v1/image`)
+
+```xml
+
+```
+
+### Title (`title/v2/title`)
+
+Root selector: `.cmp-title` or component-specific selector.
+
+### Text (`text/v1/text`)
+
+Root selector: `.cmp-adaptiveform-text` or `.cmp-text`.
+
+---
+
+## Container / ThemeConfig
+
+### Container _cq_themeConfig (`container/v2/container`)
+
+| Selector | id | Notes |
+|----------|-----|-------|
+| `.cmp-adaptiveform-container` | `af2_form` | Primary form selector |
+| `.cmp-container.cmp-adaptiveform-container` | `af2_form` | Compound selector — same element when both classes present |
+
+The container themeConfig aggregates ALL component styleConfigs. It uses:
+- `componentId="af2_guideContainer"` on the root
+- `label="CC Container"` on the root
+- Direct `target` references for delegation
+
+Key groupings:
+
+```
+items
+├── afPage (body selector, id: af2_page)
+├── form (.cmp-adaptiveform-container, id: af2_form)
+├── base (generic .base field, id: af2_base)
+│ └── items: fieldLabel, widgetAndText (with many target overrides)
+├── button (grouping for all buttons via targets)
+├── panel (grouping for all panel types via targets)
+├── Image (target → image styleConfig)
+├── hcaptcha (target → hcaptcha styleConfig)
+└── recaptcha (target → recaptcha styleConfig)
+```
+
+### Header/Footer _cq_themeConfig
+
+These use `componentId` and reference their own `_cq_styleConfig` via `target`:
+
+```xml
+
+
+
+
+
+```
+
+---
+
+## Common Sub-Element Suffixes
+
+These BEM element suffixes appear consistently across components:
+
+| Suffix | Purpose | Typical States |
+|--------|---------|---------------|
+| `__widget` | The actual input/control | focus, hover, disabled, mandatory, checked |
+| `__label` | Field label text | hover, focus |
+| `__label-container` | Wrapper around label + help icon | disabled, hover, focus |
+| `__questionmark` | Help/info icon | focus, hover, disabled |
+| `__shortdescription` | Brief help text | (none) |
+| `__longdescription` | Extended help text | focus, hover |
+| `__errormessage` | Validation error text | (none) |
+| `__help-container` | Wrapper for help content | focus, hover |
+| `__widget-container` | Wrapper for checkbox/radio items | focus, hover, disabled |
+| `__text` | Button label text | focus, active, disabled, hover |
+| `__widget-slider` | Switch slider track | focus, disabled, hover, checked |
+| `__circle-indicator` | Switch handle circle | focus, disabled, hover, checked |
+
+## Inheritance-Only Components (No Own _cq_styleConfig)
+
+These components inherit their `_cq_styleConfig` entirely from their `sling:resourceSuperType` and have no file to read — use the super type's config instead:
+
+| Component | Inherits From | Notes |
+|-----------|--------------|-------|
+| `turnstile/v1` | `recaptcha/v1/recaptcha` | Cloudflare Turnstile — uses reCAPTCHA config |
+| `toggleablelink/v1` | `checkboxgroup/v1/checkboxgroup` | Uses checkbox group config |
+| `fragment/v1` | `panelcontainer/v1/panelcontainer` | Form fragment — uses panel config |
+| `fragmentcontainer/v1` | `container/v2/container` | Fragment container — uses container themeConfig |
+| `checkboxgroup/v2` | `checkboxgroup/v1/checkboxgroup` | Version upgrade — no override needed |
+| `radiobutton/v2` | `radiobutton/v1/radiobutton` | Version upgrade — no override needed |
+
+---
+
+## Base / Generic Field Selectors
+
+The `base/v1/base/_cq_styleConfig` defines fallback selectors for all fields:
+
+| Selector | Purpose |
+|----------|---------|
+| `.cmp-adaptiveform-container .base` | Generic field wrapper |
+| `.cmp-adaptiveform-container input,select,textarea` | All native inputs |
+| `.cmp-adaptiveform-container label` | All labels |
+| `.cmp-adaptiveform-container [class$='__errormessage']` | All error messages |
diff --git a/.cursor/skills/css-to-theme-content-xml/config-file-index.md b/.cursor/skills/css-to-theme-content-xml/config-file-index.md
new file mode 100644
index 0000000000..956d367528
--- /dev/null
+++ b/.cursor/skills/css-to-theme-content-xml/config-file-index.md
@@ -0,0 +1,104 @@
+# Config File Index
+
+All `_cq_styleConfig` and `_cq_themeConfig` content.xml files in this repository, organized by component type. Use these as authoritative templates when generating new config files.
+
+Base path: `ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/`
+
+## _cq_styleConfig Files (32 files)
+
+### Input Fields (Pattern A)
+
+| Component | Path | Pattern | sling:resourceSuperType |
+|-----------|------|---------|------------------------|
+| Text Input | `textinput/v1/textinput/_cq_styleConfig/.content.xml` | A (includes textarea variant) | `base/v1/base` |
+| Number Input | `numberinput/v1/numberinput/_cq_styleConfig/.content.xml` | A | `base/v1/base` |
+| Email Input | `emailinput/v1/emailinput/_cq_styleConfig/.content.xml` | A (hides textarea) | `textinput/v1/textinput` |
+| Telephone Input | `telephoneinput/v1/telephoneinput/_cq_styleConfig/.content.xml` | A (hides textarea) | `textinput/v1/textinput` |
+| Date Picker | `datepicker/v1/datepicker/_cq_styleConfig/.content.xml` | A | `base/v1/base` |
+| DateTime | `datetime/v1/datetime/_cq_styleConfig/.content.xml` | A | `base/v1/base` |
+| Dropdown | `dropdown/v1/dropdown/_cq_styleConfig/.content.xml` | A | `base/v1/base` |
+
+### Buttons (Pattern B)
+
+| Component | Path | Pattern | sling:resourceSuperType |
+|-----------|------|---------|------------------------|
+| Button | `button/v2/button/_cq_styleConfig/.content.xml` | B | `button/v1/button` → `base/v1/base` |
+| Submit | `actions/submit/v2/submit/_cq_styleConfig/.content.xml` | B | `button/v2/button` |
+| Reset | `actions/reset/v2/reset/_cq_styleConfig/.content.xml` | B | `button/v2/button` |
+
+### Checkbox / Radio (Pattern C)
+
+| Component | Path | Pattern | sling:resourceSuperType |
+|-----------|------|---------|------------------------|
+| Checkbox | `checkbox/v1/checkbox/_cq_styleConfig/.content.xml` | C | `base/v1/base` |
+| CheckboxGroup | `checkboxgroup/v1/checkboxgroup/_cq_styleConfig/.content.xml` | C | `base/v1/base` |
+| RadioButton | `radiobutton/v1/radiobutton/_cq_styleConfig/.content.xml` | C | `base/v1/base` |
+
+### Switch
+
+| Component | Path | sling:resourceSuperType |
+|-----------|------|------------------------|
+| Switch | `switch/v1/switch/_cq_styleConfig/.content.xml` | `checkbox/v1/checkbox` (completely different structure) |
+
+### Panels (Pattern D)
+
+| Component | Path | Pattern | sling:resourceSuperType |
+|-----------|------|---------|------------------------|
+| Panel Container | `panelcontainer/v1/panelcontainer/_cq_styleConfig/.content.xml` | D | `responsivegrid` |
+| Accordion | `accordion/v1/accordion/_cq_styleConfig/.content.xml` | D | `wcm accordion` |
+| Tabs on Top | `tabsontop/v1/tabsontop/_cq_styleConfig/.content.xml` | D | `wcm tabs` |
+| Vertical Tabs | `verticaltabs/v1/verticaltabs/_cq_styleConfig/.content.xml` | D | `wcm panelcontainer` |
+| Wizard | `wizard/v2/wizard/_cq_styleConfig/.content.xml` | D | `wizard/v1/wizard` |
+
+### Simple / Other (Pattern E and unique)
+
+| Component | Path | Pattern | sling:resourceSuperType |
+|-----------|------|---------|------------------------|
+| Image | `image/v1/image/_cq_styleConfig/.content.xml` | E | `base/v1/base` |
+| Title | `title/v2/title/_cq_styleConfig/.content.xml` | E-variant | `title/v1/title` → `wcm title` |
+| Text | `text/v1/text/_cq_styleConfig/.content.xml` | E-variant | `base/v1/base` |
+| Base (generic field) | `base/v1/base/_cq_styleConfig/.content.xml` | Shared base | (root — no super) |
+| File Input v3 | `fileinput/v3/fileinput/_cq_styleConfig/.content.xml` | Unique | `fileinput/v2` → `v1` → `base` |
+| File Input v4 | `fileinput/v4/fileinput/_cq_styleConfig/.content.xml` | Unique | `fileinput/v3` |
+| Scribble | `scribble/v1/scribble/_cq_styleConfig/.content.xml` | Unique | `base/v1/base` |
+| Terms & Conditions | `termsandconditions/v1/termsandconditions/_cq_styleConfig/.content.xml` | Unique | `responsivegrid` |
+| hCaptcha | `hcaptcha/v1/hcaptcha/_cq_styleConfig/.content.xml` | Unique | `recaptcha/v1/recaptcha` |
+| reCAPTCHA | `recaptcha/v1/recaptcha/_cq_styleConfig/.content.xml` | Unique | `base/v1/base` |
+| Review | `review/v1/review/_cq_styleConfig/.content.xml` | Unique | — |
+| Footer | `footer/v1/footer/_cq_styleConfig/.content.xml` | Header/Footer | — |
+| Page Header | `pageheader/v1/pageheader/_cq_styleConfig/.content.xml` | Header/Footer | — |
+
+### Components Without Own _cq_styleConfig (inherit from super type)
+
+| Component | Inherits Config From | sling:resourceSuperType |
+|-----------|---------------------|------------------------|
+| `turnstile/v1/turnstile` | `recaptcha/v1/recaptcha/_cq_styleConfig` | `recaptcha/v1/recaptcha` |
+| `toggleablelink/v1/toggleablelink` | `checkboxgroup/v1/checkboxgroup/_cq_styleConfig` | `checkboxgroup/v1/checkboxgroup` |
+| `fragment/v1/fragment` | `panelcontainer/v1/panelcontainer/_cq_styleConfig` | `panelcontainer/v1/panelcontainer` |
+| `fragmentcontainer/v1/fragmentcontainer` | `container/v2/container/_cq_themeConfig` | `container/v2/container` |
+| `checkboxgroup/v2/checkboxgroup` | `checkboxgroup/v1/checkboxgroup/_cq_styleConfig` | `checkboxgroup/v1/checkboxgroup` |
+| `radiobutton/v2/radiobutton` | `radiobutton/v1/radiobutton/_cq_styleConfig` | `radiobutton/v1/radiobutton` |
+| `submit/v1/submit` | `button/v1/button` → `base/v1/base/_cq_styleConfig` | `button/v1/button` |
+| `reset/v1/reset` | `button/v1/button` → `base/v1/base/_cq_styleConfig` | `button/v1/button` |
+
+## _cq_themeConfig Files (3 files)
+
+| Component | Path | Purpose |
+|-----------|------|---------|
+| Container | `container/v2/container/_cq_themeConfig/.content.xml` | Main aggregator — references all component styleConfigs |
+| Footer | `footer/v1/footer/_cq_themeConfig/.content.xml` | Footer aggregator |
+| Page Header | `pageheader/v1/pageheader/_cq_themeConfig/.content.xml` | Header aggregator |
+
+## Recommended Templates
+
+When generating a new config file, use the closest existing component as a template:
+
+| If the new component is... | Use as template |
+|---------------------------|----------------|
+| A text/number/email-like input | `textinput/v1/textinput/_cq_styleConfig/.content.xml` |
+| A button variant | `button/v2/button/_cq_styleConfig/.content.xml` |
+| A checkbox/radio/toggle | `checkbox/v1/checkbox/_cq_styleConfig/.content.xml` |
+| A panel/container | `panelcontainer/v1/panelcontainer/_cq_styleConfig/.content.xml` |
+| An accordion/tabs/wizard | `accordion/v1/accordion/_cq_styleConfig/.content.xml` |
+| A simple display component | `image/v1/image/_cq_styleConfig/.content.xml` |
+| A container themeConfig | `container/v2/container/_cq_themeConfig/.content.xml` |
diff --git a/scripts/css_to_theme_xml.py b/scripts/css_to_theme_xml.py
new file mode 100644
index 0000000000..bd81b44bdb
--- /dev/null
+++ b/scripts/css_to_theme_xml.py
@@ -0,0 +1,332 @@
+#!/usr/bin/env python3
+"""
+Parse CSS and generate theme.structure content.xml per css-to-theme-content-xml skill.
+"""
+import re
+import sys
+from collections import defaultdict
+
+# Selector pattern -> (af2_id, state)
+# State: default, hover, focus, error, success, disabled, active, checked
+SELECTOR_MAP = [
+ # Container / Form
+ (r"^\.cmp-container\.cmp-adaptiveform-container$", ("af2_form", "default")),
+ (r"^\.cmp-adaptiveform-container$", ("af2_form", "default")),
+ # Button
+ (r"^\.cmp-adaptiveform-button$", ("af2_button", "default")),
+ (r"^\.cmp-adaptiveform-button__widget$", ("af2_button", "default")),
+ (r"^\.cmp-adaptiveform-button__widget:hover$", ("af2_button", "hover")),
+ (r"^\[data-cmp-enabled\]:not\(\[data-cmp-enabled=true\]\) \.cmp-adaptiveform-button__widget$", ("af2_button", "disabled")),
+ (r"^\.cmp-adaptiveform-button__text$", ("af2_buttontext", "default")),
+ (r"^\.cmp-adaptiveform-button__longdescription$", ("af2_buttondescriptionlong", "default")),
+ (r"^\.cmp-adaptiveform-button__shortdescription$", ("af2_buttondescriptionshort", "default")),
+ (r"^\.cmp-adaptiveform-button__questionmark$", ("af2_buttonhelpicon", "default")),
+ (r"^\.cmp-adaptiveform-button__help-container$", ("af2_buttonhelpcontainer", "default")),
+ # Input fields - widget
+ (r"^\.cmp-adaptiveform-textinput__widget$", ("af2_textinputwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-numberinput__widget$", ("af2_numberinputwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-emailinput__widget$", ("af2_emailinputwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-telephoneinput__widget$", ("af2_telephoneinputwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-datepicker__widget$", ("af2_datepickerwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-dropdown__widget$", ("af2_dropdownwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-datetime__widget$", ("af2_datetimewidgetandtext", "default")),
+ (r"^textarea\.cmp-adaptiveform-textinput__widget$", ("af2_textareawidgetandtext", "default")),
+ # Input fields - root (widgetAndText)
+ (r"^\.cmp-adaptiveform-textinput$", ("af2_textinputwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-numberinput$", ("af2_numberinputwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-emailinput$", ("af2_emailinputwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-telephoneinput$", ("af2_telephoneinputwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-datepicker$", ("af2_datepickerwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-dropdown$", ("af2_dropdownwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-datetime$", ("af2_datetimewidgetandtext", "default")),
+ # Input fields - data-cmp-valid states
+ (r"^\.cmp-adaptiveform-textinput\[data-cmp-valid=false\]$", ("af2_textinputwidgetandtext", "error")),
+ (r"^\.cmp-adaptiveform-textinput\[data-cmp-valid=true\]$", ("af2_textinputwidgetandtext", "success")),
+ (r"^\.cmp-adaptiveform-numberinput\[data-cmp-valid=false\]$", ("af2_numberinputwidgetandtext", "error")),
+ (r"^\.cmp-adaptiveform-numberinput\[data-cmp-valid=true\]$", ("af2_numberinputwidgetandtext", "success")),
+ (r"^\.cmp-adaptiveform-emailinput\[data-cmp-valid=false\]$", ("af2_emailinputwidgetandtext", "error")),
+ (r"^\.cmp-adaptiveform-emailinput\[data-cmp-valid=true\]$", ("af2_emailinputwidgetandtext", "success")),
+ (r"^\.cmp-adaptiveform-telephoneinput\[data-cmp-valid=false\]$", ("af2_telephoneinputwidgetandtext", "error")),
+ (r"^\.cmp-adaptiveform-telephoneinput\[data-cmp-valid=true\]$", ("af2_telephoneinputwidgetandtext", "success")),
+ (r"^\.cmp-adaptiveform-datepicker\[data-cmp-valid=false\]$", ("af2_datepickerwidgetandtext", "error")),
+ (r"^\.cmp-adaptiveform-datepicker\[data-cmp-valid=true\]$", ("af2_datepickerwidgetandtext", "success")),
+ (r"^\.cmp-adaptiveform-dropdown\[data-cmp-valid=false\]$", ("af2_dropdownwidgetandtext", "error")),
+ (r"^\.cmp-adaptiveform-dropdown\[data-cmp-valid=true\]$", ("af2_dropdownwidgetandtext", "success")),
+ (r"^\.cmp-adaptiveform-datetime\[data-cmp-valid=false\]$", ("af2_datetimewidgetandtext", "error")),
+ (r"^\.cmp-adaptiveform-datetime\[data-cmp-valid=true\]$", ("af2_datetimewidgetandtext", "success")),
+ # Checkbox / Radio / CheckboxGroup
+ (r"^\.cmp-adaptiveform-checkboxgroup$", ("af2_checkboxgroupwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-checkboxgroup\[data-cmp-valid=false\]$", ("af2_checkboxgroupwidgetandtext", "error")),
+ (r"^\.cmp-adaptiveform-checkboxgroup\[data-cmp-valid=true\]$", ("af2_checkboxgroupwidgetandtext", "success")),
+ (r"^\.cmp-adaptiveform-checkbox$", ("af2_checkboxwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-checkbox\[data-cmp-valid=false\]$", ("af2_checkboxwidgetandtext", "error")),
+ (r"^\.cmp-adaptiveform-checkbox\[data-cmp-valid=true\]$", ("af2_checkboxwidgetandtext", "success")),
+ (r"^\.cmp-adaptiveform-checkbox__widget$", ("af2_checkboxwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-radiobutton$", ("af2_radiobuttonwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-radiobutton\[data-cmp-valid=false\]$", ("af2_radiobuttonwidgetandtext", "error")),
+ (r"^\.cmp-adaptiveform-radiobutton\[data-cmp-valid=true\]$", ("af2_radiobuttonwidgetandtext", "success")),
+ (r"^\.cmp-adaptiveform-radiobutton__widget$", ("af2_radiobuttonwidgetandtext", "default")),
+ # Switch
+ (r"^\.cmp-adaptiveform-switch$", ("af2_switchwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-switch__widget-slider$", ("af2_switchonlabel", "default")),
+ (r"^\.cmp-adaptiveform-switch__circle-indicator$", ("af2_switchhandle", "default")),
+ (r"^\.cmp-adaptiveform-switch\[data-cmp-valid=false\]$", ("af2_switchwidgetandtext", "error")),
+ (r"^\.cmp-adaptiveform-switch\[data-cmp-valid=true\]$", ("af2_switchwidgetandtext", "success")),
+ # File input
+ (r"^\.cmp-adaptiveform-fileinput$", ("af2_fileinputwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-fileinput__widget$", ("af2_fileinputwidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-fileinput\[data-cmp-valid=false\]$", ("af2_fileinputwidgetandtext", "error")),
+ (r"^\.cmp-adaptiveform-fileinput\[data-cmp-valid=true\]$", ("af2_fileinputwidgetandtext", "success")),
+ # Panel / Accordion / Tabs / Wizard
+ (r"^\.cmp-container$", ("af2_panel", "default")),
+ (r"^\.cmp-accordion$", ("af2_accordionpanel", "default")),
+ (r"^\.cmp-accordion__panel$", ("af2_accordionpanelwidget", "default")),
+ (r"^\.cmp-tabs$", ("af2_tabsontoppanel", "default")),
+ (r"^\.cmp-verticaltabs$", ("af2_tabsonleftpanel", "default")),
+ (r"^\.cmp-adaptiveform-wizard$", ("af2_wizardpanel", "default")),
+ # Image / Text / Title
+ (r"^\.cmp-image$", ("af2_image", "default")),
+ (r"^\.cmp-image__image$", ("af2_image", "default")),
+ (r"^\.cmp-adaptiveform-text$", ("af2_text", "default")),
+ (r"^\.cmp-title__text$", ("af2_title", "default")),
+ # reCAPTCHA / hCaptcha
+ (r"^\.cmp-adaptiveform-recaptcha$", ("af2_recaptchawidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-recaptcha\[data-cmp-valid=false\]$", ("af2_recaptchawidgetandtext", "error")),
+ (r"^\.cmp-adaptiveform-recaptcha\[data-cmp-valid=true\]$", ("af2_recaptchawidgetandtext", "success")),
+ (r"^\.cmp-adaptiveform-hcaptcha$", ("af2_hcaptchawidgetandtext", "default")),
+ (r"^\.cmp-adaptiveform-hcaptcha\[data-cmp-valid=false\]$", ("af2_hcaptchawidgetandtext", "error")),
+ (r"^\.cmp-adaptiveform-hcaptcha\[data-cmp-valid=true\]$", ("af2_hcaptchawidgetandtext", "success")),
+ # Footer
+ (r"^\.cmp-adaptiveform-footer$", ("af2_footer", "default")),
+ (r"^\.cmp-adaptiveform-footer__text$", ("af2_footertext", "default")),
+]
+
+def hex_to_rgb(hex_val):
+ """Convert #RRGGBB or #RGB to rgb(r,g,b). Commas escaped in normalize_prop."""
+ h = hex_val.lstrip("#")
+ if len(h) == 6:
+ r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
+ elif len(h) == 3:
+ r, g, b = int(h[0]*2, 16), int(h[1]*2, 16), int(h[2]*2, 16)
+ else:
+ return hex_val
+ return f"rgb({r},{g},{b})"
+
+def normalize_prop(prop, val):
+ """Normalize CSS property for bracket format. Convert hex to rgb, escape commas."""
+ prop = prop.strip().lower()
+ val = val.strip()
+ # Skip url(), complex values
+ if "url(" in val or "gradient" in val:
+ return None
+ # Convert hex to rgb (returns rgb(r,g,b) with raw commas)
+ hex_match = re.search(r"#([0-9a-fA-F]{3,8})\b", val)
+ if hex_match:
+ val = re.sub(r"#([0-9a-fA-F]{3,8})\b", lambda m: hex_to_rgb(m.group(0)), val)
+ # Escape commas in values for JCR bracket format
+ val = val.replace(",", "\\,")
+ return f"{prop}:{val}"
+
+def parse_css(content):
+ """Parse CSS into (selector, props) list. Skip @media for now."""
+ rules = []
+ # Remove @media blocks for simplicity
+ content = re.sub(r"@media[^{]+\{[^}]*\}", "", content, flags=re.DOTALL)
+ # Match selector { ... }
+ pattern = re.compile(r"([^{]+)\{([^}]*)\}", re.DOTALL)
+ for m in pattern.finditer(content):
+ selector = m.group(1).strip().strip(",").strip()
+ # Take first selector if comma-separated
+ if "," in selector:
+ for s in selector.split(","):
+ s = s.strip()
+ if s and s.startswith(".cmp-"):
+ rules.append((s, m.group(2)))
+ else:
+ if selector.startswith(".cmp-") or "cmp-adaptiveform" in selector or "cmp-container" in selector:
+ rules.append((selector, m.group(2)))
+ return rules
+
+def resolve_selector(sel):
+ """Resolve selector to (af2_id, state)."""
+ sel = sel.strip()
+ for pattern, (af2_id, state) in SELECTOR_MAP:
+ if re.match(pattern, sel):
+ return (af2_id, state)
+ return None
+
+def parse_declarations(block):
+ """Parse CSS declarations into list of (prop, val)."""
+ props = []
+ for part in block.split(";"):
+ part = part.strip()
+ if ":" in part:
+ idx = part.index(":")
+ prop, val = part[:idx], part[idx+1:].strip()
+ if prop and val:
+ props.append((prop, val))
+ return props
+
+def build_bracket(props):
+ """Build bracket value string from props."""
+ parts = []
+ for prop, val in props:
+ n = normalize_prop(prop, val)
+ if n:
+ parts.append(n)
+ if not parts:
+ return None
+ return "[" + ",".join(parts) + "]"
+
+def escape_commas(val):
+ """Escape commas for bracket format: , -> \\,"""
+ return val.replace(",", "\\,")
+
+def extract_ui_metadata(props):
+ """Extract UI metadata (popover, backgroundColor) from props."""
+ ui = []
+ has_border = any("border" in p for p, _ in props)
+ has_border_radius = any("radius" in p for p, _ in props)
+ has_padding = any("padding" in p for p, _ in props)
+ has_margin = any("margin" in p for p, _ in props)
+ bg = None
+ for p, v in props:
+ if "background" in p and "color" in p.lower() and "url" not in v:
+ bg = v.strip()
+ hex_m = re.search(r"#([0-9a-fA-F]{3,8})\b", bg)
+ if hex_m:
+ bg = escape_commas(hex_to_rgb(hex_m.group(0)))
+ if bg:
+ ui.append(f"backgroundColor:{bg}")
+ if has_border:
+ # Find border-width
+ bw = "1px"
+ for p, v in props:
+ if "border-width" in p or (p == "border" and "solid" in v):
+ if "border-width" in p:
+ bw = v.strip()
+ break
+ ui.append(f"borderWidthPopover:{bw}")
+ if has_border_radius:
+ br = "0"
+ for p, v in props:
+ if "radius" in p:
+ br = v.strip()
+ break
+ ui.append(f"borderRadiusPopover:{br}")
+ if has_padding:
+ pad = "0"
+ for p, v in props:
+ if "padding" in p:
+ pad = v.strip()
+ break
+ ui.append(f"paddingPopover:{pad}")
+ if has_margin:
+ mar = "0"
+ for p, v in props:
+ if "margin" in p:
+ mar = v.strip()
+ break
+ ui.append(f"marginPopover:{mar}")
+ if not ui:
+ return None
+ return "[" + ",".join(ui) + "]"
+
+def expand_shorthand(props):
+ """Expand border, padding, margin shorthand to longhand."""
+ result = []
+ for prop, val in props:
+ p = prop.strip().lower()
+ v = val.strip()
+ if p == "border" and "solid" in v:
+ parts = v.split()
+ width = "1px"
+ color = "#000"
+ for x in parts:
+ if x.endswith("px") or x.endswith("rem"):
+ width = x
+ elif x.startswith("#") or x in ("solid", "none"):
+ if x.startswith("#"):
+ color = x
+ result.extend([
+ ("border-style", "solid"),
+ ("border-top-width", width),
+ ("border-right-width", width),
+ ("border-bottom-width", width),
+ ("border-left-width", width),
+ ("border-color", color),
+ ])
+ elif p == "padding" and not any(x in p for x in ["top", "right", "bottom", "left"]):
+ result.extend([
+ ("padding-top", v),
+ ("padding-right", v),
+ ("padding-bottom", v),
+ ("padding-left", v),
+ ])
+ elif p == "margin" and not any(x in p for x in ["top", "right", "bottom", "left"]):
+ result.extend([
+ ("margin-top", v),
+ ("margin-right", v),
+ ("margin-bottom", v),
+ ("margin-left", v),
+ ])
+ else:
+ result.append((prop, val))
+ return result
+
+def main():
+ css_path = sys.argv[1] if len(sys.argv) > 1 else "a.css"
+ with open(css_path, "r") as f:
+ css = f.read()
+
+ nodes = defaultdict(dict) # af2_id -> { state -> (bracket, ui) }
+
+ for selector, block in parse_css(css):
+ resolved = resolve_selector(selector)
+ if not resolved:
+ continue
+ af2_id, state = resolved
+ props = parse_declarations(block)
+ if not props:
+ continue
+ props = expand_shorthand(props)
+ bracket = build_bracket(props)
+ if not bracket:
+ continue
+ ui = extract_ui_metadata(props)
+ attr = f"default_x0023_{state}"
+ nodes[af2_id][attr] = (bracket, ui)
+
+ # Emit XML
+ print('')
+ print('')
+ print(' ')
+ print(' ')
+ print(' ')
+
+ # Sort nodes for consistent output
+ for af2_id in sorted(nodes.keys()):
+ attrs = nodes[af2_id]
+ parts = [f' <{af2_id}',
+ ' jcr:primaryType="nt:unstructured"']
+ for attr in sorted(attrs.keys()):
+ bracket, ui = attrs[attr]
+ parts.append(f' {attr}="{bracket}"')
+ if ui:
+ parts.append(f' {attr}_x0023_ui="{ui}"')
+ parts.append(' />')
+ print("\n".join(parts))
+
+ print(' ')
+ print(' ')
+ print(' ')
+ print(' ')
+ print('')
+
+if __name__ == "__main__":
+ main()