Compare commits
2 Commits
feat/setup
...
fix/pr23-c
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ae582455c | |||
| 75ab0b0ff5 |
14
AGENTS.md
14
AGENTS.md
@@ -41,17 +41,3 @@ History follows Conventional Commit-style subjects, for example `feat: Fase 0
|
|||||||
## Security & Configuration
|
## Security & Configuration
|
||||||
|
|
||||||
Copy `.env.example`; never commit secrets or production credentials. Development seed credentials are local-only. Validate uploads and authorization through Laravel policies, and run `composer security-audit` after dependency changes.
|
Copy `.env.example`; never commit secrets or production credentials. Development seed credentials are local-only. Validate uploads and authorization through Laravel policies, and run `composer security-audit` after dependency changes.
|
||||||
|
|
||||||
## Agent skills
|
|
||||||
|
|
||||||
### Issue tracker
|
|
||||||
|
|
||||||
Issues live in Linear, driven through the Linear MCP tools. See `docs/agents/issue-tracker.md`.
|
|
||||||
|
|
||||||
### Triage labels
|
|
||||||
|
|
||||||
Default vocabulary: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`. See `docs/agents/triage-labels.md`.
|
|
||||||
|
|
||||||
### Domain docs
|
|
||||||
|
|
||||||
Single-context: `CONTEXT.md` at root + `docs/adr/`. See `docs/agents/domain.md`.
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
# Domain Docs
|
|
||||||
|
|
||||||
How the engineering skills should consume this repo's domain documentation when exploring the codebase.
|
|
||||||
|
|
||||||
## Before exploring, read these
|
|
||||||
|
|
||||||
- **`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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## File structure
|
|
||||||
|
|
||||||
Single-context repo (most repos):
|
|
||||||
|
|
||||||
```
|
|
||||||
/
|
|
||||||
├── 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 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 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 your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
|
|
||||||
|
|
||||||
> _Contradicts ADR-0007 (event-sourced orders) — but worth reopening because…_
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# Issue tracker: Linear
|
|
||||||
|
|
||||||
Issues and specs for this repo live in Linear. Use the Linear MCP tools for all operations.
|
|
||||||
|
|
||||||
- **Workspace**: maneco-workspace (https://linear.app/maneco-workspace)
|
|
||||||
- **Team**: Maneco-workspace
|
|
||||||
- **Issue statuses**: Backlog, Todo, In Progress, Done, Canceled, Duplicate
|
|
||||||
|
|
||||||
## Conventions
|
|
||||||
|
|
||||||
- **Create an issue**: `save_issue` with `title`, `team` (`Maneco-workspace`), and markdown `description`. Assign via `assignee` ("me" for the current user).
|
|
||||||
- **Read an issue**: `get_issue` by identifier (e.g. `LIN-123`).
|
|
||||||
- **List issues**: `list_issues` filtered by `team`, `assignee`, `state`, or `project`.
|
|
||||||
- **Comment on an issue**: `save_comment` with `issueId` and markdown `body`.
|
|
||||||
- **Apply / remove labels**: `save_issue` with `labels` (replaces the full set).
|
|
||||||
- **Move workflow state**: `save_issue` with `state` (e.g. `Todo`, `In Progress`, `Done`, `Canceled`).
|
|
||||||
- **Link work**: `save_issue` with `project`, `cycle`, `parentId`, `blocks` / `blockedBy` (relations are append-only).
|
|
||||||
- **Cross-link to a PR**: attach the PR URL via `save_issue` `links`.
|
|
||||||
|
|
||||||
## When a skill says "publish to the issue tracker"
|
|
||||||
|
|
||||||
Create a Linear issue with `save_issue` on team `Maneco-workspace`.
|
|
||||||
|
|
||||||
## When a skill says "fetch the relevant ticket"
|
|
||||||
|
|
||||||
Run `get_issue` on the identifier and read the description, state, labels, and assignee.
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# Triage Labels
|
|
||||||
|
|
||||||
The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's issue tracker.
|
|
||||||
|
|
||||||
| Label in mattpocock/skills | Label in our tracker | Meaning |
|
|
||||||
| -------------------------- | -------------------- | ---------------------------------------- |
|
|
||||||
| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue |
|
|
||||||
| `needs-info` | `needs-info` | Waiting on reporter for more information |
|
|
||||||
| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent |
|
|
||||||
| `ready-for-human` | `ready-for-human` | Requires human implementation |
|
|
||||||
| `wontfix` | `wontfix` | Will not be actioned |
|
|
||||||
|
|
||||||
When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table.
|
|
||||||
|
|
||||||
Edit the right-hand column to match whatever vocabulary you actually use.
|
|
||||||
BIN
docs/screenshots/home-editorial-cadence-desktop.webp
Normal file
BIN
docs/screenshots/home-editorial-cadence-desktop.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 137 KiB |
BIN
docs/screenshots/home-editorial-cadence-mobile.webp
Normal file
BIN
docs/screenshots/home-editorial-cadence-mobile.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 192 KiB |
@@ -109,6 +109,43 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home-chapters {
|
||||||
|
counter-reset: home-chapter -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-chapter {
|
||||||
|
counter-increment: home-chapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-folio {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
color: var(--amare-color-accent);
|
||||||
|
font-size: var(--amare-text-xs);
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
line-height: 1;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-folio--inverse {
|
||||||
|
color: color-mix(in srgb, var(--amare-color-accent-text) 82%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-folio__separator {
|
||||||
|
color: var(--amare-color-sage);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-folio--inverse .home-folio__separator {
|
||||||
|
color: color-mix(in srgb, var(--amare-color-accent-text) 55%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-folio__number::before {
|
||||||
|
content: counter(home-chapter, decimal-leading-zero);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
.main-nav {
|
.main-nav {
|
||||||
transition: opacity var(--amare-duration-normal) var(--amare-ease-standard);
|
transition: opacity var(--amare-duration-normal) var(--amare-ease-standard);
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
@props([
|
@props([
|
||||||
'settings',
|
'settings',
|
||||||
|
'editorial' => false,
|
||||||
])
|
])
|
||||||
|
|
||||||
<section aria-labelledby="final-cta-heading" class="border-t border-amare-border bg-amare-bg-deep py-20" data-chapter="final-cta" data-reveal-group>
|
<section aria-labelledby="final-cta-heading" @class([
|
||||||
|
'border-t border-amare-border bg-amare-bg-deep py-20',
|
||||||
|
'home-chapter' => $editorial,
|
||||||
|
]) data-chapter="final-cta" data-reveal-group>
|
||||||
<div class="container-amare space-y-6 text-center" data-reveal data-reveal-from="up">
|
<div class="container-amare space-y-6 text-center" data-reveal data-reveal-from="up">
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.14em] text-amare-accent">Próximo passo</p>
|
@if ($editorial)
|
||||||
|
<x-home.folio label="Próximo passo" class="justify-center" />
|
||||||
|
@else
|
||||||
|
<p class="text-xs font-semibold uppercase tracking-[0.14em] text-amare-accent">Próximo passo</p>
|
||||||
|
@endif
|
||||||
<h2 id="final-cta-heading" class="text-headline font-medium text-amare-text">Do casamento ao evento corporativo, tudo começa com uma boa conversa.</h2>
|
<h2 id="final-cta-heading" class="text-headline font-medium text-amare-text">Do casamento ao evento corporativo, tudo começa com uma boa conversa.</h2>
|
||||||
<p class="mx-auto max-w-2xl text-amare-muted">
|
<p class="mx-auto max-w-2xl text-amare-muted">
|
||||||
Compartilhe as primeiras informações do seu evento. A Amare retorna para entender o contexto e orientar os próximos passos.
|
Compartilhe as primeiras informações do seu evento. A Amare retorna para entender o contexto e orientar os próximos passos.
|
||||||
|
|||||||
20
resources/views/components/home/folio.blade.php
Normal file
20
resources/views/components/home/folio.blade.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
@props([
|
||||||
|
'label',
|
||||||
|
'tone' => 'default',
|
||||||
|
])
|
||||||
|
|
||||||
|
@php
|
||||||
|
$tone = $tone === 'inverse' ? 'inverse' : 'default';
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<p
|
||||||
|
{{ $attributes->class([
|
||||||
|
'home-folio',
|
||||||
|
'home-folio--inverse' => $tone === 'inverse',
|
||||||
|
]) }}
|
||||||
|
data-home-folio
|
||||||
|
>
|
||||||
|
<span data-home-folio-label>{{ $label }}</span>
|
||||||
|
<span class="home-folio__separator" aria-hidden="true">·</span>
|
||||||
|
<span class="home-folio__number" data-home-folio-number aria-hidden="true"></span>
|
||||||
|
</p>
|
||||||
@@ -4,11 +4,15 @@
|
|||||||
|
|
||||||
<section
|
<section
|
||||||
aria-labelledby="hero-heading"
|
aria-labelledby="hero-heading"
|
||||||
class="border-b border-amare-border bg-amare-bg"
|
class="home-chapter border-b border-amare-border bg-amare-bg"
|
||||||
data-chapter="hero"
|
data-chapter="hero"
|
||||||
data-motion="page-open"
|
data-motion="page-open"
|
||||||
>
|
>
|
||||||
<div class="container-amare grid gap-12 py-20 md:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)] md:items-center md:py-28" data-reveal-group>
|
<div class="container-amare pt-8 md:pt-10">
|
||||||
|
<x-home.folio label="Capa" data-motion-beat="folio" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container-amare grid gap-12 pb-20 pt-10 md:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)] md:items-center md:pb-28 md:pt-14" data-reveal-group>
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<div data-motion-beat="seal" class="flex items-center gap-4">
|
<div data-motion-beat="seal" class="flex items-center gap-4">
|
||||||
<x-brand.logo mark variant="on-light" class="h-8 w-auto" alt="" />
|
<x-brand.logo mark variant="on-light" class="h-8 w-auto" alt="" />
|
||||||
|
|||||||
@@ -8,9 +8,13 @@
|
|||||||
$body = $settings->manifesto_body ?: 'A Amare combina sensibilidade e precisão para criar encontros coerentes com cada cliente, marca e ocasião — sem fórmulas prontas, excessos ou ruído.';
|
$body = $settings->manifesto_body ?: 'A Amare combina sensibilidade e precisão para criar encontros coerentes com cada cliente, marca e ocasião — sem fórmulas prontas, excessos ou ruído.';
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<section aria-labelledby="manifesto-heading" class="border-b border-amare-border bg-amare-bg-deep py-20" data-chapter="manifesto">
|
<section aria-labelledby="manifesto-heading" class="home-chapter border-b border-amare-border bg-amare-bg-deep py-20" data-chapter="manifesto">
|
||||||
<div class="container-amare grid gap-8 md:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]" data-reveal-group>
|
<div class="container-amare grid gap-8 md:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]" data-reveal-group>
|
||||||
<p class="text-sm font-medium uppercase tracking-[0.18em] text-amare-accent" data-reveal data-reveal-from="up">Manifesto</p>
|
<div class="space-y-5 md:pr-8" data-reveal data-reveal-from="up">
|
||||||
|
<x-home.folio label="Manifesto" />
|
||||||
|
<span class="block h-px w-16 bg-amare-border" aria-hidden="true"></span>
|
||||||
|
<p class="max-w-52 text-lg leading-snug text-amare-text">Humana no cuidado. Precisa na entrega.</p>
|
||||||
|
</div>
|
||||||
<div class="space-y-6" data-reveal data-reveal-from="up">
|
<div class="space-y-6" data-reveal data-reveal-from="up">
|
||||||
<h2 id="manifesto-heading" class="max-w-3xl text-headline font-medium text-amare-text">{{ $title }}</h2>
|
<h2 id="manifesto-heading" class="max-w-3xl text-headline font-medium text-amare-text">{{ $title }}</h2>
|
||||||
<p class="max-w-2xl text-xl leading-relaxed text-amare-text">{{ $lead }}</p>
|
<p class="max-w-2xl text-xl leading-relaxed text-amare-text">{{ $lead }}</p>
|
||||||
|
|||||||
@@ -7,12 +7,19 @@
|
|||||||
$intro = $settings->method_intro ?: 'Clareza em cada etapa. Tranquilidade durante todo o processo.';
|
$intro = $settings->method_intro ?: 'Clareza em cada etapa. Tranquilidade durante todo o processo.';
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<section aria-labelledby="method-heading" class="border-b border-amare-border bg-amare-bg-archive py-20" data-chapter="method">
|
<section aria-labelledby="method-heading" class="home-chapter border-b border-amare-border bg-amare-bg-archive py-20" data-chapter="method">
|
||||||
<div class="container-amare grid gap-10 md:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)] md:items-start" data-reveal-group>
|
<div class="container-amare grid gap-10 md:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)] md:items-start" data-reveal-group>
|
||||||
<div class="space-y-3" data-reveal data-reveal-from="up">
|
<div class="space-y-4" data-reveal data-reveal-from="up">
|
||||||
<p class="text-sm font-medium uppercase tracking-[0.18em] text-amare-accent">Método</p>
|
<x-home.folio label="Método" />
|
||||||
<h2 id="method-heading" class="text-headline font-medium text-amare-text">Cuidado orientado por processo.</h2>
|
<h2 id="method-heading" class="text-headline font-medium text-amare-text">Cuidado orientado por processo.</h2>
|
||||||
<p class="text-amare-text-muted">{{ $intro }}</p>
|
<p class="text-amare-text-muted">{{ $intro }}</p>
|
||||||
|
<x-brand.logo
|
||||||
|
mark
|
||||||
|
variant="on-light"
|
||||||
|
class="mt-8 h-8 w-auto opacity-60"
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ol class="grid gap-5 border-t border-amare-border">
|
<ol class="grid gap-5 border-t border-amare-border">
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
])
|
])
|
||||||
|
|
||||||
@if ($cases->isNotEmpty())
|
@if ($cases->isNotEmpty())
|
||||||
<section aria-labelledby="portfolio-heading" class="border-b border-amare-accent-deep bg-amare-accent-deep py-20 text-amare-accent-text" data-chapter="portfolio">
|
<section aria-labelledby="portfolio-heading" class="home-chapter border-b border-amare-accent-deep bg-amare-accent-deep py-20 text-amare-accent-text" data-chapter="portfolio">
|
||||||
<div class="container-amare space-y-10" data-reveal-group>
|
<div class="container-amare space-y-10" data-reveal-group>
|
||||||
<div class="grid gap-4 md:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]" data-reveal data-reveal-from="up">
|
<div class="grid gap-4 md:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]" data-reveal data-reveal-from="up">
|
||||||
<p class="text-sm font-medium uppercase tracking-[0.18em] text-amare-accent-text/80">Portfólio</p>
|
<x-home.folio label="Portfólio" tone="inverse" />
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<h2 id="portfolio-heading" class="text-headline font-medium">Celebrações que ganham forma com intenção.</h2>
|
<h2 id="portfolio-heading" class="text-headline font-medium">Celebrações que ganham forma com intenção.</h2>
|
||||||
<p class="max-w-2xl text-amare-accent-text/80">Recortes de eventos conduzidos com escuta, direção e presença em cada etapa.</p>
|
<p class="max-w-2xl text-amare-accent-text/80">Recortes de eventos conduzidos com escuta, direção e presença em cada etapa.</p>
|
||||||
|
|||||||
@@ -7,10 +7,14 @@
|
|||||||
$principles = filled($settings->principles) ? $settings->principles : \App\Models\SiteSetting::defaultPrinciples();
|
$principles = filled($settings->principles) ? $settings->principles : \App\Models\SiteSetting::defaultPrinciples();
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<section aria-labelledby="positioning-heading" class="border-b border-amare-border py-20" data-chapter="positioning">
|
<section aria-labelledby="positioning-heading" class="home-chapter border-b border-amare-border py-20" data-chapter="positioning">
|
||||||
<div class="container-amare grid gap-10 md:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]" data-reveal-group>
|
<div class="container-amare grid gap-10 md:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]" data-reveal-group>
|
||||||
<div class="space-y-3" data-reveal data-reveal-from="up">
|
<div class="space-y-4" data-reveal data-reveal-from="up">
|
||||||
<p class="text-sm font-medium uppercase tracking-[0.18em] text-amare-accent">A Amare</p>
|
<x-home.folio label="A Amare" />
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<span class="h-px w-10 bg-amare-border" aria-hidden="true"></span>
|
||||||
|
<p class="text-sm uppercase tracking-[0.12em] text-amare-text-muted">Assessoria boutique · São Paulo</p>
|
||||||
|
</div>
|
||||||
<h2 id="positioning-heading" class="text-headline font-medium text-amare-text">Presença que organiza o essencial.</h2>
|
<h2 id="positioning-heading" class="text-headline font-medium text-amare-text">Presença que organiza o essencial.</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-8" data-reveal data-reveal-from="up">
|
<div class="space-y-8" data-reveal data-reveal-from="up">
|
||||||
|
|||||||
@@ -3,12 +3,17 @@
|
|||||||
])
|
])
|
||||||
|
|
||||||
@if ($services->isNotEmpty())
|
@if ($services->isNotEmpty())
|
||||||
<section aria-labelledby="services-heading" class="border-b border-amare-border py-20" data-chapter="services">
|
<section aria-labelledby="services-heading" class="home-chapter border-b border-amare-border py-20" data-chapter="services">
|
||||||
<div class="container-amare space-y-10" data-reveal-group>
|
<div class="container-amare space-y-10" data-reveal-group>
|
||||||
<div class="max-w-2xl space-y-3" data-reveal data-reveal-from="up">
|
<div class="grid gap-6 md:grid-cols-12 md:items-end" data-reveal data-reveal-from="up">
|
||||||
<p class="text-sm font-medium uppercase tracking-[0.18em] text-amare-accent">Atuação</p>
|
<div class="space-y-3 md:col-span-4">
|
||||||
<h2 id="services-heading" class="text-3xl font-medium text-amare-text">Serviços</h2>
|
<x-home.folio label="Serviços" />
|
||||||
<p class="text-amare-text-muted">Assessoria sob medida para decisões importantes e celebrações bem conduzidas.</p>
|
<h2 id="services-heading" class="text-3xl font-medium text-amare-text">Serviços</h2>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4 md:col-span-6 md:col-start-7">
|
||||||
|
<p class="max-w-xl text-amare-text-muted">Assessoria sob medida para decisões importantes e celebrações bem conduzidas.</p>
|
||||||
|
<p class="border-l border-amare-border pl-4 text-sm uppercase tracking-[0.12em] text-amare-accent">Do íntimo ao corporativo</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ol class="border-t border-amare-border">
|
<ol class="border-t border-amare-border">
|
||||||
|
|||||||
@@ -9,9 +9,10 @@
|
|||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@if ($testimonials->isNotEmpty())
|
@if ($testimonials->isNotEmpty())
|
||||||
<section aria-labelledby="testimonials-heading" class="border-b border-amare-border bg-amare-bg py-16" data-chapter="testimonials">
|
<section aria-labelledby="testimonials-heading" class="home-chapter border-b border-amare-border bg-amare-bg py-16" data-chapter="testimonials">
|
||||||
<div class="container-amare space-y-8" data-reveal-group>
|
<div class="container-amare space-y-8" data-reveal-group>
|
||||||
<div class="max-w-2xl space-y-3" data-reveal data-reveal-from="up">
|
<div class="max-w-2xl space-y-3" data-reveal data-reveal-from="up">
|
||||||
|
<x-home.folio label="Depoimentos" />
|
||||||
<h2 id="testimonials-heading" class="text-3xl font-medium text-amare-text">Depoimentos</h2>
|
<h2 id="testimonials-heading" class="text-3xl font-medium text-amare-text">Depoimentos</h2>
|
||||||
<p class="text-amare-text-muted">Quem celebrou com a Amare conta como foi a experiência.</p>
|
<p class="text-amare-text-muted">Quem celebrou com a Amare conta como foi a experiência.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -23,12 +24,15 @@
|
|||||||
$paragraphs = array_values(array_filter(array_map('trim', $paragraphs), fn (string $p): bool => $p !== ''));
|
$paragraphs = array_values(array_filter(array_map('trim', $paragraphs), fn (string $p): bool => $p !== ''));
|
||||||
@endphp
|
@endphp
|
||||||
<blockquote class="space-y-4 border-t border-amare-border pt-4" data-reveal data-reveal-from="{{ $loop->odd ? 'left' : 'right' }}">
|
<blockquote class="space-y-4 border-t border-amare-border pt-4" data-reveal data-reveal-from="{{ $loop->odd ? 'left' : 'right' }}">
|
||||||
<div class="space-y-3 text-lg text-amare-text">
|
<div class="grid grid-cols-[1.5rem_minmax(0,1fr)] gap-2">
|
||||||
@foreach ($paragraphs as $index => $paragraph)
|
<span class="text-3xl leading-none text-amare-sage" aria-hidden="true">“</span>
|
||||||
<p>@if ($index === 0)“@endif{{ $paragraph }}@if ($index === count($paragraphs) - 1)”@endif</p>
|
<div class="space-y-3 text-lg text-amare-text">
|
||||||
@endforeach
|
@foreach ($paragraphs as $paragraph)
|
||||||
|
<p>{{ $paragraph }}</p>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<footer class="text-sm text-amare-text-muted">
|
<footer class="pl-8 text-sm text-amare-text-muted">
|
||||||
<cite class="not-italic font-semibold text-amare-text">{{ $testimonial->author_name }}</cite>
|
<cite class="not-italic font-semibold text-amare-text">{{ $testimonial->author_name }}</cite>
|
||||||
@if (filled($testimonial->context))
|
@if (filled($testimonial->context))
|
||||||
<span> — {{ $testimonial->context }}</span>
|
<span> — {{ $testimonial->context }}</span>
|
||||||
|
|||||||
@@ -31,12 +31,14 @@
|
|||||||
|
|
||||||
<x-home.chapter-index :chapters="$chapters" />
|
<x-home.chapter-index :chapters="$chapters" />
|
||||||
|
|
||||||
<x-home.hero :settings="$content->settings" />
|
<div class="home-chapters">
|
||||||
<x-home.manifesto :settings="$content->settings" />
|
<x-home.hero :settings="$content->settings" />
|
||||||
<x-home.services :services="$content->featuredServices" />
|
<x-home.manifesto :settings="$content->settings" />
|
||||||
<x-home.portfolio :cases="$content->featuredCases" />
|
<x-home.services :services="$content->featuredServices" />
|
||||||
<x-home.method :settings="$content->settings" />
|
<x-home.portfolio :cases="$content->featuredCases" />
|
||||||
<x-home.testimonials :testimonials="$testimonials" />
|
<x-home.method :settings="$content->settings" />
|
||||||
<x-home.positioning :settings="$content->settings" />
|
<x-home.testimonials :testimonials="$content->testimonials" />
|
||||||
<x-home.final-cta :settings="$content->settings" />
|
<x-home.positioning :settings="$content->settings" />
|
||||||
|
<x-home.final-cta :settings="$content->settings" editorial />
|
||||||
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
60
tests/Browser/HomeEditorialCadenceTest.php
Normal file
60
tests/Browser/HomeEditorialCadenceTest.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Database\Seeders\VisualContentSeeder;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function (): void {
|
||||||
|
Artisan::call('db:seed', ['--class' => VisualContentSeeder::class, '--force' => true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps the full home cadence accessible and contained at both viewports', function (): void {
|
||||||
|
foreach ([[1440, 1000], [390, 844]] as [$width, $height]) {
|
||||||
|
$page = $this->visit('/', [
|
||||||
|
'reducedMotion' => 'reduce',
|
||||||
|
])->resize($width, $height);
|
||||||
|
|
||||||
|
$page->assertNoAccessibilityIssues(1);
|
||||||
|
|
||||||
|
$layout = $page->script(<<<'JS'
|
||||||
|
() => {
|
||||||
|
const chapters = Array.from(document.querySelectorAll('.home-chapter'));
|
||||||
|
const folios = Array.from(document.querySelectorAll('[data-home-folio]'));
|
||||||
|
const clippedValues = new Set(['clip', 'hidden']);
|
||||||
|
const textNodes = Array.from(document.querySelectorAll(
|
||||||
|
'.home-chapters h1, .home-chapters h2, .home-chapters h3, .home-chapters p, .home-chapters a, .home-chapters span'
|
||||||
|
));
|
||||||
|
|
||||||
|
return {
|
||||||
|
horizontalOverflow: document.documentElement.scrollWidth > window.innerWidth,
|
||||||
|
overlappingChapters: chapters.slice(1).some((chapter, index) => {
|
||||||
|
const previous = chapters[index].getBoundingClientRect();
|
||||||
|
const current = chapter.getBoundingClientRect();
|
||||||
|
return current.top < previous.bottom - 1;
|
||||||
|
}),
|
||||||
|
overflowingFolios: folios.filter((folio) => {
|
||||||
|
const rect = folio.getBoundingClientRect();
|
||||||
|
return rect.left < -1 || rect.right > window.innerWidth + 1;
|
||||||
|
}).length,
|
||||||
|
clippedText: textNodes.filter((node) => {
|
||||||
|
const style = getComputedStyle(node);
|
||||||
|
const clippedX = clippedValues.has(style.overflowX) && node.scrollWidth > node.clientWidth + 1;
|
||||||
|
const clippedY = clippedValues.has(style.overflowY) && node.scrollHeight > node.clientHeight + 1;
|
||||||
|
return clippedX || clippedY;
|
||||||
|
}).length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
JS);
|
||||||
|
|
||||||
|
expect($layout)->toBe([
|
||||||
|
'horizontalOverflow' => false,
|
||||||
|
'overlappingChapters' => false,
|
||||||
|
'overflowingFolios' => 0,
|
||||||
|
'clippedText' => 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -302,3 +302,48 @@ it('updates the active chapter while scrolling the home dossier', function (): v
|
|||||||
expect($before)->not->toBeNull();
|
expect($before)->not->toBeNull();
|
||||||
expect($after)->toBe('#method-heading');
|
expect($after)->toBe('#method-heading');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('applies CSS numbering to only rendered home folios in chapter order', function (): void {
|
||||||
|
PortfolioCase::factory()->published()->create([
|
||||||
|
'is_featured' => true,
|
||||||
|
]);
|
||||||
|
Testimonial::factory()->published()->create([
|
||||||
|
'quote' => 'Relato publicado.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$page = $this->visit('/', [
|
||||||
|
'reducedMotion' => 'reduce',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$numbering = $page->script(<<<'JS'
|
||||||
|
() => {
|
||||||
|
const flow = document.querySelector('.home-chapters');
|
||||||
|
const chapters = Array.from(document.querySelectorAll('.home-chapter'));
|
||||||
|
const folios = Array.from(document.querySelectorAll('[data-home-folio]'));
|
||||||
|
|
||||||
|
return {
|
||||||
|
reset: flow ? getComputedStyle(flow).counterReset : '',
|
||||||
|
increments: chapters.map((chapter) => getComputedStyle(chapter).counterIncrement),
|
||||||
|
labels: folios.map((folio) => folio.querySelector('[data-home-folio-label]')?.textContent?.trim() ?? ''),
|
||||||
|
contents: folios.map((folio) => {
|
||||||
|
const number = folio.querySelector('[data-home-folio-number]');
|
||||||
|
return number ? getComputedStyle(number, '::before').content.replace(/["']/g, '') : '';
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
JS);
|
||||||
|
|
||||||
|
expect($numbering['reset'])->toContain('home-chapter -1');
|
||||||
|
expect($numbering['increments'])->each->toContain('home-chapter 1');
|
||||||
|
expect($numbering['labels'])->toBe([
|
||||||
|
'Capa',
|
||||||
|
'Manifesto',
|
||||||
|
'Serviços',
|
||||||
|
'Portfólio',
|
||||||
|
'Método',
|
||||||
|
'Depoimentos',
|
||||||
|
'A Amare',
|
||||||
|
'Próximo passo',
|
||||||
|
]);
|
||||||
|
expect($numbering['contents'])->each->toBe('counter(home-chapter, decimal-leading-zero)');
|
||||||
|
});
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ foreach ($screens as $screen => $path) {
|
|||||||
])
|
])
|
||||||
->withLocale('pt-BR')
|
->withLocale('pt-BR')
|
||||||
->withTimezone('America/Fortaleza')
|
->withTimezone('America/Fortaleza')
|
||||||
->resize($width, $height)
|
->resize($width, $height),
|
||||||
|
resetScroll: $path === '/',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,4 +197,65 @@ class HomePageContentTest extends TestCase
|
|||||||
->assertDontSee('href="#testimonials-heading"', false)
|
->assertDontSee('href="#testimonials-heading"', false)
|
||||||
->assertDontSee('Relato vazio');
|
->assertDontSee('Relato vazio');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_home_folios_follow_rendered_chapter_order_and_hide_decorative_numbers(): void
|
||||||
|
{
|
||||||
|
SiteSetting::instance();
|
||||||
|
|
||||||
|
Service::factory()->published()->featured()->create();
|
||||||
|
PortfolioCase::factory()->published()->create(['is_featured' => true]);
|
||||||
|
Testimonial::factory()->published()->create(['quote' => 'Relato publicado.']);
|
||||||
|
|
||||||
|
$html = $this->get(route('home'))->assertOk()->getContent();
|
||||||
|
|
||||||
|
$this->assertSame([
|
||||||
|
'Capa',
|
||||||
|
'Manifesto',
|
||||||
|
'Serviços',
|
||||||
|
'Portfólio',
|
||||||
|
'Método',
|
||||||
|
'Depoimentos',
|
||||||
|
'A Amare',
|
||||||
|
'Próximo passo',
|
||||||
|
], $this->folioLabels((string) $html));
|
||||||
|
$this->assertSame(8, preg_match_all('/data-home-folio-number[^>]*aria-hidden="true"/i', (string) $html));
|
||||||
|
$this->assertSame(1, preg_match_all('/<h1\b/i', (string) $html));
|
||||||
|
|
||||||
|
$css = (string) file_get_contents(resource_path('css/app.css'));
|
||||||
|
|
||||||
|
$this->assertStringContainsString('counter-reset: home-chapter -1', $css);
|
||||||
|
$this->assertStringContainsString('counter-increment: home-chapter', $css);
|
||||||
|
$this->assertStringContainsString('counter(home-chapter, decimal-leading-zero)', $css);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_home_folios_are_omitted_with_conditional_chapters_without_leaving_markup_gaps(): void
|
||||||
|
{
|
||||||
|
SiteSetting::instance();
|
||||||
|
|
||||||
|
$html = $this->get(route('home'))->assertOk()->getContent();
|
||||||
|
|
||||||
|
$this->assertSame([
|
||||||
|
'Capa',
|
||||||
|
'Manifesto',
|
||||||
|
'Método',
|
||||||
|
'A Amare',
|
||||||
|
'Próximo passo',
|
||||||
|
], $this->folioLabels((string) $html));
|
||||||
|
$this->assertSame(5, preg_match_all('/data-home-folio-number[^>]*aria-hidden="true"/i', (string) $html));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return list<string> */
|
||||||
|
private function folioLabels(string $html): array
|
||||||
|
{
|
||||||
|
preg_match_all(
|
||||||
|
'/<span\b[^>]*data-home-folio-label[^>]*>\s*([^<]+?)\s*<\/span>/is',
|
||||||
|
$html,
|
||||||
|
$matches,
|
||||||
|
);
|
||||||
|
|
||||||
|
return array_values(array_map(
|
||||||
|
static fn (string $label): string => trim(html_entity_decode($label)),
|
||||||
|
$matches[1],
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ trait StableScreenshot
|
|||||||
* images, so this helper additionally exports standalone diff/expected/actual
|
* images, so this helper additionally exports standalone diff/expected/actual
|
||||||
* PNG files that are directly viewable in CI artifacts.
|
* PNG files that are directly viewable in CI artifacts.
|
||||||
*/
|
*/
|
||||||
public function assertStableScreenshotMatches(AwaitableWebpage $awaitable): void
|
public function assertStableScreenshotMatches(AwaitableWebpage $awaitable, bool $resetScroll = false): void
|
||||||
{
|
{
|
||||||
$page = $awaitable->page();
|
$page = $awaitable->page();
|
||||||
|
|
||||||
@@ -66,6 +66,11 @@ trait StableScreenshot
|
|||||||
}
|
}
|
||||||
JS);
|
JS);
|
||||||
|
|
||||||
|
if ($resetScroll) {
|
||||||
|
$page->evaluate('window.scrollTo(0, 0)');
|
||||||
|
$page->waitForFunction('window.scrollY === 0');
|
||||||
|
}
|
||||||
|
|
||||||
usleep(300_000);
|
usleep(300_000);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user