diff --git a/.env.ci b/.env.ci
index 858375c..665468d 100644
--- a/.env.ci
+++ b/.env.ci
@@ -14,7 +14,6 @@ LOG_LEVEL=debug
NIGHTWATCH_ENABLED=false
-
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
diff --git a/lang/de_CH/components.php b/lang/de_CH/components.php
index 75e1dd6..26e11a6 100644
--- a/lang/de_CH/components.php
+++ b/lang/de_CH/components.php
@@ -5,9 +5,7 @@
return [
'intro' => [
'title' => 'Schön, bist du da',
- 'window' => 'Wir sind codebar',
'legend' => 'Was möchtest du über uns wissen?',
- 'overview' => 'Übersicht',
'shortcuts' => 'Abschnitt wechseln: Zifferntasten 1 bis 3 oder die Pfeiltasten links und rechts.',
'prev_section' => 'Vorheriger Abschnitt',
'next_section' => 'Nächster Abschnitt',
diff --git a/lang/en_CH/components.php b/lang/en_CH/components.php
index 3fc3bfc..d4a82b0 100644
--- a/lang/en_CH/components.php
+++ b/lang/en_CH/components.php
@@ -5,9 +5,7 @@
return [
'intro' => [
'title' => 'Good to have you here',
- 'window' => 'We are codebar',
'legend' => 'What would you like to know about us?',
- 'overview' => 'Overview',
'shortcuts' => 'Switch section: number keys 1 to 3, or the left and right arrow keys.',
'prev_section' => 'Previous section',
'next_section' => 'Next section',
diff --git a/resources/css/app.css b/resources/css/app.css
index 0cc3a36..d263079 100644
--- a/resources/css/app.css
+++ b/resources/css/app.css
@@ -857,31 +857,44 @@ body {
}
.intro-tabs__panels {
- display: grid;
min-width: 0;
}
-.intro-tabs__panel {
- grid-area: 1 / 1;
-}
-
-.intro-tabs__panel:not(:first-child) {
- visibility: hidden;
- opacity: 0;
+@supports not selector(:has(*)) {
+ .intro-tabs__panel:not(:first-child) {
+ display: none;
+ }
}
@supports selector(:has(*)) {
.intro-tabs__panel {
- visibility: hidden;
- opacity: 0;
- transition: opacity 200ms ease;
+ display: none;
}
.intro-tabs:has([data-tab='0']:checked) [data-panel='0'],
.intro-tabs:has([data-tab='1']:checked) [data-panel='1'],
- .intro-tabs:has([data-tab='2']:checked) [data-panel='2'],
- .intro-tabs:has([data-tab='3']:checked) [data-panel='3'] {
- visibility: visible;
- opacity: 1;
+ .intro-tabs:has([data-tab='2']:checked) [data-panel='2'] {
+ display: block;
+ }
+
+ @media (width >= 40rem) {
+ .intro-tabs__panels {
+ display: grid;
+ }
+
+ .intro-tabs__panel {
+ display: block;
+ grid-area: 1 / 1;
+ visibility: hidden;
+ opacity: 0;
+ transition: opacity 200ms ease;
+ }
+
+ .intro-tabs:has([data-tab='0']:checked) [data-panel='0'],
+ .intro-tabs:has([data-tab='1']:checked) [data-panel='1'],
+ .intro-tabs:has([data-tab='2']:checked) [data-panel='2'] {
+ visibility: visible;
+ opacity: 1;
+ }
}
}
diff --git a/resources/views/components/intro.blade.php b/resources/views/components/intro.blade.php
index 4c985ce..a014563 100644
--- a/resources/views/components/intro.blade.php
+++ b/resources/views/components/intro.blade.php
@@ -1,8 +1,7 @@
@php
$sections = [
- ['key' => 'start', 'command' => null, 'next' => null],
- ['key' => 'who_we_are', 'command' => __('components.intro.who_we_are.command'), 'next' => 2],
- ['key' => 'what_we_do', 'command' => __('components.intro.what_we_do.command'), 'next' => 3],
+ ['key' => 'who_we_are', 'command' => __('components.intro.who_we_are.command'), 'next' => 1],
+ ['key' => 'what_we_do', 'command' => __('components.intro.what_we_do.command'), 'next' => 2],
['key' => 'how_we_work', 'command' => __('components.intro.how_we_work.command'), 'next' => null],
];
@@ -22,20 +21,17 @@
- {{ __('components.intro.window') }} – {{ config('company.legal_name') }}
+ {{ config('company.legal_name') }}
-
-
- @foreach(array_slice($sections, 1) as $section)
-
-
+ first) class="sr-only"/>
{{ $loop->iteration }}
{{ $section['command'] }}
@@ -53,57 +49,39 @@ class="{{ $cap }} cursor-pointer transition hover:bg-white focus-ring">→index }}">
- @if($section['key'] === 'start')
-
- @foreach(array_slice($sections, 1) as $target)
-
-
- {{ $loop->iteration }}
-
- {{ __('components.intro.'.$target['key'].'.teaser') }}
-
-
+ {{ __('components.intro.'.$section['key'].'.title') }}
+
+ @foreach (\Illuminate\Support\Arr::wrap(__('components.intro.'.$section['key'].'.text')) as $paragraph)
+ ! $loop->first])>{!! $paragraph !!}
+ @endforeach
+
+ @php($items = __('components.intro.'.$section['key'].'.items'))
+ @if (is_array($items))
+
+ @foreach ($items as $item)
+
+ ·
+ {!! $item !!}
@endforeach
- @else
- {{ __('components.intro.'.$section['key'].'.title') }}
-
- @foreach (\Illuminate\Support\Arr::wrap(__('components.intro.'.$section['key'].'.text')) as $paragraph)
- ! $loop->first])>{!! $paragraph !!}
- @endforeach
-
- @php($items = __('components.intro.'.$section['key'].'.items'))
- @if (is_array($items))
-
- @foreach ($items as $item)
-
- ·
- {!! $item !!}
-
- @endforeach
-
- @endif
@endif
- @unless($section['key'] === 'start')
-
- @if($section['next'] !== null)
-
- →
- {{ __('components.intro.next', ['title' => $sections[$section['next']]['command']]) }}
-
- @else
-
- →
- {{ __('components.intro.cta') }}
-
- @endif
-
- @endunless
+
+ @if($section['next'] !== null)
+
+ →
+ {{ __('components.intro.next', ['title' => $sections[$section['next']]['command']]) }}
+
+ @else
+
+ →
+ {{ __('components.intro.cta') }}
+
+ @endif
+
@endforeach
diff --git a/tests/Feature/Components/IntroTabsTest.php b/tests/Feature/Components/IntroTabsTest.php
index 5fa7f56..60d1c5a 100644
--- a/tests/Feature/Components/IntroTabsTest.php
+++ b/tests/Feature/Components/IntroTabsTest.php
@@ -34,14 +34,14 @@ function introPanel(string $html, int $index): string
: $panel->before('')->toString();
}
-it('renders all four sections as panels at once', function () {
+it('renders the three sections as panels at once', function () {
$html = introMarkup();
- foreach (range(0, 3) as $index) {
+ foreach (range(0, 2) as $index) {
expect($html)->toContain('data-panel="'.$index.'"');
}
- expect($html)->not->toContain('data-panel="4"');
+ expect($html)->not->toContain('data-panel="3"');
});
it('has a stylesheet rule for every tab it renders', function () {
@@ -72,7 +72,7 @@ function introPanel(string $html, int $index): string
});
it('keeps the four areas on one line each', function () {
- $panel = introPanel(introMarkup(), 2);
+ $panel = introPanel(introMarkup(), 1);
foreach (introItems() as $item) {
expect($panel)->toContain(''.$item.' ');
@@ -82,34 +82,40 @@ function introPanel(string $html, int $index): string
it('switches without javascript through radio inputs', function () {
$html = introMarkup();
- foreach (range(0, 3) as $index) {
+ foreach (range(0, 2) as $index) {
expect($html)
->toContain('id="intro-tab-'.$index.'"')
- ->toContain('data-tab="'.$index.'"');
- }
-
- foreach (range(1, 3) as $index) {
- expect($html)->toContain('for="intro-tab-'.$index.'"');
+ ->toContain('data-tab="'.$index.'"')
+ ->toContain('for="intro-tab-'.$index.'"');
}
expect($html)->toContain('name="intro-tab"');
});
-it('leaves the start panel out of the tab bar', function () {
+it('puts every section into the tab bar, and nothing else', function () {
$bar = Str::of(introMarkup())->after('')->before('intro-tabs__panels')->toString();
- expect($bar)->not->toContain('for="intro-tab-0"')
- ->and(substr_count($bar, 'toBe(3);
+ expect(substr_count($bar, 'toBe(3)
+ ->and($bar)->toContain('for="intro-tab-0"');
});
-it('opens on the start tab', function () {
+it('opens on the first section instead of an overview', function () {
$html = introMarkup();
$first = Str::of($html)->after('id="intro-tab-0"')->before('/>')->toString();
$second = Str::of($html)->after('id="intro-tab-1"')->before('/>')->toString();
expect($first)->toContain('checked')
- ->and($second)->not->toContain('checked');
+ ->and($second)->not->toContain('checked')
+ ->and(introPanel($html, 0))->toContain(__('components.intro.who_we_are.text'));
+});
+
+it('no longer lists the sections as teasers anywhere', function () {
+ $window = Str::of(introMarkup())->after('before(' ')->toString();
+
+ foreach (['who_we_are', 'what_we_do', 'how_we_work'] as $key) {
+ expect($window)->not->toContain(__('components.intro.'.$key.'.teaser'));
+ }
});
it('exposes one number key per tab', function () {
@@ -135,32 +141,15 @@ function introPanel(string $html, int $index): string
->toContain('not->toContain(__('components.intro.title'));
-});
-
-it('links from the start tab to each of the other three, with the full sentence as the link', function () {
- $panel = introPanel(introMarkup(), 0);
-
- foreach ([1, 2, 3] as $index) {
- expect($panel)->toContain('for="intro-tab-'.$index.'"');
- }
-
- foreach (['who_we_are', 'what_we_do', 'how_we_work'] as $key) {
- expect($panel)->toContain(__('components.intro.'.$key.'.teaser'))
- ->and($panel)->not->toContain(__('components.intro.'.$key.'.command').'');
- }
-});
-
-it('offers the next section below the two middle panels', function () {
+it('offers the next section below the first two panels', function () {
$html = introMarkup();
- expect(introPanel($html, 1))->toContain('for="intro-tab-2"')
- ->and(introPanel($html, 2))->toContain('for="intro-tab-3"');
+ expect(introPanel($html, 0))->toContain('for="intro-tab-1"')
+ ->and(introPanel($html, 1))->toContain('for="intro-tab-2"');
});
it('ends the last panel on the contact page instead of another tab', function () {
- $panel = introPanel(introMarkup(), 3);
+ $panel = introPanel(introMarkup(), 2);
expect($panel)
->toContain(localized_route('contact.index'))
@@ -195,12 +184,19 @@ function introPanel(string $html, int $index): string
expect(introMarkup())->not->toContain('➜');
});
-it('numbers the three teasers like the tabs they open', function () {
- $panel = introPanel(introMarkup(), 0);
+it('lets the panel height follow the content on mobile and freezes it on desktop', function () {
+ $intro = Str::after(file_get_contents(resource_path('css/app.css')), '.intro-tabs__panels');
- foreach (range(1, 3) as $index) {
- expect($panel)->toContain('>'.$index.'');
- }
+ $mobile = Str::before($intro, '@media');
+ $desktop = Str::after($intro, '@media (width >= 40rem)');
+
+ expect($mobile)->toContain('display: none')
+ ->and($mobile)->not->toContain('display: grid')
+ ->and($desktop)->toContain('display: grid')
+ ->and($desktop)->toContain('grid-area: 1 / 1');
+
+ expect(Str::before($intro, '.intro-tabs__panel:not(:first-child)'))
+ ->toContain('@supports not selector(:has(*))');
});
it('translates the commands per locale', function () {
diff --git a/tests/js/intro-tabs.test.js b/tests/js/intro-tabs.test.js
index c6d3fb1..dc0af28 100644
--- a/tests/js/intro-tabs.test.js
+++ b/tests/js/intro-tabs.test.js
@@ -2,16 +2,15 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { INTRO_TAB_KEY, introTabs } from '../../resources/js/intro-tabs.js'
-const TABS = [1, 2, 3]
+const TABS = [0, 1, 2]
function mount() {
document.body.innerHTML = `
-
${TABS.map((index) => `
+ data-tab="${index}" data-shortcut="${index + 1}" ${index === 0 ? 'checked' : ''}>
`).join('')}
@@ -48,7 +47,7 @@ afterEach(() => {
})
describe('remembering the tab for the session', () => {
- it('opens on the start panel when the session is new', () => {
+ it('opens on the first section when the session is new', () => {
mount()
expect(checkedIndex()).toBe(0)
@@ -70,8 +69,8 @@ describe('remembering the tab for the session', () => {
press('3')
- expect(checkedIndex()).toBe(3)
- expect(window.sessionStorage.getItem(INTRO_TAB_KEY)).toBe('3')
+ expect(checkedIndex()).toBe(2)
+ expect(window.sessionStorage.getItem(INTRO_TAB_KEY)).toBe('2')
})
it('stores the tab the arrow keys land on', () => {
@@ -111,7 +110,7 @@ describe('remembering the tab for the session', () => {
press('3')
}).not.toThrow()
- expect(checkedIndex()).toBe(3)
+ expect(checkedIndex()).toBe(2)
})
})
@@ -120,21 +119,21 @@ describe('keyboard navigation', () => {
mount()
for (const index of TABS) {
- press(String(index))
- expect(checkedIndex(), `key ${index}`).toBe(index)
+ press(String(index + 1))
+ expect(checkedIndex(), `key ${index + 1}`).toBe(index)
}
})
- it('has no shortcut left for the start panel', () => {
+ it('has no shortcut left over for a fourth tab', () => {
mount()
press('3')
press('4')
- expect(checkedIndex()).toBe(3)
+ expect(checkedIndex()).toBe(2)
})
- it('enters the tabs from the start panel with the right arrow', () => {
+ it('moves to the next tab with the right arrow', () => {
mount()
press('ArrowRight')
@@ -142,23 +141,23 @@ describe('keyboard navigation', () => {
expect(checkedIndex()).toBe(1)
})
- it('enters the tabs from the start panel with the left arrow', () => {
+ it('wraps to the last tab with the left arrow', () => {
mount()
press('ArrowLeft')
- expect(checkedIndex()).toBe(3)
+ expect(checkedIndex()).toBe(2)
})
- it('wraps the arrow keys through the three tabs, never back to the start panel', () => {
+ it('wraps the arrow keys through the three tabs', () => {
mount()
press('3')
press('ArrowRight')
- expect(checkedIndex()).toBe(1)
+ expect(checkedIndex()).toBe(0)
press('ArrowLeft')
- expect(checkedIndex()).toBe(3)
+ expect(checkedIndex()).toBe(2)
})
it('ignores shortcuts while the visitor is typing in a field', () => {
@@ -213,7 +212,7 @@ describe('arrow buttons', () => {
component.step(-1)
- expect(checkedIndex()).toBe(3)
+ expect(checkedIndex()).toBe(2)
})
it('wraps forward past the last tab back to the first', () => {
@@ -221,20 +220,9 @@ describe('arrow buttons', () => {
component.step(1)
component.step(1)
- component.step(1)
- expect(checkedIndex()).toBe(3)
+ expect(checkedIndex()).toBe(2)
component.step(1)
- expect(checkedIndex()).toBe(1)
- })
-
- it('never lands back on the start panel', () => {
- const { component } = mount()
-
- for (let i = 0; i < 10; i++) component.step(1)
- expect(checkedIndex()).not.toBe(0)
-
- for (let i = 0; i < 10; i++) component.step(-1)
- expect(checkedIndex()).not.toBe(0)
+ expect(checkedIndex()).toBe(0)
})
})