# LiipImagineBundle Image Optimization — Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Optimize image delivery using LiipImagineBundle — auto-resize + WebP conversion via `<picture>` elements.

**Architecture:** Install `liip/imagine-bundle` v2.17 with 3 filter pairs (PNG+WebP per context: icon 256x256, full 512x512, og 1200x630). Create a Twig macro for `<picture>` boilerplate. Update all templates to use macro. Thumbnails generated on first HTTP request and cached.

**Tech Stack:** PHP 8.4, Symfony 8.0, liip/imagine-bundle ^2.17, GD extension, Twig macros

---

### Task 1: Install LiipImagineBundle

**Files:**
- Modify: `composer.json`
- Create: `config/packages/liip_imagine.yaml`
- Auto-created by Flex: `config/routes/liip_imagine.yaml`

**Step 1: Install the bundle via Composer**

Run:
```bash
composer require liip/imagine-bundle:^2.17
```

Expected: Bundle installed, Flex auto-configures routes in `config/routes/liip_imagine.yaml`.

**Step 2: Verify route registration**

Run:
```bash
php bin/console debug:router | grep imagine
```

Expected: Route `liip_imagine_filter` or similar is registered.

**Step 3: Commit**

```bash
git add composer.json composer.lock symfony.lock config/packages/liip_imagine.yaml config/routes/liip_imagine.yaml
git commit -m "Install liip/imagine-bundle for image optimization"
```

---

### Task 2: Configure filter sets

**Files:**
- Modify: `config/packages/liip_imagine.yaml`

**Step 1: Write the filter configuration**

Replace the contents of `config/packages/liip_imagine.yaml` with:

```yaml
liip_imagine:
    driver: gd

    filter_sets:
        # --- Character icon (grid cards: home, /postacie) ---
        char_icon:
            quality: 85
            filters:
                thumbnail: { size: [256, 256], mode: outbound }

        char_icon_webp:
            format: webp
            quality: 80
            filters:
                thumbnail: { size: [256, 256], mode: outbound }

        # --- Character full (single character page avatar) ---
        char_full:
            quality: 85
            filters:
                thumbnail: { size: [512, 512], mode: outbound }

        char_full_webp:
            format: webp
            quality: 80
            filters:
                thumbnail: { size: [512, 512], mode: outbound }

        # --- Logo (navbar/footer, max 96px height) ---
        logo:
            quality: 85
            filters:
                thumbnail: { size: [null, 96], mode: inset }

        logo_webp:
            format: webp
            quality: 80
            filters:
                thumbnail: { size: [null, 96], mode: inset }

        # --- Open Graph image ---
        og_image:
            quality: 85
            filters:
                thumbnail: { size: [1200, 630], mode: outbound }

        og_image_webp:
            format: webp
            quality: 80
            filters:
                thumbnail: { size: [1200, 630], mode: outbound }
```

**Step 2: Verify config is valid**

Run:
```bash
php bin/console debug:config liip_imagine filter_sets
```

Expected: All 8 filter sets listed (char_icon, char_icon_webp, char_full, char_full_webp, logo, logo_webp, og_image, og_image_webp).

**Step 3: Test a filter resolves correctly**

Run:
```bash
php bin/console liip:imagine:cache:resolve images/characters/scout_ikona.png --filter=char_icon
```

Expected: Cached file created in `public/media/cache/char_icon/images/characters/scout_ikona.png`.

**Step 4: Commit**

```bash
git add config/packages/liip_imagine.yaml
git commit -m "Configure LiipImagine filter sets: icon, full, logo, og"
```

---

### Task 3: Create Twig image macro

**Files:**
- Create: `templates/_macros/image.html.twig`

**Step 1: Create the macro file**

Create `templates/_macros/image.html.twig`:

```twig
{#
  Renders a <picture> element with WebP source + PNG/JPEG fallback via LiipImagine.

  @param string path       — relative image path (e.g. 'images/characters/scout_ikona.png')
  @param string filter     — base filter name (e.g. 'char_icon') — appends '_webp' automatically
  @param string alt        — alt text
  @param string class      — CSS classes for <img> (default '')
  @param string loading    — 'lazy' or 'eager' (default 'lazy')
  @param int|null width    — explicit width attribute (optional)
  @param int|null height   — explicit height attribute (optional)
#}
{% macro picture(path, filter, alt, class, loading, width, height) %}
<picture>
    <source srcset="{{ path | imagine_filter(filter ~ '_webp') }}" type="image/webp">
    <img src="{{ path | imagine_filter(filter) }}"
         alt="{{ alt }}"
         {% if class %}class="{{ class }}"{% endif %}
         loading="{{ loading|default('lazy') }}"
         {% if width %}width="{{ width }}"{% endif %}
         {% if height %}height="{{ height }}"{% endif %}
    >
</picture>
{% endmacro %}
```

**Step 2: Commit**

```bash
git add templates/_macros/image.html.twig
git commit -m "Add Twig macro for <picture> with WebP via LiipImagine"
```

---

### Task 4: Update home page template

**Files:**
- Modify: `templates/home/index.html.twig:101-105` (character icons in "Poznaj druzyne" grid)

**Step 1: Import macro and replace img tags**

At the top of `templates/home/index.html.twig` (after `{% block body %}`), add macro import:

```twig
{% from '_macros/image.html.twig' import picture %}
```

Replace line 104:
```twig
<img src="/images/characters/{{ char.slug }}_ikona.png" alt="{{ char.name }}" class="w-full h-full object-cover rounded-full">
```

With:
```twig
{{ picture('images/characters/' ~ char.slug ~ '_ikona.png', 'char_icon', char.name, 'w-full h-full object-cover rounded-full') }}
```

