# Tailwind CSS

Expert workflow for implementing layouts, styling, and animations using Tailwind CSS utilities and the project's fluid grid system.

## Core Principles

### Never Use Custom Styles

Avoid Tailwind custom styles (`[--custom-value]` syntax):

```
❌ BAD:  class="w-[200px]"
✅ GOOD: class="span-w-6"

❌ BAD:  class="text-[#FF5733]"
✅ GOOD: class="text-accent"

❌ BAD:  class="p-[1.5rem]"
✅ GOOD: class="p-24"
```

Always use design system utilities from documented classes.

## Width Sizing Workflow

### CRITICAL: Always Convert Pixels to Columns

For any width sizing, convert pixel values to grid spans using the grid system CLI.

**Command**: `bunx px-to-cols` (from `@numbered/tailwind-fluid-layout-system`)

**Usage**:

```bash
bunx px-to-cols <pixels> --columns N --mockup N --gutter N --margin N
```

**Parameters**:

- `pixels` - The pixel value to convert (positional argument)
- `--columns` - Number of grid columns (6 mobile, 24 desktop)
- `--mockup` - Total mockup width in pixels (375 mobile, 1440 desktop)
- `--gutter` - Gutter size between columns in pixels (12 mobile, 24 desktop)
- `--margin` - Outer margins in pixels (12 mobile, 24 desktop)
- `--json` - Output full JSON result with grid configuration

**Grid Parameter Values**: See `docs/grid-system.md` for column counts, gutter, margin, and mockup widths per breakpoint (Mobile, Tablet, Desktop).

**Workflow**:

1. Get pixel measurement from design/Figma
2. Run the script with pixel value and grid parameters
3. Apply returned span class to element
4. Never guess or manually calculate

**Example**:

```bash
# Desktop: 330px element
bunx px-to-cols 330 --columns 24 --mockup 1440 --gutter 24 --margin 24
# Output: span-w-6 (348px, +18px)

# Mobile: 150px element
bunx px-to-cols 150 --columns 6 --mockup 375 --gutter 12 --margin 12
# Output: span-w-3 (175px, +25px)

# Full JSON output
bunx px-to-cols 330 --columns 24 --mockup 1440 --gutter 24 --margin 24 --json
```

Apply the class: `<div class="span-w-6">`

**Available Span Classes**:

- `span-w-{cols}` - Standard grid span
- `span-w-{cols}-wide` - Wide variant (includes partial gutter)
- `span-w-{cols}-wider` - Wider variant (includes full gutter)

### Never Use `w-auto` on Images

Images must always have an explicit `span-w-*` at every breakpoint. Never use `w-auto`.

```
❌ BAD:  <img class="span-w-5 lg:w-auto" width="287" ...>
✅ GOOD: <img class="span-w-5 lg:span-w-5" ...>
```

`w-auto` relies on the image's intrinsic pixel dimensions, which breaks fluid scaling and causes layout measurement failures in components like `ScrollMarquee` (which measures content width to calculate clones — `w-auto` on unloaded lazy images resolves to 0px, causing excessive DOM duplication).

Always convert pixel values to `span-w-*` using `bunx px-to-cols` for both mobile and desktop breakpoints.

### Apply Sizing to the Innermost Sizing Element

Always place `span-w-*` on the element that actually defines the rendered box size, not on a wrapper.

```
❌ BAD:  <picture class="span-w-3-wide"><img ...></picture>
✅ GOOD: <picture><img class="span-w-3-wide" ...></picture>
```

Wrappers like `<picture>`, `<figure>`, or decorative `<div>`s don't constrain their children by default — the `<img>` (or equivalent) needs the class directly.

### Responsive Width Patterns

```liquid
<!-- Full width on mobile, half width on desktop -->
<div class="span-w-12 lg:span-w-6">

<!-- Nested grid spans -->
<div class="span-w-12">
  <div class="span-w-6">Half of parent</div>
</div>
```

## Vertical Spacing

### Direct Pixel Values

For vertical spacing, use px values directly. The system auto-converts to rem.

**Pattern**: `(my|py|pt|pb|mt|mb)-{pxValue}`

### Collapse Symmetric Spacing

When top and bottom (or left and right) values are equal, **always use the shorthand axis utility**:

```
❌ BAD:  pt-60 pb-60 lg:pt-120 lg:pb-120
✅ GOOD: py-60 lg:py-120

❌ BAD:  pl-24 pr-24
✅ GOOD: px-24

❌ BAD:  mt-32 mb-32
✅ GOOD: my-32
```

This applies even when Figma JSON provides separate `pt`/`pb` values — if they are equal, collapse them.

**Examples**:

```liquid
<section class='py-48 lg:py-64'>
	<div class='mt-24 mb-32'>
		<p class='pb-16'>Content</p>
	</div>
</section>
```

**Common Values**:

- Small: `8`, `12`, `16`
- Medium: `24`, `32`
- Large: `48`, `64`
- Extra Large: `80`, `96`

### Spacing Utilities

```liquid
<!-- Gap (flexbox/grid) -->
<div class="flex gap-16 lg:gap-24">

<!-- Space between children -->
<div class="space-y-24">
  <div>Item 1</div>
  <div>Item 2</div>
</div>
```

## CSS Variable Scoping

### When to Use CSS Variables

