Compare commits
4 Commits
feat/man-1
...
feat/man-1
| Author | SHA1 | Date | |
|---|---|---|---|
| 444153bf3d | |||
| 290585d713 | |||
| 8965a7c2fd | |||
| 8decba693a |
@@ -79,4 +79,4 @@ Default vocabulary: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-
|
||||
|
||||
### Domain docs
|
||||
|
||||
Single-context repo. There is no `CONTEXT.md` on purpose — **`SPEC.md` §8.0 is the ubiquitous-language SSOT** (domain model/schema continue in §8.1+). `PRODUCT.md` covers positioning; capabilities live under `openspec/specs/`. `docs/adr/README.md` is an index only: ADR-001 through ADR-010 are decided in `SPEC.md` §21. `docs/agents/domain.md` tells skills to use `SPEC.md` §8 instead of inventing a parallel `CONTEXT.md`.
|
||||
Single-context repo. There is no `CONTEXT.md` — the domain is documented in `SPEC.md` (§8 is the domain model and database schema) and `PRODUCT.md`, with current capabilities described per-capability under `openspec/specs/`. `docs/adr/README.md` is an index only: ADR-001 through ADR-010 are decided in `SPEC.md` §21, and there are no standalone ADR files. `docs/agents/domain.md` describes the generic `CONTEXT.md`/`CONTEXT-MAP.md` layout that the engineering skills look for and instructs them to proceed silently when it's absent, which is the case here.
|
||||
|
||||
@@ -67,7 +67,7 @@ Site-wide content is a singleton row reached via `SiteSetting::instance()`. Publ
|
||||
- **Livewire/Filament temp uploads are pinned to the `local` disk** when `FILESYSTEM_DISK=r2`, because the S3 driver would make the browser PUT straight to R2 and hit CORS. Final media still lands on `r2` via `App\Support\PublicImageUploadRules`. Set `LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK` explicitly to override.
|
||||
- **Contact form is rate limited**: named limiter `contact-briefing`, 5/min per IP, registered in `AppServiceProvider` and applied in `routes/web.php`.
|
||||
- **Filament 5 nested resource layout**: resources are split into `app/Filament/Resources/<Resource>/{Pages,Schemas,Tables,RelationManagers}` rather than a flat resource class. Follow the existing shape in `Resources/PortfolioCases/`.
|
||||
- **Everything user-facing is pt-BR**: routes are `/servicos`, `/pacotes/{slug}`, `/portfolio`, `/portfolio/{slug}`, `/sobre`, `/briefing`, `/contato`, `/privacidade`. `APP_LOCALE=pt_BR`, `APP_TIMEZONE=America/Sao_Paulo` (`config/app.php:68`). Glossário canônico: `SPEC.md` §8.0.
|
||||
- **Everything user-facing is pt-BR**: routes are `/servicos`, `/portfolio`, `/portfolio/{slug}`, `/sobre`, `/privacidade`, `/contato`. `APP_LOCALE=pt_BR`, `APP_TIMEZONE=America/Sao_Paulo` (`config/app.php:68`).
|
||||
- **Design tokens** live in `resources/css/tokens.css` (Heritage Editorial; see `DESIGN.md`). `tests/Feature/PublicSite/HeritageEditorialTokensTest.php` reads that file and asserts the exact hex values, `EB Garamond`, zero border radii, `--amare-container-max: 1120px`, and the *absence* of shadow tokens — so any token edit is a deliberate test change too. Motion lives in `resources/js/motion.js` and is asserted by `tests/Feature/PublicSite/MotionMarkupTest.php` + `tests/Browser/MotionTest.php`.
|
||||
|
||||
## Navigating the normative docs
|
||||
|
||||
42
SPEC.md
42
SPEC.md
@@ -283,15 +283,11 @@ Uma funcionalidade fora do MVP só poderá entrar quando:
|
||||
| Método | Rota | Nome sugerido | Finalidade |
|
||||
|---|---|---|---|
|
||||
| GET | `/` | `home` | Home editorial |
|
||||
| GET | `/servicos` | `services.index` | Lista de serviços / modalidades publicadas |
|
||||
| GET | `/pacotes/{slug}` | `packages.show` | Detalhe de uma modalidade de casamento |
|
||||
| GET | `/servicos` | `services.index` | Lista de serviços publicados |
|
||||
| GET | `/portfolio` | `portfolio.index` | Lista de casos publicados |
|
||||
| GET | `/portfolio/{slug}` | `portfolio.show` | Detalhe de caso |
|
||||
| GET | `/sobre` | `about` | História, método e credenciais |
|
||||
| GET | `/briefing` | `briefing` | Briefing comercial (pedido de proposta) |
|
||||
| POST | `/briefing` | `briefing.store` | Envio do briefing comercial |
|
||||
| GET | `/contato` | `contact` | Contato de parceiro / fornecedor (não é briefing) |
|
||||
| POST | `/contato` | `contact.store` | Envio da consulta de parceiro |
|
||||
| GET | `/contato` | `contact` | Briefing de contato |
|
||||
| GET | `/privacidade` | `privacy` | Política de privacidade |
|
||||
| GET | `/sitemap.xml` | `sitemap` | Sitemap público |
|
||||
| GET | `/robots.txt` | `robots` | Política de crawling |
|
||||
@@ -328,8 +324,7 @@ Administração
|
||||
|
||||
- Dashboard: `Filament Page` customizada com widgets orientados a exceção.
|
||||
- Detalhe do evento: página customizada do Resource com resumo operacional.
|
||||
- Briefing comercial: Blade + Controller (`POST /briefing`), ver §11.2.
|
||||
- Contato de parceiro: Blade + Controller (`POST /contato`) — canal separado do briefing.
|
||||
- Briefing público: Blade + Controller (`POST /contato`), ver §11.2.
|
||||
- Home: Blade com componentes de design reutilizáveis.
|
||||
|
||||
### 5.4 Decisões de UX YAGNI
|
||||
@@ -1248,35 +1243,6 @@ Auditoria não precisa ser um event sourcing. Registrar apenas operações crít
|
||||
|
||||
## 8. Modelo de domínio e banco de dados
|
||||
|
||||
Esta seção é a **fonte única de verdade** do vocabulário de domínio do produto. Issues Linear, OpenSpec, código e copy devem usar estes termos. Não criar glossário paralelo em `CONTEXT.md`.
|
||||
|
||||
### 8.0 Linguagem ubíqua (glossário)
|
||||
|
||||
**Vertente**:
|
||||
Frente de marca da Amare — **Casamentos** ou **Corporate**.
|
||||
_Avoid_: linha de negócio, vertical de produto (em copy pública), “área”
|
||||
|
||||
**Modalidade**:
|
||||
Pacote/formato de acompanhamento de casamento gerenciado no CMS (`WeddingPackage`: Essenza, Conduzione, Grand Jour).
|
||||
_Avoid_: pacote (em copy pública preferir “modalidade”), serviço genérico, plano
|
||||
|
||||
**Briefing**:
|
||||
Pedido comercial de um potencial cliente descrevendo o evento e solicitando atendimento/proposta. Rota canônica: `/briefing`.
|
||||
_Avoid_: contato (quando o sentido é pedido comercial), formulário de parceiro
|
||||
|
||||
**Contato parceiro**:
|
||||
Consulta de fornecedor ou proposta de parceria. Rota canônica: `/contato`. Não mistura com Briefing.
|
||||
_Avoid_: briefing, lead comercial
|
||||
|
||||
**CTA contextual**:
|
||||
Link de conversão de uma Modalidade escolhida — WhatsApp com mensagem que nomeia a modalidade, ou fallback para `/briefing?servico_interesse=` quando não há número oficial válido.
|
||||
|
||||
**CTA orientação**:
|
||||
Link de conversão para visitante que ainda não escolheu Modalidade — WhatsApp com mensagem de orientação humana (sem quiz), ou fallback para `/briefing` quando não há número oficial válido.
|
||||
|
||||
**Boutique**:
|
||||
Conceito de posicionamento (cuidado, especialização, múltiplos serviços sob a mesma marca) — não é entidade de domínio nem rota.
|
||||
|
||||
### 8.1 Módulos
|
||||
|
||||
| Módulo | Responsabilidade |
|
||||
@@ -1829,7 +1795,7 @@ Não transformar seções estáticas em componentes Livewire. Usar Blade quando
|
||||
|
||||
### 11.2 Formulário de briefing
|
||||
|
||||
> **Estado atual:** o formulário de **Briefing** é Blade + Controller (`GET/POST /briefing`, `ContactBriefingRequest`), conforme WEB-05. A rota `/contato` é o canal de **Contato parceiro** (consulta não persistente), separado do briefing. A criação de Lead comercial persistente segue para a Fase 2.
|
||||
> **Estado atual:** o formulário é implementado em Blade + Controller (`POST /contato`, `ContactBriefingRequest`), conforme WEB-05, e essa é a abordagem aceita — não um estágio provisório. Os requisitos abaixo valem independentemente da tecnologia; a criação de Lead segue para a Fase 2.
|
||||
|
||||
O formulário deve:
|
||||
|
||||
|
||||
@@ -8,45 +8,20 @@ use App\Models\SiteSetting;
|
||||
|
||||
final class PackageContactLink
|
||||
{
|
||||
private const ORIENTATION_MESSAGE = 'Olá, ainda não sei qual modalidade de acompanhamento combina com o meu casamento e gostaria de orientação da Amare.';
|
||||
|
||||
/**
|
||||
* Resolve the contextual contact link for a wedding package modality.
|
||||
*
|
||||
* @return array{href: string, isWhatsapp: bool}
|
||||
*/
|
||||
public static function for(SiteSetting $settings, string $packageName): array
|
||||
{
|
||||
return self::resolve(
|
||||
$settings,
|
||||
'Olá, gostaria de conversar sobre a modalidade '.$packageName.' para meu casamento.',
|
||||
['servico_interesse' => $packageName],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the orientation CTA for visitors who have not chosen a modality yet.
|
||||
*
|
||||
* @return array{href: string, isWhatsapp: bool}
|
||||
*/
|
||||
public static function forOrientation(SiteSetting $settings): array
|
||||
{
|
||||
return self::resolve($settings, self::ORIENTATION_MESSAGE, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $briefingQuery
|
||||
* @return array{href: string, isWhatsapp: bool}
|
||||
*/
|
||||
private static function resolve(SiteSetting $settings, string $whatsappMessage, array $briefingQuery): array
|
||||
{
|
||||
$digits = preg_replace('/\D/', '', (string) $settings->whatsapp_number);
|
||||
$isWhatsapp = strlen($digits) >= 10;
|
||||
|
||||
return [
|
||||
'href' => $isWhatsapp
|
||||
? 'https://wa.me/'.$digits.'?text='.rawurlencode($whatsappMessage)
|
||||
: route('briefing', $briefingQuery),
|
||||
? 'https://wa.me/'.$digits.'?text='.rawurlencode('Olá, gostaria de conversar sobre a modalidade '.$packageName.' para meu casamento.')
|
||||
: route('briefing', ['servico_interesse' => $packageName]),
|
||||
'isWhatsapp' => $isWhatsapp,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,28 +1,51 @@
|
||||
# Domain Docs
|
||||
|
||||
How engineering skills should consume this repo's domain documentation.
|
||||
How the engineering skills should consume this repo's domain documentation when exploring the codebase.
|
||||
|
||||
## Single source of truth
|
||||
## Before exploring, read these
|
||||
|
||||
**`SPEC.md` §8** (especially **§8.0 Linguagem ubíqua**) is the glossary and domain model for Amare. There is no root `CONTEXT.md` on purpose — do not create one. Prefer `SPEC.md` over Linear copy or OpenSpec prose when terms conflict.
|
||||
- **`CONTEXT.md`** at the repo root, or
|
||||
- **`CONTEXT-MAP.md`** at the repo root if it exists — it points at one `CONTEXT.md` per context. Read each one relevant to the topic.
|
||||
- **`docs/adr/`** — read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.
|
||||
|
||||
Also useful:
|
||||
If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved.
|
||||
|
||||
- **`PRODUCT.md`** — positioning and audience (not the glossary)
|
||||
- **`openspec/specs/`** — current capability contracts
|
||||
- **`docs/adr/README.md`** — index only; ADR-001–010 live in `SPEC.md` §21
|
||||
## File structure
|
||||
|
||||
## Before exploring
|
||||
Single-context repo (most repos):
|
||||
|
||||
1. Read `SPEC.md` §8.0 for vocabulary; skim §5.1 for public routes and §8.2+ for persistence shape when relevant.
|
||||
2. If a skill expects `CONTEXT.md` / `CONTEXT-MAP.md` and finds neither, **proceed using `SPEC.md` §8** — do not invent a parallel glossary file.
|
||||
```
|
||||
/
|
||||
├── CONTEXT.md
|
||||
├── docs/adr/
|
||||
│ ├── 0001-event-sourced-orders.md
|
||||
│ └── 0002-postgres-for-write-model.md
|
||||
└── src/
|
||||
```
|
||||
|
||||
Multi-context repo (presence of `CONTEXT-MAP.md` at the root):
|
||||
|
||||
```
|
||||
/
|
||||
├── CONTEXT-MAP.md
|
||||
├── docs/adr/ ← system-wide decisions
|
||||
└── src/
|
||||
├── ordering/
|
||||
│ ├── CONTEXT.md
|
||||
│ └── docs/adr/ ← context-specific decisions
|
||||
└── billing/
|
||||
├── CONTEXT.md
|
||||
└── docs/adr/
|
||||
```
|
||||
|
||||
## Use the glossary's vocabulary
|
||||
|
||||
When output names a domain concept (issue title, refactor, hypothesis, test name), use the term as defined in `SPEC.md` §8.0. Don't drift to synonyms the glossary explicitly avoids (`_Avoid_`).
|
||||
When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids.
|
||||
|
||||
If the concept isn't in §8.0 yet, either you're inventing language the project doesn't use (reconsider) or there's a real gap — resolve it by updating `SPEC.md` §8.0 via `/grill-with-docs` / domain-modeling, not by adding `CONTEXT.md`.
|
||||
If the concept you need isn't in the glossary yet, that's a signal — either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`).
|
||||
|
||||
## Flag ADR conflicts
|
||||
|
||||
If output contradicts an ADR in `SPEC.md` §21, surface it explicitly rather than silently overriding.
|
||||
If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
|
||||
|
||||
> _Contradicts ADR-0007 (event-sourced orders) — but worth reopening because…_
|
||||
|
||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -5,6 +5,7 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"@fontsource/eb-garamond": "^5.2.7",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"concurrently": "^9.0.1",
|
||||
"daisyui": "^5.7.16",
|
||||
@@ -49,6 +50,16 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/eb-garamond": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/eb-garamond/-/eb-garamond-5.3.0.tgz",
|
||||
"integrity": "sha512-VplwHpB8FLuzl+4G5LDci1b3Z4SghiuXH8XEgaxjnOb5aue5mEt1njmDKeO6iyLeGa73LffU7PC7hzncLRx0ZA==",
|
||||
"dev": true,
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"prepare": "husky"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fontsource/eb-garamond": "^5.2.7",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"concurrently": "^9.0.1",
|
||||
"daisyui": "^5.7.16",
|
||||
|
||||
@@ -189,6 +189,18 @@
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
.link-editorial {
|
||||
background-image: linear-gradient(currentColor, currentColor);
|
||||
background-position: 0 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 1px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.nav-link-editorial {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:where(a, button, input, textarea, select) {
|
||||
transition-duration: var(--amare-motion-feedback);
|
||||
@@ -200,6 +212,45 @@
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
:where(button[aria-busy='true'], button:disabled):active {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.field-editorial {
|
||||
transition:
|
||||
border-color var(--amare-motion-feedback) var(--amare-ease-arrival),
|
||||
background-color var(--amare-motion-feedback) var(--amare-ease-arrival);
|
||||
}
|
||||
|
||||
.field-editorial:focus,
|
||||
.field-editorial:focus-visible {
|
||||
background-color: var(--amare-color-bg-deep);
|
||||
}
|
||||
|
||||
.link-editorial {
|
||||
transition: color var(--amare-motion-feedback) var(--amare-ease-arrival);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.nav-link-editorial::after {
|
||||
background-color: var(--amare-color-accent);
|
||||
bottom: 0.55rem;
|
||||
content: '';
|
||||
height: 1px;
|
||||
left: 0.75rem;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
transform: scaleX(0);
|
||||
transform-origin: left center;
|
||||
transition: transform var(--amare-motion-feedback) var(--amare-ease-arrival);
|
||||
}
|
||||
|
||||
.nav-link-editorial:hover::after,
|
||||
.nav-link-editorial:focus-visible::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Transform/clip only — never fade text opacity (axe + WCAG mid-transition). */
|
||||
html[data-motion="enhance"] [data-motion="page-open"]:not(.is-active) [data-motion-beat] {
|
||||
transform: translateY(var(--amare-motion-distance-y));
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="lg:col-span-6 lg:col-start-7">
|
||||
<p class="max-w-xl text-amare-text-muted">Uma composição de eventos sociais e corporativos para apresentar a versatilidade da Amare.</p>
|
||||
<a href="{{ route('portfolio.index') }}" class="btn btn-ghost mt-5 min-h-0 px-0 text-xs font-bold uppercase tracking-[0.09em] text-amare-accent hover:bg-transparent hover:text-amare-accent-deep">
|
||||
<span class="border-b border-amare-accent pb-1">Ver portfólio completo</span>
|
||||
<span class="link-editorial">Ver portfólio completo</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
autocomplete="name"
|
||||
maxlength="120"
|
||||
placeholder="Seu nome"
|
||||
class="input w-full text-amare-text placeholder:text-amare-muted/60 @error('nome') input-error @enderror"
|
||||
class="input field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('nome') input-error @enderror"
|
||||
@error('nome') aria-invalid="true" aria-describedby="briefing-nome-error" @enderror
|
||||
>
|
||||
@error('nome')
|
||||
@@ -69,7 +69,7 @@
|
||||
autocomplete="email"
|
||||
maxlength="254"
|
||||
placeholder="voce@email.com"
|
||||
class="input w-full text-amare-text placeholder:text-amare-muted/60 @error('email') input-error @enderror"
|
||||
class="input field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('email') input-error @enderror"
|
||||
@error('email') aria-invalid="true" aria-describedby="briefing-email-error" @enderror
|
||||
>
|
||||
@error('email')
|
||||
@@ -88,7 +88,7 @@
|
||||
autocomplete="tel"
|
||||
maxlength="40"
|
||||
placeholder="(11) 90000-0000"
|
||||
class="input w-full text-amare-text placeholder:text-amare-muted/60 @error('telefone') input-error @enderror"
|
||||
class="input field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('telefone') input-error @enderror"
|
||||
@error('telefone') aria-invalid="true" aria-describedby="briefing-telefone-error" @enderror
|
||||
>
|
||||
@error('telefone')
|
||||
@@ -102,7 +102,7 @@
|
||||
id="briefing-tipo_evento"
|
||||
name="tipo_evento"
|
||||
required
|
||||
class="select w-full text-amare-text @error('tipo_evento') select-error @enderror"
|
||||
class="select field-editorial w-full text-amare-text @error('tipo_evento') select-error @enderror"
|
||||
@error('tipo_evento') aria-invalid="true" aria-describedby="briefing-tipo_evento-error" @enderror
|
||||
>
|
||||
<option value="" selected disabled>Selecione...</option>
|
||||
@@ -125,7 +125,7 @@
|
||||
value="{{ old('data_periodo') }}"
|
||||
maxlength="80"
|
||||
placeholder="Ex.: novembro de 2027"
|
||||
class="input w-full text-amare-text placeholder:text-amare-muted/60 @error('data_periodo') input-error @enderror"
|
||||
class="input field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('data_periodo') input-error @enderror"
|
||||
@error('data_periodo') aria-invalid="true" aria-describedby="briefing-data_periodo-error" @enderror
|
||||
>
|
||||
@error('data_periodo')
|
||||
@@ -143,7 +143,7 @@
|
||||
required
|
||||
maxlength="80"
|
||||
placeholder="Ex.: São Paulo"
|
||||
class="input w-full text-amare-text placeholder:text-amare-muted/60 @error('cidade') input-error @enderror"
|
||||
class="input field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('cidade') input-error @enderror"
|
||||
@error('cidade') aria-invalid="true" aria-describedby="briefing-cidade-error" @enderror
|
||||
>
|
||||
@error('cidade')
|
||||
@@ -163,7 +163,7 @@
|
||||
inputmode="numeric"
|
||||
autocomplete="off"
|
||||
placeholder="Ex.: 120"
|
||||
class="input w-full text-amare-text placeholder:text-amare-muted/60 @error('convidados') input-error @enderror"
|
||||
class="input field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('convidados') input-error @enderror"
|
||||
@error('convidados') aria-invalid="true" aria-describedby="briefing-convidados-error" @enderror
|
||||
>
|
||||
@error('convidados')
|
||||
@@ -180,7 +180,7 @@
|
||||
value="{{ old('servico_interesse') }}"
|
||||
maxlength="120"
|
||||
placeholder="Ex.: planejamento completo"
|
||||
class="input w-full text-amare-text placeholder:text-amare-muted/60 @error('servico_interesse') input-error @enderror"
|
||||
class="input field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('servico_interesse') input-error @enderror"
|
||||
@error('servico_interesse') aria-invalid="true" aria-describedby="briefing-servico_interesse-error" @enderror
|
||||
>
|
||||
@error('servico_interesse')
|
||||
@@ -198,7 +198,7 @@
|
||||
rows="5"
|
||||
maxlength="3000"
|
||||
placeholder="O que já está definido? Em que ponto você precisa de ajuda?"
|
||||
class="textarea w-full min-h-[110px] text-amare-text placeholder:text-amare-muted/60 @error('mensagem') textarea-error @enderror"
|
||||
class="textarea field-editorial w-full min-h-[110px] text-amare-text placeholder:text-amare-muted/60 @error('mensagem') textarea-error @enderror"
|
||||
@error('mensagem') aria-invalid="true" aria-describedby="briefing-mensagem-error" @enderror
|
||||
>{{ old('mensagem') }}</textarea>
|
||||
@error('mensagem')
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
@if (filled($settings->hero_secondary_cta_label))
|
||||
<a href="{{ route('portfolio.index') }}" class="btn btn-ghost min-h-0 px-0 text-xs font-bold uppercase tracking-[0.09em] text-amare-accent hover:bg-transparent hover:text-amare-accent-deep">
|
||||
<span class="border-b border-amare-accent pb-1">{{ $settings->hero_secondary_cta_label }}</span>
|
||||
<span class="link-editorial">{{ $settings->hero_secondary_cta_label }}</span>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
])
|
||||
|
||||
@php
|
||||
$orientationLink = \App\Support\PackageContactLink::forOrientation($settings);
|
||||
$bandHref = $bandCtaHref ?? $orientationLink['href'];
|
||||
$bandIsWhatsapp = $bandCtaHref === null && $orientationLink['isWhatsapp'];
|
||||
$bandHref = $bandCtaHref ?? route('briefing');
|
||||
@endphp
|
||||
<section
|
||||
aria-labelledby="packages-heading"
|
||||
@@ -66,7 +64,7 @@
|
||||
href="{{ route('packages.show', $package->slug) }}"
|
||||
class="btn btn-ghost min-h-0 px-0 text-xs font-semibold uppercase tracking-[0.09em] text-amare-accent hover:bg-transparent hover:text-amare-accent-deep"
|
||||
>
|
||||
<span class="border-b border-amare-accent pb-1">Conhecer esta modalidade</span>
|
||||
<span class="link-editorial">Conhecer esta modalidade</span>
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
@@ -79,11 +77,7 @@
|
||||
<h3 class="text-[clamp(1.5625rem,2.7vw,2.125rem)] font-medium leading-tight">Ainda não sabe qual modalidade é ideal?</h3>
|
||||
<p class="text-amare-accent-text/80">{{ $bandBody }}</p>
|
||||
</div>
|
||||
<a
|
||||
href="{{ $bandHref }}"
|
||||
@if ($bandIsWhatsapp) target="_blank" rel="noopener noreferrer" @endif
|
||||
class="inline-flex min-h-[48px] items-center justify-center self-start border border-amare-accent-text px-6 text-xs font-bold uppercase tracking-[0.09em] text-amare-accent-text transition-colors hover:bg-amare-accent-text hover:text-amare-accent-deep md:self-auto"
|
||||
>
|
||||
<a href="{{ $bandHref }}" class="inline-flex min-h-[48px] items-center justify-center self-start border border-amare-accent-text px-6 text-xs font-bold uppercase tracking-[0.09em] text-amare-accent-text transition-colors hover:bg-amare-accent-text hover:text-amare-accent-deep md:self-auto">
|
||||
Conversar com a Amare
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</ul>
|
||||
<p>
|
||||
<a href="{{ route('about') }}" class="btn btn-ghost min-h-0 px-0 text-sm font-semibold text-amare-accent hover:bg-transparent hover:text-amare-accent-deep">
|
||||
<span class="border-b border-amare-accent pb-1">Conhecer a Amare</span>
|
||||
<span class="link-editorial">Conhecer a Amare</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
<p>
|
||||
<a href="{{ route('services.index') }}" class="btn btn-ghost min-h-0 px-0 text-sm font-semibold text-amare-accent hover:bg-transparent hover:text-amare-accent-deep">
|
||||
<span class="border-b border-amare-accent pb-1">Ver todos os serviços</span>
|
||||
<span class="link-editorial">Ver todos os serviços</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<ol class="grid gap-8 border-t border-amare-border pt-6 md:grid-cols-3">
|
||||
@foreach ($packages as $package)
|
||||
@php($contactLink = \App\Support\PackageContactLink::for($settings, $package->name))
|
||||
<li class="space-y-5 border-t border-amare-border pt-4" data-reveal data-reveal-from="up"><p class="text-xs font-semibold uppercase tracking-[.14em] text-amare-accent">{{ $package->level }}</p><h3 class="text-3xl font-medium">{{ $package->name }}</h3><p class="text-amare-muted">{{ $package->summary }}</p><ul class="space-y-2 text-sm text-amare-text-muted">@foreach ($package->scope_items as $item)<li class="border-l border-amare-border pl-3">{{ $item }}</li>@endforeach</ul><div class="space-y-2"><a href="{{ $contactLink['href'] }}" @if ($contactLink['isWhatsapp']) target="_blank" rel="noopener" @endif class="btn btn-ghost min-h-0 px-0 text-sm font-semibold text-amare-accent hover:bg-transparent hover:text-amare-accent-deep"><span class="border-b border-amare-accent pb-1">{{ $package->cta_label }}</span></a><a href="{{ route('packages.show', $package->slug) }}" class="btn btn-ghost min-h-0 px-0 text-sm font-semibold text-amare-accent hover:bg-transparent hover:text-amare-accent-deep"><span class="border-b border-amare-accent pb-1">Conhecer esta modalidade</span></a></div></li>
|
||||
<li class="space-y-5 border-t border-amare-border pt-4" data-reveal data-reveal-from="up"><p class="text-xs font-semibold uppercase tracking-[.14em] text-amare-accent">{{ $package->level }}</p><h3 class="text-3xl font-medium">{{ $package->name }}</h3><p class="text-amare-muted">{{ $package->summary }}</p><ul class="space-y-2 text-sm text-amare-text-muted">@foreach ($package->scope_items as $item)<li class="border-l border-amare-border pl-3">{{ $item }}</li>@endforeach</ul><div class="space-y-2"><a href="{{ $contactLink['href'] }}" @if ($contactLink['isWhatsapp']) target="_blank" rel="noopener" @endif class="btn btn-ghost min-h-0 px-0 text-sm font-semibold text-amare-accent hover:bg-transparent hover:text-amare-accent-deep"><span class="link-editorial">{{ $package->cta_label }}</span></a><a href="{{ route('packages.show', $package->slug) }}" class="btn btn-ghost min-h-0 px-0 text-sm font-semibold text-amare-accent hover:bg-transparent hover:text-amare-accent-deep"><span class="link-editorial">Conhecer esta modalidade</span></a></div></li>
|
||||
@endforeach
|
||||
</ol>
|
||||
@endif
|
||||
|
||||
44
resources/views/components/public/form-success.blade.php
Normal file
44
resources/views/components/public/form-success.blade.php
Normal file
@@ -0,0 +1,44 @@
|
||||
@props([
|
||||
'seal' => 'Recebido',
|
||||
'title',
|
||||
'body',
|
||||
'homeLabel' => 'Voltar para a home',
|
||||
'whatsappLabel' => 'Falar no WhatsApp',
|
||||
'whatsappText' => 'Olá, enviei um briefing e gostaria de acompanhar.',
|
||||
])
|
||||
|
||||
@php
|
||||
$settings = $siteSettings ?? \App\Models\SiteSetting::instance();
|
||||
$digits = preg_replace('/\D/', '', (string) ($settings->whatsapp_number ?? ''));
|
||||
$whatsappHref = strlen($digits) >= 10
|
||||
? 'https://wa.me/'.$digits.'?text='.rawurlencode($whatsappText)
|
||||
: null;
|
||||
@endphp
|
||||
|
||||
<div
|
||||
role="status"
|
||||
data-form-success
|
||||
class="mt-10 max-w-2xl space-y-6 border border-amare-border bg-amare-bg-deep px-6 py-10 md:px-10"
|
||||
data-motion="page-open"
|
||||
{{ $attributes }}
|
||||
>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.14em] text-amare-accent" data-motion-beat="seal">{{ $seal }}</p>
|
||||
<h3 class="text-3xl font-medium tracking-tight text-amare-text md:text-4xl" data-motion-beat="title">{{ $title }}</h3>
|
||||
<p class="max-w-xl text-lg leading-relaxed text-amare-muted" data-motion-beat="heading">{{ $body }}</p>
|
||||
|
||||
<div class="flex flex-col items-start gap-4 pt-2 sm:flex-row sm:items-center sm:gap-8" data-motion-beat="cta">
|
||||
<a href="{{ route('home') }}" class="btn btn-primary min-h-11 px-6 text-xs font-bold uppercase tracking-[0.09em]">
|
||||
{{ $homeLabel }}
|
||||
</a>
|
||||
@if ($whatsappHref)
|
||||
<a
|
||||
href="{{ $whatsappHref }}"
|
||||
class="btn btn-ghost min-h-0 px-0 text-xs font-bold uppercase tracking-[0.09em] text-amare-accent hover:bg-transparent hover:text-amare-accent-deep"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<span class="link-editorial">{{ $whatsappLabel }}</span>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -5,7 +5,7 @@
|
||||
} catch (\Throwable $e) {
|
||||
$pageMeta = new \App\Application\Data\PageMeta(
|
||||
title: 'Página não encontrada - Amare Assessoria',
|
||||
description: 'A página que você procura não existe ou foi movida.',
|
||||
description: 'Não encontramos este endereço. Volte à home e continue por ali.',
|
||||
canonical: '',
|
||||
robots: 'noindex, nofollow',
|
||||
);
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="container-amare max-w-2xl space-y-6 py-20 text-center" data-motion="page-open" data-reveal-group>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.14em] text-amare-accent" data-motion-beat="seal">Erro 404</p>
|
||||
<h1 class="text-4xl font-medium tracking-tight text-amare-text" data-motion-beat="title">Página não encontrada</h1>
|
||||
<p class="text-amare-muted" data-reveal data-reveal-from="up">O endereço que você tentou abrir não existe ou foi movido.</p>
|
||||
<p class="text-amare-muted" data-reveal data-reveal-from="up">Não encontramos este endereço — talvez tenha mudado de lugar. A home continua sendo o melhor ponto de partida.</p>
|
||||
<p data-reveal data-reveal-from="up">
|
||||
<a href="{{ route('home') }}" class="btn btn-primary text-sm font-semibold uppercase tracking-[0.12em]">
|
||||
Voltar para a home
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
} catch (\Throwable $e) {
|
||||
$pageMeta = new \App\Application\Data\PageMeta(
|
||||
title: 'Sessão expirada - Amare Assessoria',
|
||||
description: 'Sua sessão expirou. Volte e tente enviar novamente.',
|
||||
description: 'Sua sessão ficou quieta demais. Volte e envie de novo com calma.',
|
||||
canonical: '',
|
||||
robots: 'noindex, nofollow',
|
||||
);
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="container-amare max-w-2xl space-y-6 py-20 text-center" data-motion="page-open" data-reveal-group>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.14em] text-amare-accent" data-motion-beat="seal">Erro 419</p>
|
||||
<h1 class="text-4xl font-medium tracking-tight text-amare-text" data-motion-beat="title">Sessão expirada</h1>
|
||||
<p class="text-amare-muted" data-reveal data-reveal-from="up">Sua sessão expirou. Volte e tente enviar novamente.</p>
|
||||
<p class="text-amare-muted" data-reveal data-reveal-from="up">Sua sessão ficou quieta demais e precisamos recomeçar. Volte à página anterior e envie de novo — os dados ainda estão seguros.</p>
|
||||
<p data-reveal data-reveal-from="up">
|
||||
<a href="{{ route('home') }}" class="btn btn-primary text-sm font-semibold uppercase tracking-[0.12em]">
|
||||
Voltar para a home
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
} catch (\Throwable $e) {
|
||||
$pageMeta = new \App\Application\Data\PageMeta(
|
||||
title: 'Muitas solicitações - Amare Assessoria',
|
||||
description: 'Você enviou muitas solicitações em pouco tempo. Aguarde um instante e tente novamente.',
|
||||
description: 'Recebemos muitos pedidos em pouco tempo. Aguarde um instante e tente de novo.',
|
||||
canonical: '',
|
||||
robots: 'noindex, nofollow',
|
||||
);
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="container-amare max-w-2xl space-y-6 py-20 text-center" data-motion="page-open" data-reveal-group>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.14em] text-amare-accent" data-motion-beat="seal">Erro 429</p>
|
||||
<h1 class="text-4xl font-medium tracking-tight text-amare-text" data-motion-beat="title">Muitas solicitações</h1>
|
||||
<p class="text-amare-muted" data-reveal data-reveal-from="up">Você enviou muitas solicitações em pouco tempo. Aguarde um instante e tente novamente.</p>
|
||||
<p class="text-amare-muted" data-reveal data-reveal-from="up">Recebemos vários pedidos em pouco tempo. Espere um instante — assim que pudermos, tentamos de novo com calma.</p>
|
||||
<p data-reveal data-reveal-from="up">
|
||||
<a href="{{ route('home') }}" class="btn btn-primary text-sm font-semibold uppercase tracking-[0.12em]">
|
||||
Voltar para a home
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
} catch (\Throwable $e) {
|
||||
$pageMeta = new \App\Application\Data\PageMeta(
|
||||
title: 'Algo deu errado - Amare Assessoria',
|
||||
description: 'Não foi possível concluir o pedido. Tente novamente em instantes.',
|
||||
description: 'Algo saiu do roteiro do nosso lado. Tente novamente em instantes.',
|
||||
canonical: '',
|
||||
robots: 'noindex, nofollow',
|
||||
);
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="container-amare max-w-2xl space-y-6 py-20 text-center" data-motion="page-open" data-reveal-group>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.14em] text-amare-accent" data-motion-beat="seal">Erro 500</p>
|
||||
<h1 class="text-4xl font-medium tracking-tight text-amare-text" data-motion-beat="title">Algo deu errado</h1>
|
||||
<p class="text-amare-muted" data-reveal data-reveal-from="up">Não foi possível concluir o pedido agora. Tente novamente em instantes.</p>
|
||||
<p class="text-amare-muted" data-reveal data-reveal-from="up">Algo saiu do roteiro do nosso lado. Já estamos cuidando disso — tente novamente em instantes.</p>
|
||||
<p data-reveal data-reveal-from="up">
|
||||
<a href="{{ route('home') }}" class="btn btn-primary text-sm font-semibold uppercase tracking-[0.12em]">
|
||||
Voltar para a home
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
} catch (\Throwable $e) {
|
||||
$pageMeta = new \App\Application\Data\PageMeta(
|
||||
title: 'Em manutenção - Amare Assessoria',
|
||||
description: 'Estamos realizando uma breve manutenção. Tente novamente em instantes.',
|
||||
description: 'Estamos em uma pausa breve de manutenção. Voltamos em instantes.',
|
||||
canonical: '',
|
||||
robots: 'noindex, nofollow',
|
||||
);
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="container-amare max-w-2xl space-y-6 py-20 text-center" data-motion="page-open" data-reveal-group>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.14em] text-amare-accent" data-motion-beat="seal">Erro 503</p>
|
||||
<h1 class="text-4xl font-medium tracking-tight text-amare-text" data-motion-beat="title">Em manutenção</h1>
|
||||
<p class="text-amare-muted" data-reveal data-reveal-from="up">Estamos realizando uma breve manutenção. Tente novamente em instantes.</p>
|
||||
<p class="text-amare-muted" data-reveal data-reveal-from="up">Estamos em uma pausa breve para deixar tudo no lugar. Voltamos em instantes — obrigado pela paciência.</p>
|
||||
<p data-reveal data-reveal-from="up">
|
||||
<a href="{{ route('home') }}" class="btn btn-primary text-sm font-semibold uppercase tracking-[0.12em]">
|
||||
Voltar para a home
|
||||
|
||||
@@ -58,10 +58,10 @@
|
||||
<header class="site-header sticky top-0 z-40 border-b border-amare-border bg-amare-bg/95 backdrop-blur-sm">
|
||||
<div class="container-amare grid grid-cols-[auto_1fr_auto] items-center gap-4 py-4 md:grid-cols-[1fr_auto_1fr] md:h-[74px] md:py-0">
|
||||
<nav id="main-nav" class="main-nav order-3 col-span-3 hidden flex-col gap-4 border-t border-amare-border pt-4 md:order-1 md:col-span-1 md:flex md:flex-row md:items-center md:gap-1 md:border-0 md:pt-0" aria-label="Principal" data-main-nav>
|
||||
<a href="{{ url('/#casamentos') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Casamentos</a>
|
||||
<a href="{{ url('/#corporate') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Corporate</a>
|
||||
<a href="{{ route('portfolio.index') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Portfólio</a>
|
||||
<a href="{{ route('about') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Amare</a>
|
||||
<a href="{{ url('/#casamentos') }}" class="nav-link-editorial btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:bg-transparent hover:text-amare-accent md:px-3">Casamentos</a>
|
||||
<a href="{{ url('/#corporate') }}" class="nav-link-editorial btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:bg-transparent hover:text-amare-accent md:px-3">Corporate</a>
|
||||
<a href="{{ route('portfolio.index') }}" class="nav-link-editorial btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:bg-transparent hover:text-amare-accent md:px-3">Portfólio</a>
|
||||
<a href="{{ route('about') }}" class="nav-link-editorial btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:bg-transparent hover:text-amare-accent md:px-3">Amare</a>
|
||||
<a href="{{ route('briefing') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:hidden">Solicitar proposta</a>
|
||||
</nav>
|
||||
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
</p>
|
||||
|
||||
@if (session('status') === 'briefing-sent')
|
||||
<div role="status" class="alert mt-8 border border-amare-border bg-amare-bg-deep">
|
||||
<p class="font-semibold text-amare-text">Mensagem enviada.</p>
|
||||
<p class="mt-1 text-amare-muted">Recebemos seu briefing e retornaremos em breve pelo canal informado.</p>
|
||||
</div>
|
||||
<x-public.form-success
|
||||
seal="Briefing recebido"
|
||||
title="Recebemos sua ficha."
|
||||
body="Vamos ler com atenção e retornar em breve pelo canal que você informou. Enquanto isso, pode seguir explorando o portfólio."
|
||||
/>
|
||||
@else
|
||||
<form
|
||||
method="POST"
|
||||
@@ -79,7 +80,7 @@
|
||||
autocomplete="name"
|
||||
maxlength="120"
|
||||
placeholder="Seu nome"
|
||||
class="input w-full text-amare-text placeholder:text-amare-muted/60 @error('nome') input-error @enderror"
|
||||
class="input field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('nome') input-error @enderror"
|
||||
@error('nome') aria-invalid="true" aria-describedby="nome-error" @enderror
|
||||
>
|
||||
@error('nome')
|
||||
@@ -98,7 +99,7 @@
|
||||
autocomplete="email"
|
||||
maxlength="254"
|
||||
placeholder="voce@email.com"
|
||||
class="input w-full text-amare-text placeholder:text-amare-muted/60 @error('email') input-error @enderror"
|
||||
class="input field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('email') input-error @enderror"
|
||||
@error('email') aria-invalid="true" aria-describedby="email-error" @enderror
|
||||
>
|
||||
@error('email')
|
||||
@@ -117,7 +118,7 @@
|
||||
autocomplete="tel"
|
||||
maxlength="40"
|
||||
placeholder="(11) 90000-0000"
|
||||
class="input w-full text-amare-text placeholder:text-amare-muted/60 @error('telefone') input-error @enderror"
|
||||
class="input field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('telefone') input-error @enderror"
|
||||
@error('telefone') aria-invalid="true" aria-describedby="telefone-error" @enderror
|
||||
>
|
||||
@error('telefone')
|
||||
@@ -131,7 +132,7 @@
|
||||
id="tipo_evento"
|
||||
name="tipo_evento"
|
||||
required
|
||||
class="select w-full text-amare-text @error('tipo_evento') select-error @enderror"
|
||||
class="select field-editorial w-full text-amare-text @error('tipo_evento') select-error @enderror"
|
||||
@error('tipo_evento') aria-invalid="true" aria-describedby="tipo_evento-error" @enderror
|
||||
>
|
||||
<option value="" selected disabled>Selecione...</option>
|
||||
@@ -154,7 +155,7 @@
|
||||
value="{{ old('data_periodo') }}"
|
||||
maxlength="80"
|
||||
placeholder="Ex.: novembro de 2027"
|
||||
class="input w-full text-amare-text placeholder:text-amare-muted/60 @error('data_periodo') input-error @enderror"
|
||||
class="input field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('data_periodo') input-error @enderror"
|
||||
@error('data_periodo') aria-invalid="true" aria-describedby="data_periodo-error" @enderror
|
||||
>
|
||||
@error('data_periodo')
|
||||
@@ -172,7 +173,7 @@
|
||||
required
|
||||
maxlength="80"
|
||||
placeholder="Ex.: São Paulo"
|
||||
class="input w-full text-amare-text placeholder:text-amare-muted/60 @error('cidade') input-error @enderror"
|
||||
class="input field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('cidade') input-error @enderror"
|
||||
@error('cidade') aria-invalid="true" aria-describedby="cidade-error" @enderror
|
||||
>
|
||||
@error('cidade')
|
||||
@@ -192,7 +193,7 @@
|
||||
inputmode="numeric"
|
||||
autocomplete="off"
|
||||
placeholder="Ex.: 120"
|
||||
class="input w-full text-amare-text placeholder:text-amare-muted/60 @error('convidados') input-error @enderror"
|
||||
class="input field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('convidados') input-error @enderror"
|
||||
@error('convidados') aria-invalid="true" aria-describedby="convidados-error" @enderror
|
||||
>
|
||||
@error('convidados')
|
||||
@@ -209,7 +210,7 @@
|
||||
value="{{ old('servico_interesse', request('servico_interesse')) }}"
|
||||
maxlength="120"
|
||||
placeholder="Ex.: planejamento completo"
|
||||
class="input w-full text-amare-text placeholder:text-amare-muted/60 @error('servico_interesse') input-error @enderror"
|
||||
class="input field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('servico_interesse') input-error @enderror"
|
||||
@error('servico_interesse') aria-invalid="true" aria-describedby="servico_interesse-error" @enderror
|
||||
>
|
||||
@error('servico_interesse')
|
||||
@@ -227,7 +228,7 @@
|
||||
rows="6"
|
||||
maxlength="3000"
|
||||
placeholder="Conte sobre o seu evento, expectativas e principais preocupações."
|
||||
class="textarea w-full text-amare-text placeholder:text-amare-muted/60 @error('mensagem') textarea-error @enderror"
|
||||
class="textarea field-editorial w-full text-amare-text placeholder:text-amare-muted/60 @error('mensagem') textarea-error @enderror"
|
||||
@error('mensagem') aria-invalid="true" aria-describedby="mensagem-error" @enderror
|
||||
>{{ old('mensagem') }}</textarea>
|
||||
@error('mensagem')
|
||||
|
||||
@@ -20,7 +20,12 @@
|
||||
<div class="container-amare py-16 md:py-24" data-reveal-group>
|
||||
<h2 id="partner-form-heading" class="text-3xl font-medium text-amare-text" data-reveal data-reveal-from="up">Apresente seu trabalho</h2>
|
||||
@if (session('status') === 'partner-inquiry-sent')
|
||||
<div role="status" class="mt-8 border border-amare-border bg-amare-bg px-6 py-5"><p class="font-semibold">Mensagem enviada.</p><p class="mt-1 text-amare-muted">Recebemos sua apresentação e entraremos em contato se houver aderência.</p></div>
|
||||
<x-public.form-success
|
||||
seal="Apresentação recebida"
|
||||
title="Recebemos sua apresentação."
|
||||
body="Vamos avaliar a aderência com o cuidado que cada parceria merece. Se fizer sentido, entraremos em contato."
|
||||
whatsapp-text="Olá, enviei uma apresentação de parceria e gostaria de acompanhar."
|
||||
/>
|
||||
@else
|
||||
<form method="POST" action="{{ route('contact.store') }}" class="mt-10 max-w-3xl space-y-8" data-contact-form data-reveal data-reveal-from="up">
|
||||
@csrf
|
||||
@@ -28,10 +33,10 @@
|
||||
@if ($errors->any())<div role="alert" class="border border-amare-error/40 px-6 py-5">Revise os campos destacados abaixo.</div>@endif
|
||||
<div class="grid gap-8 sm:grid-cols-2">
|
||||
@foreach (['nome' => 'Nome *', 'empresa' => 'Nome profissional ou empresa', 'email' => 'E-mail *', 'atuacao' => 'Atuação ou serviço *', 'area_atendimento' => 'Área de atendimento *', 'portfolio_redes' => 'Portfólio ou redes', 'telefone' => 'Telefone'] as $field => $label)
|
||||
<div><label class="mb-2 block text-xs font-semibold uppercase tracking-[.14em]" for="{{ $field }}">{{ $label }}</label><input class="w-full border-0 border-b border-amare-border bg-transparent py-3 focus:border-amare-accent focus:outline-none @error($field) border-amare-error @enderror" id="{{ $field }}" name="{{ $field }}" value="{{ old($field) }}" maxlength="{{ $field === 'portfolio_redes' ? 500 : ($field === 'email' ? 254 : 160) }}" @required(in_array($field, ['nome','email','atuacao','area_atendimento'], true)) @error($field) aria-invalid="true" @enderror>@error($field)<p class="mt-2 text-sm text-amare-error">{{ $message }}</p>@enderror</div>
|
||||
<div><label class="mb-2 block text-xs font-semibold uppercase tracking-[.14em]" for="{{ $field }}">{{ $label }}</label><input class="field-editorial w-full border-0 border-b border-amare-border bg-transparent py-3 focus:border-amare-accent focus:outline-none @error($field) border-amare-error @enderror" id="{{ $field }}" name="{{ $field }}" value="{{ old($field) }}" maxlength="{{ $field === 'portfolio_redes' ? 500 : ($field === 'email' ? 254 : 160) }}" @required(in_array($field, ['nome','email','atuacao','area_atendimento'], true)) @error($field) aria-invalid="true" @enderror>@error($field)<p class="mt-2 text-sm text-amare-error">{{ $message }}</p>@enderror</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<div><label class="mb-2 block text-xs font-semibold uppercase tracking-[.14em]" for="mensagem">Mensagem *</label><textarea class="w-full border border-amare-border bg-transparent p-3 focus:border-amare-accent focus:outline-none" id="mensagem" name="mensagem" rows="6" required maxlength="3000">{{ old('mensagem') }}</textarea>@error('mensagem')<p class="mt-2 text-sm text-amare-error">{{ $message }}</p>@enderror</div>
|
||||
<div><label class="mb-2 block text-xs font-semibold uppercase tracking-[.14em]" for="mensagem">Mensagem *</label><textarea class="field-editorial w-full border border-amare-border bg-transparent p-3 focus:border-amare-accent focus:outline-none" id="mensagem" name="mensagem" rows="6" required maxlength="3000">{{ old('mensagem') }}</textarea>@error('mensagem')<p class="mt-2 text-sm text-amare-error">{{ $message }}</p>@enderror</div>
|
||||
<p class="max-w-2xl text-sm text-amare-muted">Ao enviar, você reconhece o uso destes dados exclusivamente para analisar uma possível parceria, conforme a <a class="text-amare-accent underline" href="{{ route('privacy') }}">política de privacidade</a>.</p>
|
||||
<button type="submit" data-submit-button class="btn btn-primary text-sm font-semibold uppercase tracking-[.12em]">Enviar apresentação</button>
|
||||
</form>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
Conhecer os serviços
|
||||
</a>
|
||||
<a href="{{ route('contact') }}" class="btn btn-ghost min-h-0 px-0 text-xs font-bold uppercase tracking-[0.09em] text-amare-accent hover:bg-transparent hover:text-amare-accent-deep">
|
||||
<span class="border-b border-amare-accent pb-1">Tenho um evento em mente</span>
|
||||
<span class="link-editorial">Tenho um evento em mente</span>
|
||||
</a>
|
||||
</div>
|
||||
</x-public.photo-hero>
|
||||
@@ -47,6 +47,7 @@
|
||||
:show-intro="false"
|
||||
kicker="tag"
|
||||
:show-subtitle="true"
|
||||
:band-cta-href="route('briefing')"
|
||||
band-body="Conte um pouco sobre o casamento. A Amare entende o momento de vocês e orienta o melhor formato de acompanhamento sem depender de um quiz automático."
|
||||
:show-note="false"
|
||||
/>
|
||||
@@ -64,7 +65,7 @@
|
||||
<p class="text-amare-text-muted">{{ $package->compare_summary }}</p>
|
||||
</div>
|
||||
<a href="{{ route('packages.show', $package->slug) }}" class="btn btn-ghost min-h-0 self-start px-0 text-xs font-semibold uppercase tracking-[0.09em] text-amare-accent hover:bg-transparent hover:text-amare-accent-deep">
|
||||
<span class="border-b border-amare-accent pb-1">Conhecer esta modalidade</span>
|
||||
<span class="link-editorial">Conhecer esta modalidade</span>
|
||||
</a>
|
||||
</article>
|
||||
@endif
|
||||
|
||||
@@ -89,6 +89,28 @@ class ContactBriefingTest extends TestCase
|
||||
});
|
||||
}
|
||||
|
||||
public function test_successful_submission_shows_editorial_confirmation(): void
|
||||
{
|
||||
SiteSetting::instance()->update(['whatsapp_number' => '+55 11 98888-7777']);
|
||||
Mail::fake();
|
||||
|
||||
$response = $this
|
||||
->from(route('briefing'))
|
||||
->followingRedirects()
|
||||
->post(route('briefing.store'), $this->validPayload());
|
||||
|
||||
$response
|
||||
->assertOk()
|
||||
->assertSee('data-form-success', false)
|
||||
->assertSee('Briefing recebido')
|
||||
->assertSee('Recebemos sua ficha')
|
||||
->assertSee('role="status"', false)
|
||||
->assertSee('data-motion-beat="seal"', false)
|
||||
->assertSee(route('home'), false)
|
||||
->assertSee('https://wa.me/5511988887777', false)
|
||||
->assertDontSee('<form', false);
|
||||
}
|
||||
|
||||
public function test_submission_normalizes_email_and_phone_before_reaching_mailables(): void
|
||||
{
|
||||
$settings = SiteSetting::instance();
|
||||
|
||||
@@ -72,7 +72,19 @@ class HeritageEditorialTokensTest extends TestCase
|
||||
{
|
||||
$vite = (string) file_get_contents(base_path('vite.config.js'));
|
||||
|
||||
$this->assertStringContainsString("bunny('EB Garamond'", $vite);
|
||||
$this->assertMatchesRegularExpression(
|
||||
"/^import \\{ fontsource \\} from 'laravel-vite-plugin\\/fonts';/m",
|
||||
$vite,
|
||||
);
|
||||
$this->assertDoesNotMatchRegularExpression(
|
||||
'/^import \\{[^}]*\\bbunny\\b/m',
|
||||
$vite,
|
||||
);
|
||||
$this->assertDoesNotMatchRegularExpression(
|
||||
'/^import \\{[^}]*\\bgoogle\\b/m',
|
||||
$vite,
|
||||
);
|
||||
$this->assertStringContainsString("fontsource('EB Garamond'", $vite);
|
||||
$this->assertStringNotContainsString('Instrument Sans', $vite);
|
||||
}
|
||||
|
||||
|
||||
@@ -213,26 +213,7 @@ class HomePageContentTest extends TestCase
|
||||
false,
|
||||
)
|
||||
->assertSee('target="_blank"', false)
|
||||
->assertDontSee(route('briefing', ['servico_interesse' => 'Grand Jour']), false)
|
||||
->assertSee(
|
||||
'https://wa.me/5511988887777?text='.rawurlencode('Olá, ainda não sei qual modalidade de acompanhamento combina com o meu casamento e gostaria de orientação da Amare.'),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
public function test_orientation_cta_falls_back_to_briefing_without_whatsapp_number(): void
|
||||
{
|
||||
SiteSetting::instance()->update(['whatsapp_number' => null]);
|
||||
|
||||
$response = $this->get(route('home'));
|
||||
|
||||
$response
|
||||
->assertOk()
|
||||
->assertSee('href="'.route('briefing').'"', false)
|
||||
->assertDontSee(
|
||||
'Olá, ainda não sei qual modalidade de acompanhamento combina com o meu casamento e gostaria de orientação da Amare.',
|
||||
false,
|
||||
);
|
||||
->assertDontSee(route('briefing', ['servico_interesse' => 'Grand Jour']), false);
|
||||
}
|
||||
|
||||
public function test_corporate_steps_and_placeholder_render_from_settings(): void
|
||||
|
||||
@@ -96,6 +96,9 @@ class MotionMarkupTest extends TestCase
|
||||
$this->assertStringContainsString('[data-reveal]', $appCss);
|
||||
$this->assertStringNotContainsString('[data-chapter-index]', $appCss);
|
||||
$this->assertStringContainsString('prefers-reduced-motion: no-preference', $appCss);
|
||||
$this->assertStringContainsString('.field-editorial', $appCss);
|
||||
$this->assertStringContainsString('.nav-link-editorial', $appCss);
|
||||
$this->assertStringContainsString('.link-editorial', $appCss);
|
||||
|
||||
// Entrance motion must not fade text opacity — mid-fade fails WCAG contrast (axe).
|
||||
$this->assertDoesNotMatchRegularExpression(
|
||||
|
||||
@@ -66,6 +66,22 @@ class PartnerInquiryTest extends TestCase
|
||||
Mail::assertQueued(PartnerInquiry::class, 1);
|
||||
}
|
||||
|
||||
public function test_successful_partner_inquiry_shows_editorial_confirmation(): void
|
||||
{
|
||||
SiteSetting::instance();
|
||||
Mail::fake();
|
||||
|
||||
$this
|
||||
->from(route('contact'))
|
||||
->followingRedirects()
|
||||
->post(route('contact.store'), $this->validPayload())
|
||||
->assertOk()
|
||||
->assertSee('data-form-success', false)
|
||||
->assertSee('Apresentação recebida')
|
||||
->assertSee('Recebemos sua apresentação')
|
||||
->assertDontSee('name="atuacao"', false);
|
||||
}
|
||||
|
||||
public function test_partner_honeypot_reports_success_without_sending_mail(): void
|
||||
{
|
||||
SiteSetting::instance();
|
||||
|
||||
@@ -50,18 +50,4 @@ class ServicesPageCtaTest extends TestCase
|
||||
->assertSee('href="'.route('briefing', ['servico_interesse' => 'Essenza']).'"', false)
|
||||
->assertDontSee('wa.me/', false);
|
||||
}
|
||||
|
||||
public function test_orientation_cta_uses_distinct_whatsapp_message_when_a_number_is_configured(): void
|
||||
{
|
||||
SiteSetting::instance()->update(['whatsapp_number' => '+55 11 98888-7777']);
|
||||
|
||||
$response = $this->get(route('services.index'));
|
||||
|
||||
$response
|
||||
->assertOk()
|
||||
->assertSee(
|
||||
'https://wa.me/5511988887777?text='.rawurlencode('Olá, ainda não sei qual modalidade de acompanhamento combina com o meu casamento e gostaria de orientação da Amare.'),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import laravel from 'laravel-vite-plugin';
|
||||
import { bunny } from 'laravel-vite-plugin/fonts';
|
||||
import { fontsource } from 'laravel-vite-plugin/fonts';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
/**
|
||||
@@ -76,7 +76,7 @@ export default defineConfig({
|
||||
],
|
||||
refresh: true,
|
||||
fonts: [
|
||||
bunny('EB Garamond', {
|
||||
fontsource('EB Garamond', {
|
||||
weights: [400, 500, 600],
|
||||
}),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user