**Step 2: Test in browser**

Run:
```bash
symfony serve
```

Visit `http://localhost:8000` — scroll to "Poznaj druzyne" section. Inspect the `<picture>` element — should have `<source type="image/webp">` and `<img>` fallback. First load will be slower (thumbnail generation), subsequent loads are cached.

**Step 3: Commit**

```bash
git add templates/home/index.html.twig
git commit -m "Use LiipImagine for character icons on home page"
```

---

### Task 5: Update character listing page

**Files:**
- Modify: `templates/character/index.html.twig:18`

**Step 1: Import macro and replace img tag**

Add after `{% block body %}`:
```twig
{% from '_macros/image.html.twig' import picture %}
```

Replace line 18:
```twig
<img src="/images/characters/{{ slug }}_ikona.png" alt="{{ char.name }}" class="w-full h-full object-cover rounded-full">
```

With:
```twig
{{ picture('images/characters/' ~ slug ~ '_ikona.png', 'char_icon', char.name, 'w-full h-full object-cover rounded-full') }}
```

**Step 2: Test in browser**

Visit `http://localhost:8000/postacie` — verify `<picture>` elements render correctly.

**Step 3: Commit**

```bash
git add templates/character/index.html.twig
git commit -m "Use LiipImagine for character icons on listing page"
```

---

### Task 6: Update single character page

**Files:**
- Modify: `templates/character/show.html.twig:15`

**Step 1: Import macro and replace img tag**

Add after `{% block body %}`:
```twig
{% from '_macros/image.html.twig' import picture %}
```

Replace line 15:
```twig
<img src="/images/characters/{{ character.slug }}_full.png" alt="{{ character.name }}" class="w-full h-full object-cover rounded-full">
```

With:
```twig
{{ picture('images/characters/' ~ character.slug ~ '_full.png', 'char_full', character.name, 'w-full h-full object-cover rounded-full', 'eager') }}
```

Note: `loading='eager'` because this is above-the-fold hero content.

**Step 2: Test in browser**

Visit `http://localhost:8000/postacie/scout` — verify avatar renders as `<picture>`.

**Step 3: Commit**

```bash
git add templates/character/show.html.twig
git commit -m "Use LiipImagine for character avatar on detail page"
```

---

### Task 7: Update base layout (logo)

**Files:**
- Modify: `templates/base.html.twig:39,93`

**Step 1: Import macro at top of body**

The logo appears in navbar (line 39) and footer (line 93). Since `base.html.twig` has no `{% block body %}`, import the macro at file level — add before the `<body>` tag or inside a Twig block. Best approach: add import right after `<body>`:

After line 33 (`<body ...>`), add:
```twig
{% from '_macros/image.html.twig' import picture %}
```

Replace line 39:
```twig
<img src="/images/logo.png" alt="TROPO" class="h-12">
```

With:
```twig
{{ picture('images/logo.png', 'logo', 'TROPO', 'h-12', 'eager') }}
```

Replace line 93:
```twig
<img src="/images/logo.png" alt="TROPO" class="h-10 brightness-0 invert">
```

With:
```twig
{{ picture('images/logo.png', 'logo', 'TROPO', 'h-10 brightness-0 invert') }}
```

**Step 2: Test in browser**

Visit any page — verify navbar and footer logos render as `<picture>` with WebP.

**Step 3: Commit**

```bash
git add templates/base.html.twig
git commit -m "Use LiipImagine for logo in navbar and footer"
```

---

### Task 8: Warm cache for all images

**Files:** None (CLI only)

**Step 1: Warm all filter caches**

Run:
```bash
php bin/console liip:imagine:cache:resolve images/logo.png --filter=logo --filter=logo_webp
php bin/console liip:imagine:cache:resolve images/characters/scout_ikona.png images/characters/blitz_ikona.png images/characters/nova_ikona.png images/characters/echo_ikona.png --filter=char_icon --filter=char_icon_webp
php bin/console liip:imagine:cache:resolve images/characters/scout_full.png images/characters/blitz_full.png images/characters/nova_full.png images/characters/echo_full.png --filter=char_full --filter=char_full_webp
```

Expected: Cached files generated under `public/media/cache/`.

**Step 2: Verify cache directory size vs originals**

Run:
```bash
du -sh public/media/cache/
du -sh public/images/
```

Expected: Cache directory is drastically smaller than originals (likely <2 MB vs ~80 MB).

**Step 3: Add cache dir to .gitignore**

Check if `public/media/` is already gitignored. If not, add it:

```bash
echo "/public/media/" >> .gitignore
```

**Step 4: Commit**

```bash
git add .gitignore
git commit -m "Gitignore LiipImagine cache directory"
```

---

### Task 9: Final smoke test

**Step 1: Clear cache and test full flow**

Run:
```bash
php bin/console cache:clear
rm -rf public/media/cache/
symfony serve
```

**Step 2: Visit all pages with images**

- `http://localhost:8000` — home (logo + 4 character icons)
- `http://localhost:8000/postacie` — listing (4 character icons)
- `http://localhost:8000/postacie/scout` — detail (full avatar)
- `http://localhost:8000/postacie/blitz` — detail
- `http://localhost:8000/postacie/nova` — detail
- `http://localhost:8000/postacie/echo` — detail

**Step 3: Verify in DevTools**

Open Network tab in browser DevTools. Filter by images. Verify:
- WebP is served when browser supports it (check Content-Type in response)
- Images are resized (check dimensions — icons should be 256x256, not original size)
- `loading="lazy"` attribute present on below-fold images

**Step 4: Final commit**

If any fixes were needed during smoke test, commit them now.
