Compare commits
1 Commits
feat/home-
...
docs/agent
| Author | SHA1 | Date | |
|---|---|---|---|
| 96f94fd571 |
@@ -31,9 +31,13 @@ Hooks live in `.husky/` and auto-install on any plain `npm install` via the `pre
|
||||
|
||||
Follow PSR-4 and Laravel conventions: PascalCase classes, camelCase methods, and snake_case database columns. Use four spaces (two in YAML, except four in Compose files), LF endings, and UTF-8 as defined by `.editorconfig`. Every project-owned PHP file must place `declare(strict_types=1);` immediately after `<?php`. Keep domain code independent of Filament and Livewire. Run `composer pint` to format and `composer phpstan` before review.
|
||||
|
||||
## Design Principles: DRY & YAGNI
|
||||
|
||||
Write for the problem at hand, not an imagined future. **DRY**: extract and reuse a piece of logic as soon as it is genuinely duplicated in more than one place — but not before. **YAGNI**: do not add abstraction, configurability, or layers speculatively; add them only when a concrete requirement demands it. Prefer the simplest thing that solves the current requirement. Duplication that appears once is not yet a reason to abstract — wait for a second real occurrence before generalizing. This repo already encodes YAGNI in `openspec/config.yaml` (no generic repositories / `BaseService`); keep that spirit in new code. Avoid over-engineering and avoid premature extraction.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
Tests use Pest 4; browser coverage uses Pest Browser/Playwright. Name files by behavior, ending in `Test.php`, and add tests in the suite matching the changed layer. Feature tests use `RefreshDatabase`. Add architecture coverage for dependency-boundary changes. No numeric coverage threshold is enforced, but changed behavior must have regression coverage.
|
||||
Tests use Pest 4; browser coverage uses Pest Browser/Playwright. Tests are verification, not a design driver — write them to cover behavior you've already implemented, matching the layer you changed. Name files by behavior, ending in `Test.php`, and add tests in the suite matching the changed layer. Feature tests use `RefreshDatabase`. Add architecture coverage for dependency-boundary changes. No numeric coverage threshold is enforced; add regression tests where a bug was fixed or behavior is non-obvious, without making tests a front-loaded design ceremony.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ If the import above did not load, read `AGENTS.md` at the repo root now — it i
|
||||
- Every project-owned PHP file starts with `declare(strict_types=1);` immediately after `<?php`.
|
||||
- Browser tests are CI-only (they run against a FrankenPHP container built by `docker build`, not `artisan serve`).
|
||||
|
||||
## Design principles: DRY & YAGNI (not TDD)
|
||||
|
||||
Write for the problem at hand, not an imagined future. **DRY**: extract and reuse only once logic is genuinely duplicated in more than one place. **YAGNI**: no speculative abstraction, configurability, or layers — add them only when a concrete requirement demands it. Prefer the simplest thing that solves the current requirement; avoid over-engineering and premature extraction. Tests are verification, not a design driver: write them to cover behavior already implemented, not as a front-loaded TDD ceremony.
|
||||
|
||||
## Environment note
|
||||
|
||||
PHP and Composer are **not on PATH** in this environment, and `vendor/` and `node_modules/` are absent. Every `composer …` / `php artisan …` command in `AGENTS.md` and `README.md` assumes a PHP 8.4+ runtime with Composer 2 installed. Verify the toolchain before promising a command ran.
|
||||
|
||||
@@ -2,29 +2,48 @@
|
||||
'settings',
|
||||
])
|
||||
|
||||
<x-public.photo-hero
|
||||
@class(['home-chapter'])
|
||||
<section
|
||||
aria-labelledby="hero-heading"
|
||||
class="home-chapter border-b border-amare-border bg-amare-bg"
|
||||
data-chapter="hero"
|
||||
:image-path="$settings->hero_image_path"
|
||||
:image-alt="$settings->hero_image_alt ?: $settings->brand_name"
|
||||
:eyebrow="$settings->hero_eyebrow"
|
||||
:title="$settings->hero_title ?: 'Celebrações com propósito'"
|
||||
:summary="$settings->hero_subtitle"
|
||||
brand-mark
|
||||
data-motion="page-open"
|
||||
>
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<a href="{{ route('contact') }}" data-testid="home-primary-cta" class="inline-flex min-h-11 items-center bg-amare-accent px-5 py-3 text-sm font-semibold text-amare-accent-text transition-colors duration-(--amare-duration-normal) ease-(--amare-ease-standard) hover:bg-amare-accent-hover">
|
||||
{{ $settings->hero_cta_label ?: 'Solicitar proposta' }}
|
||||
</a>
|
||||
|
||||
@if (filled($settings->hero_secondary_cta_label))
|
||||
<a href="{{ route('portfolio.index') }}" class="inline-flex min-h-11 items-center text-sm font-semibold text-amare-accent transition-colors hover:text-amare-accent-deep">
|
||||
<span class="border-b border-amare-accent pb-1">{{ $settings->hero_secondary_cta_label }}</span>
|
||||
</a>
|
||||
@endif
|
||||
<div class="container-amare grid items-end gap-12 pb-12 pt-16 md:grid-cols-[1.1fr_0.9fr] md:gap-[clamp(3rem,6vw,7rem)] md:pb-[72px] md:pt-24">
|
||||
<div data-hero-content data-reveal-group class="flex flex-col gap-8">
|
||||
<div data-motion-beat="eyebrow">
|
||||
<p class="text-xs font-bold uppercase tracking-[0.16em] text-amare-accent-deep">Assessoria & produção de eventos • São Paulo</p>
|
||||
</div>
|
||||
|
||||
@if (filled($settings->hero_note))
|
||||
<p class="mt-4 max-w-xl text-sm text-amare-text-muted">{{ $settings->hero_note }}</p>
|
||||
<h1 id="hero-heading" data-motion-beat="title" class="text-[clamp(3rem,8vw,5.75rem)] font-medium leading-[0.95] tracking-[-0.04em] text-amare-text">
|
||||
Eventos com intenção, cuidado e presença.
|
||||
</h1>
|
||||
|
||||
<p class="max-w-[760px] text-[clamp(1.1875rem,2vw,1.5rem)] leading-[1.55] text-amare-text-muted">
|
||||
Uma boutique de assessoria que une sensibilidade, organização e condução profissional para transformar cada projeto em uma experiência bem cuidada.
|
||||
</p>
|
||||
|
||||
<div data-motion-beat="cta" class="flex flex-wrap items-center gap-4">
|
||||
<a href="#sobre" data-testid="home-primary-cta" class="inline-flex min-h-[48px] items-center justify-center border border-transparent bg-amare-accent px-6 text-xs font-bold uppercase tracking-[0.09em] text-amare-accent-text transition-colors hover:bg-amare-accent-deep">
|
||||
Conhecer a Amare
|
||||
</a>
|
||||
<a href="#contato" class="inline-flex min-h-[48px] items-center justify-center border border-amare-accent px-6 text-xs font-bold uppercase tracking-[0.09em] text-amare-accent-deep transition-colors hover:bg-amare-accent hover:text-amare-accent-text">
|
||||
Enviar briefing
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-split-hero
|
||||
data-motion-beat="media"
|
||||
class="min-h-[320px] overflow-hidden bg-amare-bg-deep md:min-h-[520px]"
|
||||
>
|
||||
@if (filled($settings->hero_image_path))
|
||||
<x-media.image :path="$settings->hero_image_path" :alt="$settings->hero_image_alt ?: $settings->brand_name" loading="eager" fetchpriority="high" sizes="(max-width: 1023px) 100vw, 45vw" class="img-editorial h-full w-full object-cover" />
|
||||
@else
|
||||
<div data-tonal-hero class="flex h-full min-h-[320px] items-center justify-center bg-gradient-to-br from-amare-bg-deep to-amare-bg-archive md:min-h-[520px]">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-amare-muted">Fotografia hero</p>
|
||||
</div>
|
||||
@endif
|
||||
</x-public.photo-hero>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -38,7 +38,7 @@ it('keeps the hero asymmetric on desktop and stacked through md and mobile', fun
|
||||
startsBelowHeader: Math.abs((heroRect?.top ?? -1) - (headerRect?.bottom ?? -2)) <= 1,
|
||||
mediaBesideContent: (mediaRect?.left ?? 0) >= (contentRect?.right ?? Number.POSITIVE_INFINITY) - 1,
|
||||
mediaMinHeight: Math.round(mediaRect?.height ?? 0) >= 519,
|
||||
mediaTouchesViewportRight: (mediaRect?.right ?? 0) >= window.innerWidth - 4,
|
||||
mediaInsideContainer: (mediaRect?.right ?? 0) < window.innerWidth - 100,
|
||||
horizontalOverflow: document.documentElement.scrollWidth > window.innerWidth,
|
||||
};
|
||||
}
|
||||
@@ -49,7 +49,7 @@ it('keeps the hero asymmetric on desktop and stacked through md and mobile', fun
|
||||
'startsBelowHeader' => true,
|
||||
'mediaBesideContent' => true,
|
||||
'mediaMinHeight' => true,
|
||||
'mediaTouchesViewportRight' => true,
|
||||
'mediaInsideContainer' => true,
|
||||
'horizontalOverflow' => false,
|
||||
]);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class HomePageTest extends TestCase
|
||||
|
||||
$response
|
||||
->assertOk()
|
||||
->assertSee('Celebrações com propósito')
|
||||
->assertSee('Eventos com intenção, cuidado e presença.')
|
||||
->assertSee($settings->brand_name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,18 +105,18 @@ class HomePageContentTest extends TestCase
|
||||
'id="testimonials-heading"',
|
||||
'id="briefing-heading"',
|
||||
], false)
|
||||
->assertSee('Fotografia hero')
|
||||
->assertSee('FOTO DE CASAMENTO 01')
|
||||
->assertSee('Nome e data reais — aguardando autorização')
|
||||
->assertSee('Conteúdo em construção')
|
||||
->assertSee('Fornecedores e parcerias — pós-MVP');
|
||||
}
|
||||
|
||||
public function test_hero_falls_back_to_default_copy_when_fields_are_empty(): void
|
||||
public function test_hero_uses_fixed_editorial_copy_regardless_of_settings(): void
|
||||
{
|
||||
SiteSetting::instance()->update([
|
||||
'hero_title' => '',
|
||||
'hero_subtitle' => '',
|
||||
'hero_cta_label' => '',
|
||||
'hero_image_path' => null,
|
||||
]);
|
||||
|
||||
@@ -124,8 +124,11 @@ class HomePageContentTest extends TestCase
|
||||
|
||||
$response
|
||||
->assertOk()
|
||||
->assertSeeInOrder(['id="hero-heading"', 'Celebrações com propósito'], false)
|
||||
->assertSeeInOrder(['data-testid="home-primary-cta"', 'Solicitar proposta'], false);
|
||||
->assertSee('Eventos com intenção, cuidado e presença.')
|
||||
->assertSee('Uma boutique de assessoria que une sensibilidade, organização e condução profissional para transformar cada projeto em uma experiência bem cuidada.')
|
||||
->assertSee('Conhecer a Amare')
|
||||
->assertSee('Enviar briefing')
|
||||
->assertSeeInOrder(['href="#sobre"', 'data-testid="home-primary-cta"'], false);
|
||||
}
|
||||
|
||||
public function test_home_hero_uses_its_own_cms_image_instead_of_the_social_og_image(): void
|
||||
@@ -157,11 +160,12 @@ class HomePageContentTest extends TestCase
|
||||
|
||||
$this->get(route('home'))
|
||||
->assertOk()
|
||||
->assertSee('data-tonal-hero', false)
|
||||
->assertSee('min-h-[320px]', false)
|
||||
->assertSee('md:min-h-[520px]', false)
|
||||
->assertSee('Fotografia hero')
|
||||
->assertSee('id="hero-heading"', false)
|
||||
->assertSee('data-testid="home-primary-cta"', false)
|
||||
->assertDontSee('data-photo-hero', false)
|
||||
->assertDontSee('data-motion-beat="media"', false)
|
||||
->assertSee('data-motion-beat="media"', false)
|
||||
->assertDontSee('content/home/hero.jpg', false);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,16 +37,15 @@ class ImmersivePhotoHeroTest extends TestCase
|
||||
->assertOk()
|
||||
->assertSee('data-chapter="hero"', false)
|
||||
->assertSee('data-motion="page-open"', false)
|
||||
->assertSee('data-photo-hero', false)
|
||||
->assertSee('min-h-[calc(100dvh-5rem)]', false)
|
||||
->assertSee('md:min-h-[520px]', false)
|
||||
->assertSee('data-hero-content', false)
|
||||
->assertSee('data-split-hero', false)
|
||||
->assertSee('data-motion-beat="media"', false)
|
||||
->assertSee('lg:grid-cols-12', false)
|
||||
->assertSee('md:grid-cols-[1.1fr_0.9fr]', false)
|
||||
->assertSee('content/heroes/home.jpg', false)
|
||||
->assertSee('loading="eager"', false)
|
||||
->assertSee('fetchpriority="high"', false)
|
||||
->assertSee('sizes="(max-width: 1023px) 100vw, 58vw"', false)
|
||||
->assertSee('sizes="(max-width: 1023px) 100vw, 45vw"', false)
|
||||
->assertDontSee('data-tonal-hero', false)
|
||||
->assertSee('content="http://localhost/storage/content/og/social.jpg"', false);
|
||||
}
|
||||
@@ -76,8 +75,9 @@ class ImmersivePhotoHeroTest extends TestCase
|
||||
->assertSee('data-motion="page-open"', false)
|
||||
->assertSee('id="hero-heading"', false)
|
||||
->assertSee('data-testid="home-primary-cta"', false)
|
||||
->assertDontSee('data-photo-hero', false)
|
||||
->assertDontSee('data-split-hero', false)
|
||||
->assertSee('data-split-hero', false)
|
||||
->assertSee('data-tonal-hero', false)
|
||||
->assertSee('Fotografia hero')
|
||||
->assertDontSee('content/heroes/home.jpg', false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class MotionMarkupTest extends TestCase
|
||||
$response
|
||||
->assertOk()
|
||||
->assertSee('data-motion="page-open"', false)
|
||||
->assertSee('data-motion-beat="seal"', false)
|
||||
->assertSee('data-motion-beat="eyebrow"', false)
|
||||
->assertSee('data-motion-beat="title"', false)
|
||||
->assertSee('data-motion-beat="media"', false)
|
||||
->assertSee('data-motion-beat="cta"', false)
|
||||
|
||||
Reference in New Issue
Block a user