Akkordeons weicher aufklappen, starken Hover entfernen
Die Klappelemente sprangen auf und zu: x-show gibt dem Element sofort die volle Hoehe und blendet nur den Inhalt ein. Ersetzt durch eine Animation von grid-template-rows 0fr → 1fr ueber 500 ms mit cubic-bezier(0.33, 1, 0.68, 1), die die tatsaechliche Hoehe bewegt – ohne das Alpine-Collapse-Plugin, das in diesem Projekt nicht registriert ist. - Der zugeklappte Zustand steht auch statisch im class-Attribut, damit vor dem Start von Alpine nichts aufgeklappt aufblitzt. - aria-hidden haengt am Zustand, weil der Inhalt jetzt dauerhaft im DOM steht. - Hover: die Flaechenaufhellung (hover:bg-muted/30) ist raus, geblieben ist ein feiner Farbwechsel der Ueberschrift. Im FAQ entfaellt zusaetzlich der Hintergrundwechsel am Icon-Kreis. - Der Pfeil dreht in derselben Dauer und Kurve wie das Aufklappen. Betrifft beide Akkordeons – die Vertiefungen der Markenseiten und das FAQ. Tests: 861 gruen (3189 Assertions), Pint sauber, npm run build:b2in gebaut. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
a52347845e
commit
f971f416db
5 changed files with 115 additions and 54 deletions
|
|
@ -66,6 +66,7 @@ resources/views/livewire/web/components/sections/brand-detail.blade.php
|
|||
resources/views/livewire/web/components/sections/brand-overview.blade.php
|
||||
resources/views/livewire/web/components/sections/core-brands.blade.php
|
||||
resources/views/livewire/web/components/sections/display-feed.blade.php
|
||||
resources/views/livewire/web/components/sections/f-a-q.blade.php
|
||||
resources/views/livewire/web/components/sections/immobilien-teaser.blade.php
|
||||
resources/views/livewire/web/components/sections/magazin-list.blade.php
|
||||
resources/views/livewire/web/components/sections/marken-cta.blade.php
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ eingearbeitet. Offen: Bildmaterial, Rechtstexte und die Herstellerfreigaben.
|
|||
**Stand 03.09.2026:** T1 und B1–B13 abgearbeitet, **alle Arbeitspakete AP0–AP6 umgesetzt**, die
|
||||
Bestandsinhalte auf den Interior-Auftritt umgestellt, die Seite abgestimmt und alle vier Punkte
|
||||
der Abstimmungsrunde vom 03.09. umgesetzt (Nachtrag 5). Alle drei Markenseiten haben jetzt einen
|
||||
ausführlichen Text. `php artisan test --compact`: **859 grün** (3180 Assertions), Pint sauber,
|
||||
ausführlichen Text. `php artisan test --compact`: **861 grün** (3189 Assertions), Pint sauber,
|
||||
`npm run build:b2in` gebaut.
|
||||
**Die Seite ist testbereit** – die ausstehenden Zulieferungen trägt Marcel selbst im CMS nach.
|
||||
|
||||
|
|
@ -986,6 +986,28 @@ Textlink. Neu:
|
|||
Die Textinhalte kommen unverändert aus dem CMS (`cta_title` / `cta_text`) – Punkt 4 der Liste war
|
||||
inhaltlich bereits durch die neuen Texte erledigt („Ihre Küche. Persönlich geplant.").
|
||||
|
||||
#### 5 · Akkordeons weicher (Nachreichung aus derselben Runde)
|
||||
|
||||
Rückmeldung: Die Klappelemente springen zu schnell auf und zu, der Hover-Effekt ist zu stark.
|
||||
|
||||
Ursache war `x-show` mit einer Opacity-Bewegung: Das Element nimmt sofort seine volle Höhe ein
|
||||
und blendet nur den Inhalt ein – es sieht aus wie ein Sprung. Ersetzt durch eine Animation von
|
||||
`grid-template-rows: 0fr → 1fr` über 500 ms mit `cubic-bezier(0.33, 1, 0.68, 1)`, die die
|
||||
tatsächliche Höhe bewegt. Das Alpine-Collapse-Plugin ist in diesem Projekt nicht registriert;
|
||||
diese Lösung kommt ohne neue Abhängigkeit aus.
|
||||
|
||||
- Der zugeklappte Zustand steht **auch statisch im `class`-Attribut**, damit vor dem Start von
|
||||
Alpine nichts aufgeklappt aufblitzt. Die Objekt-Schreibweise von `x-bind:class` entfernt ihn
|
||||
beim Öffnen wieder.
|
||||
- `aria-hidden` hängt am Zustand, weil der Inhalt jetzt dauerhaft im DOM steht.
|
||||
- **Hover:** Die Flächenaufhellung (`hover:bg-muted/30`) ist raus. Geblieben ist ein feiner
|
||||
Farbwechsel der Überschrift auf die Sekundärfarbe. Im FAQ entfällt zusätzlich der
|
||||
Hintergrundwechsel am Icon-Kreis.
|
||||
- Der Pfeil dreht in derselben Dauer und Kurve wie das Aufklappen, damit beides zusammenläuft.
|
||||
|
||||
Betrifft **beide** Akkordeons – die Vertiefungen auf den Markenseiten und das FAQ; sie hatten
|
||||
dasselbe Verhalten.
|
||||
|
||||
#### Tests
|
||||
|
||||
`BrandPageSectionsTest` und `BrandPageTest` erweitert:
|
||||
|
|
@ -1000,8 +1022,11 @@ inhaltlich bereits durch die neuen Texte erledigt („Ihre Küche. Persönlich g
|
|||
erscheint auf der deutschen Seite.
|
||||
- Demogalerien tragen keinen „BEISPIEL"-Vermerk mehr, dafür durchgehend eine Kategorie.
|
||||
- Der Normalizer entfernt den Vermerk, ohne Bild, Kategorie oder Reihenfolge zu verändern.
|
||||
- Jede Vertiefung rendert zugeklappt und mit der Höhen-Animation; die alte Sprung-Bewegung ist
|
||||
weg.
|
||||
- Weder Markenseite noch FAQ verwenden noch den Flächen-Hover.
|
||||
|
||||
`php artisan test --compact`: **859 grün** (3180 Assertions), Pint sauber, `npm run build:b2in`
|
||||
`php artisan test --compact`: **861 grün** (3189 Assertions), Pint sauber, `npm run build:b2in`
|
||||
gebaut.
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -88,27 +88,32 @@
|
|||
<button type="button" x-on:click="open = !open"
|
||||
x-bind:aria-expanded="open.toString()"
|
||||
aria-controls="detail-{{ $index }}"
|
||||
class="flex w-full items-center justify-between gap-4 p-5 text-left cursor-pointer hover:bg-muted/30 transition-colors">
|
||||
<span class="font-medium">{{ $section['detail']['title'] }}</span>
|
||||
<svg class="h-5 w-5 shrink-0 text-secondary transition-transform duration-200"
|
||||
x-bind:class="open ? 'rotate-180' : ''" fill="none" stroke="currentColor"
|
||||
class="group flex w-full items-center justify-between gap-4 p-5 text-left cursor-pointer">
|
||||
<span class="font-medium transition-colors duration-300 group-hover:text-secondary">{{ $section['detail']['title'] }}</span>
|
||||
<svg class="h-5 w-5 shrink-0 text-secondary transition-transform duration-500 ease-[cubic-bezier(0.33,1,0.68,1)]"
|
||||
x-bind:class="{ 'rotate-180': open }" fill="none" stroke="currentColor"
|
||||
stroke-width="2" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{{-- x-transition statt x-collapse: das Collapse-Plugin ist in diesem
|
||||
Projekt nicht registriert, dieselbe Bewegung wie im FAQ-Akkordeon. --}}
|
||||
<div id="detail-{{ $index }}" x-show="open" x-cloak
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 -translate-y-2"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2">
|
||||
<div class="px-5 pb-5 border-t border-border/60 pt-4">
|
||||
@foreach ($section['detail']['paragraphs'] as $paragraph)
|
||||
<p class="text-muted-foreground leading-relaxed mt-3 first:mt-0">{{ $paragraph }}</p>
|
||||
@endforeach
|
||||
{{-- Weiches Auf- und Zuklappen ueber grid-template-rows (0fr → 1fr):
|
||||
das animiert die tatsaechliche Hoehe, waehrend x-show den Inhalt
|
||||
schlagartig einblendet. Das Alpine-Collapse-Plugin ist in diesem
|
||||
Projekt nicht registriert, dies kommt ohne Abhaengigkeit aus. --}}
|
||||
{{-- Der zugeklappte Zustand steht auch im statischen class-Attribut,
|
||||
damit vor dem Start von Alpine nichts aufgeklappt aufblitzt. Die
|
||||
Objekt-Schreibweise von x-bind:class entfernt ihn beim Oeffnen. --}}
|
||||
<div id="detail-{{ $index }}"
|
||||
class="grid grid-rows-[0fr] opacity-0 transition-all duration-500 ease-[cubic-bezier(0.33,1,0.68,1)]"
|
||||
x-bind:class="{ 'grid-rows-[1fr] opacity-100': open, 'grid-rows-[0fr] opacity-0': !open }"
|
||||
x-bind:aria-hidden="(!open).toString()">
|
||||
<div class="overflow-hidden">
|
||||
<div class="px-5 pb-5 border-t border-border/60 pt-4">
|
||||
@foreach ($section['detail']['paragraphs'] as $paragraph)
|
||||
<p class="text-muted-foreground leading-relaxed mt-3 first:mt-0">{{ $paragraph }}</p>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -25,34 +25,39 @@
|
|||
<dt>
|
||||
<button
|
||||
@click="openIndex = openIndex === {{ $index }} ? null : {{ $index }}"
|
||||
class="flex w-full items-center justify-between p-6 text-left hover:bg-muted/30 transition-colors duration-200"
|
||||
class="group flex w-full items-center justify-between p-6 text-left cursor-pointer"
|
||||
:aria-expanded="openIndex === {{ $index }}"
|
||||
>
|
||||
<span class="text-lg font-medium text-foreground pr-6">
|
||||
<span class="text-lg font-medium text-foreground pr-6 transition-colors duration-300 group-hover:text-secondary">
|
||||
{{ $faq['question'] }}
|
||||
</span>
|
||||
<span class="flex-shrink-0 ml-6 flex h-8 w-8 items-center justify-center rounded-full bg-secondary/10 transition-all duration-200"
|
||||
:class="openIndex === {{ $index }} ? 'bg-secondary text-white' : 'text-secondary hover:bg-secondary/20'">
|
||||
<svg class="w-5 h-5 transition-transform duration-200"
|
||||
:class="openIndex === {{ $index }} ? 'rotate-180' : ''"
|
||||
<span class="flex-shrink-0 ml-6 flex h-8 w-8 items-center justify-center rounded-full bg-secondary/10 transition-colors duration-300"
|
||||
:class="openIndex === {{ $index }} ? 'bg-secondary text-white' : 'text-secondary'">
|
||||
<svg class="w-5 h-5 transition-transform duration-500 ease-[cubic-bezier(0.33,1,0.68,1)]"
|
||||
:class="{ 'rotate-180': openIndex === {{ $index }} }"
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</dt>
|
||||
<dd x-show="openIndex === {{ $index }}"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 transform -translate-y-2"
|
||||
x-transition:enter-end="opacity-100 transform translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100 transform translate-y-0"
|
||||
x-transition:leave-end="opacity-0 transform -translate-y-2"
|
||||
class="px-6 pb-6">
|
||||
<div class="border-t border-border pt-4">
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
{{ $faq['answer'] }}
|
||||
</p>
|
||||
{{-- grid-template-rows 0fr → 1fr animiert die tatsaechliche Hoehe. Der
|
||||
zugeklappte Zustand steht auch statisch im class-Attribut, damit vor
|
||||
dem Start von Alpine nichts aufgeklappt aufblitzt. --}}
|
||||
<dd class="grid grid-rows-[0fr] opacity-0 transition-all duration-500 ease-[cubic-bezier(0.33,1,0.68,1)]"
|
||||
:class="{
|
||||
'grid-rows-[1fr] opacity-100': openIndex === {{ $index }},
|
||||
'grid-rows-[0fr] opacity-0': openIndex !== {{ $index }},
|
||||
}"
|
||||
:aria-hidden="(openIndex !== {{ $index }}).toString()">
|
||||
<div class="overflow-hidden">
|
||||
<div class="px-6 pb-6">
|
||||
<div class="border-t border-border pt-4">
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
{{ $faq['answer'] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
|
@ -80,34 +85,39 @@
|
|||
<dt>
|
||||
<button
|
||||
@click="openIndex = openIndex === {{ $index }} ? null : {{ $index }}"
|
||||
class="flex w-full items-center justify-between p-6 text-left hover:bg-muted/30 transition-colors duration-200"
|
||||
class="group flex w-full items-center justify-between p-6 text-left cursor-pointer"
|
||||
:aria-expanded="openIndex === {{ $index }}"
|
||||
>
|
||||
<span class="text-lg font-medium text-foreground pr-6">
|
||||
<span class="text-lg font-medium text-foreground pr-6 transition-colors duration-300 group-hover:text-secondary">
|
||||
{{ $faq['question'] }}
|
||||
</span>
|
||||
<span class="flex-shrink-0 ml-6 flex h-8 w-8 items-center justify-center rounded-full bg-secondary/10 transition-all duration-200"
|
||||
:class="openIndex === {{ $index }} ? 'bg-secondary text-white' : 'text-secondary hover:bg-secondary/20'">
|
||||
<svg class="w-5 h-5 transition-transform duration-200"
|
||||
:class="openIndex === {{ $index }} ? 'rotate-180' : ''"
|
||||
<span class="flex-shrink-0 ml-6 flex h-8 w-8 items-center justify-center rounded-full bg-secondary/10 transition-colors duration-300"
|
||||
:class="openIndex === {{ $index }} ? 'bg-secondary text-white' : 'text-secondary'">
|
||||
<svg class="w-5 h-5 transition-transform duration-500 ease-[cubic-bezier(0.33,1,0.68,1)]"
|
||||
:class="{ 'rotate-180': openIndex === {{ $index }} }"
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</dt>
|
||||
<dd x-show="openIndex === {{ $index }}"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 transform -translate-y-2"
|
||||
x-transition:enter-end="opacity-100 transform translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100 transform translate-y-0"
|
||||
x-transition:leave-end="opacity-0 transform -translate-y-2"
|
||||
class="px-6 pb-6">
|
||||
<div class="border-t border-border pt-4">
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
{{ $faq['answer'] }}
|
||||
</p>
|
||||
{{-- grid-template-rows 0fr → 1fr animiert die tatsaechliche Hoehe. Der
|
||||
zugeklappte Zustand steht auch statisch im class-Attribut, damit vor
|
||||
dem Start von Alpine nichts aufgeklappt aufblitzt. --}}
|
||||
<dd class="grid grid-rows-[0fr] opacity-0 transition-all duration-500 ease-[cubic-bezier(0.33,1,0.68,1)]"
|
||||
:class="{
|
||||
'grid-rows-[1fr] opacity-100': openIndex === {{ $index }},
|
||||
'grid-rows-[0fr] opacity-0': openIndex !== {{ $index }},
|
||||
}"
|
||||
:aria-hidden="(openIndex !== {{ $index }}).toString()">
|
||||
<div class="overflow-hidden">
|
||||
<div class="px-6 pb-6">
|
||||
<div class="border-t border-border pt-4">
|
||||
<p class="text-muted-foreground leading-relaxed">
|
||||
{{ $faq['answer'] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -53,6 +53,26 @@ it('turns every marked deepening into a collapsible area', function () {
|
|||
->and($html)->toContain('NEW DIMENSIONS');
|
||||
});
|
||||
|
||||
it('renders every deepening collapsed and animates its height', function () {
|
||||
$html = $this->get('/marken/rotpunkt')->assertSuccessful()->getContent();
|
||||
|
||||
// Der zugeklappte Zustand muss im statischen class-Attribut stehen, sonst blitzt der Inhalt
|
||||
// vor dem Start von Alpine aufgeklappt auf.
|
||||
expect(substr_count($html, 'grid grid-rows-[0fr] opacity-0 transition-all'))->toBe(4)
|
||||
// grid-template-rows animiert die tatsaechliche Hoehe – die alte Bewegung blendete den
|
||||
// Inhalt nur ein und sprang dabei auf die volle Hoehe.
|
||||
->and($html)->not->toContain('x-transition:enter-start="opacity-0 -translate-y-2"')
|
||||
->and($html)->toContain("'grid-rows-[1fr] opacity-100': open");
|
||||
});
|
||||
|
||||
it('uses no full-surface hover on the accordion buttons', function () {
|
||||
foreach (['/marken/rotpunkt', '/faq'] as $path) {
|
||||
$html = $this->get($path)->assertSuccessful()->getContent();
|
||||
|
||||
expect($html)->not->toContain('hover:bg-muted/30');
|
||||
}
|
||||
});
|
||||
|
||||
it('merges the two sustainability sections as the corrected version requires', function () {
|
||||
$html = $this->get('/marken/rotpunkt')->assertSuccessful()->getContent();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue