Feature: Variants and Media screens match the merged design: visual setups, and the variant form as a bottom sheet on mobile As the maintainer of RenewCMS I want the visual check to cover every state of the image variants screens, and the variant form to follow the overlay pattern So that the SPA matches the merged design on desktop and mobile, in light and dark Background: Given the spec of record is docs/design/screens/image-variants.md, docs/design/screens/media.md, docs/design/screens/media-picker.md, docs/design/states.md, docs/design/routes.md and the overlay pattern of docs/design/components.md And this slice is SPA only: no Go file, route or API contract changes And the behaviours of features/image_variants_media_screens.feature, features/image_variants_screens.feature and features/image_variants_editor_picker.feature stay as they are except where this file says so And the merged board is "RenewCMS Screens Back-office.dc.html" and web/visual/check.ts already points to it, with state names in the form "V1a · Loaded" And every new English string has a French entry in web/src/i18n/messages.ts, the page uses Nuxt UI components and design tokens only, and no comment is added to any file And the session is the owner "Marta Alves" unless a scenario says otherwise Rule: Every state of the six groups has a visual setup that passes Scenario Outline: A group of the merged board is fully covered Given the group "" of the merged board lists the states "" When "task visual:check -- image-variants" runs Then each of those states has a setup in web/visual/states.ts keyed by its exact board name And each capture prints PASS at desktop and mobile, in light and dark, within 10 % And no /v1 call is left unmocked and no size mismatch is reported Examples: | group | states | | Variants list | V1a to V1l | | Add edit variant | V2a to V2k | | Media library | V3a to V3f | | Image detail | V4a to V4h | | Cache panel | V5a to V5j | | Editor picker | V6a to V6e | Scenario: Setups reuse the board's own sample data Then each setup mocks /v1 with the titles, names, sizes, counts and variant rows drawn in the board frame Scenario: Clock-sensitive states are skipped, not tuned Given a state whose rendering depends on the current date or time Then its setup carries skip: 'clock-sensitive', the check prints SKIP and no Vitest spec freezes the system date Scenario: No visual exception is added without the user's approval Given a capture cannot get within 10 % Then the setup or the SPA is fixed And no "exception" entry is added unless the user approved it explicitly (approvedBy) Scenario: The gate is hard When any capture exceeds 10 %, a state has no setup, or a /v1 call is unmocked Then "task visual:check -- image-variants" exits non-zero Rule: The Media library V3 states render the existing variants filter and total-size sort The filter, the sort and their request parameters already exist. Media code changes only if a V3 capture fails the check. Scenario Outline: The V3 states render the filter and the sort Given the 12-image fixture of features/image_variants_media_screens.feature When the setup of "" is loaded on /media Then the SPA shows "" And GET /v1/media/ carries "" Examples: | state | visible | request | | V3a · Grid with badges | "N variants" badges on cards with cached files, none on cards without, "All" selected | no variants parameter | | V3b · Sort menu | sort menu open, "Largest total size (original + variants)" last | n/a | | V3c · Filter: With variants, sorted by total size | "With variants" selected, meta lines "total · original + variants" | variants=with&sort=total_size | | V3d · Filter: Without variants | "Without" selected, no variants badge | variants=without | | V3e · Filter: nothing to show | "With variants" selected, empty cache, "No images with variants" | variants=with | Scenario: Existing filter and sort behaviour is unchanged Then the URL, request and meta-line behaviour of "The variants filter" and "Sorting by total size" in features/image_variants_media_screens.feature still holds Rule: The variant form follows the overlay pattern Scenario: Add variant is a right slide-over on desktop Given the viewport is 640 px wide or more When the admin activates "Add variant" Then the form opens as a slide-over 960 px wide at the right over the scrim, full height, with the list behind it And the header has the title and a close button (x icon, accessible name "Close") And the form is on the left and the live preview on the right Scenario: Add variant is a tall bottom sheet on mobile Given the viewport is below 640 px When the admin activates "Add variant" Then the form opens as a bottom sheet flush to the bottom, full width, 92 % of the viewport height And a grabber (36 × 4, --ui-border-accented) is drawn at the top, with a close x button (44 px) in the header And it is not a full page: the list stays visible above the sheet behind the scrim And the Preview section is stacked under the form and the body scrolls And the footer is sticky with "Cancel" and the primary action sharing the width, 44 px high Scenario: Edit uses the same container When the admin chooses "Edit" on the row "hero" Then the form opens in the same container as Add variant at the current viewport and the title is "Edit variant: hero" Scenario Outline: Dismissing the mobile sheet Given the viewport is below 640 px When the user Then Examples: | gesture | result | | closes a pristine form with x or Cancel | the sheet closes with no dialog and focus returns to the opener | | swipes down or taps the scrim, pristine | the sheet closes | | closes a dirty form (x, Cancel, Escape, swipe, scrim) | the "Discard changes?" dialog opens and the sheet stays | | any dismissal while Saving | nothing closes | Scenario: Mobile no longer shows a back arrow Given the viewport is below 640 px Then no arrow-left back button is in the form header Scenario: Focus Then focus is trapped in the sheet or slide-over and returns to the opener on close, and on open it moves to the Name field in create mode