Use **only when no Tailwind utility exists** (e.g., dynamic Shopify settings).

**Section-Specific Variables**:

```liquid
<div style='--component-bg: {{ section.settings.background_color }}'>
	<div class='bg-[--component-bg]'>
		<!-- Content -->
	</div>
</div>
```

**Guidelines**:

- Scope to component level (never global unless absolutely necessary)
- Use descriptive naming: `--{component}-{property}`
- Minimize usage - prefer Tailwind utilities
- Only for dynamic Shopify settings

**Examples**:

- `--hero-bg`
- `--card-border-color`
- `--section-text-color`

## Layout Patterns

### Flexbox Patterns

```liquid
<!-- Center content -->
<div class="flex items-center justify-center">

<!-- Space between -->
<div class="flex justify-between items-center">

<!-- Responsive direction -->
<div class="flex flex-col lg:flex-row gap-16 lg:gap-24">
```

## Class Organization

### Logical Grouping

Group utilities in order: Layout → Spacing → Typography → Colors → States

```liquid
<div class="flex items-center gap-16 p-24 text-lg font-semibold text-gray-900 hover:text-accent">
```

### Remove Redundant Classes

Don't add classes that only restate browser defaults:

```
❌ BAD:  <div class="flex flex-col gap-4 p-4 bg-white text-black text-base font-normal">
✅ GOOD: <div class="flex flex-col gap-4 p-4 bg-white">
```

## Typography

### Never Add Redundant Line-Height or Letter-Spacing

The `text-{size}` classes already include baked-in `lineHeight` and `letterSpacing` values from the design system. Do not re-declare them:

```
❌ BAD:  class="text-48 leading-[1.2] tracking-[-0.02em]"
✅ GOOD: class="text-48"

❌ BAD:  class="text-14 leading-normal"
✅ GOOD: class="text-14"
```

Only use `leading-*` or `tracking-*` when you need to **override** the fontSize defaults (e.g., a one-off layout where the design explicitly deviates from the type scale).

## Responsive Design

### Mobile-First Approach

Base styles apply to mobile, enhance for larger screens:

```liquid
<div class='py-24 lg:py-48'>
	<!-- 24px padding on mobile, 48px on desktop -->
</div>
```

### Breakpoints

- **Base (no prefix)**: Mobile (0px+)
- **lg:** Desktop (typically 1024px+)

Check `docs/grid-system.md` for exact breakpoint values.

### Never Duplicate DOM for Breakpoints

Use responsive utilities to art-direct a single DOM tree. Never render the same content twice with `hidden`/`lg:hidden` toggles.

```
❌ BAD: duplicated DOM with visibility toggles
<div class="hidden lg:flex">            ← desktop copy
  <p>{text}</p><p>{text}</p>
</div>
<div class="flex flex-col lg:hidden">   ← mobile copy
  <p>{text}</p><p>{text}</p>
</div>

✅ GOOD: single DOM, responsive classes
<div class="flex flex-col lg:flex-row gap-24">
  <p class="span-w-6 lg:span-w-7">{text}</p>
  <p class="span-w-6 lg:span-w-7">{text}</p>
</div>
```

When a feature only applies at one breakpoint (e.g. mobile read-more), add or hide that single element — never clone the surrounding content.

### Preserve Breakpoint-Only Layout Wrappers

If a Figma frame owns layout at any breakpoint, keep it as a DOM owner. Make it transparent with responsive `contents` where that frame disappears; do not flatten its children into a distant ancestor merely to avoid a wrapper or fidelity anchor.

```html
❌ BAD: flatten a desktop layout frame and place every child from the section root
<div class="grid">...</div>

✅ GOOD: preserve its desktop layout ownership and remove only its mobile box
<div class="contents lg:flex lg:flex-col lg:justify-between">
  ...
</div>
```

Exception: `<picture>` with `<source media="...">` for genuinely different image assets is fine — the browser handles it natively.

## CSS Transitions & Animations

**Prefer CSS-only solutions** using transitions and states over JavaScript animations.

### Timing Functions

- **On hover/enter**: `ease-expo-out` for snappy response
- **On mouseout/default**: `ease-quart-inout` for smooth return
- **Duration**: `duration-500` (500ms) as default

### GPU Optimization

- Use `transform-gpu` **only** when transform animations are active (scale, translate, rotate)
- Do not enable by default—adds memory overhead

### Pattern

```html
<!-- Base: smooth return with quart-inout -->
<!-- Hover: snappy response with expo-out, GPU acceleration -->
<button
	class="
  transition-transform duration-500 ease-quart-inout
  hover:scale-105 hover:ease-expo-out hover:transform-gpu
"
>
	Click me
</button>
```

### When to Use Transitions

- Hover states on interactive elements
- Focus states for accessibility
- State changes (open/close, active/inactive)
- Micro-interactions (button press, card lift)

Avoid JavaScript animations when CSS transitions can achieve the same result.

## Quick Reference

**Width**: Run `bunx px-to-cols` → Apply span class
**Height/Spacing**: Direct px values (`py-24`, `mt-48`)
**Colors**: Design system utilities (`text-accent`, `bg-primary`)
**Typography**: Font utilities (`font-moulin`, `text-lg`)
**Responsive**: Mobile-first with `lg:` prefix
**CSS Variables**: Only for dynamic Shopify settings
