12 KiB
Tasks: Feature 018 — Homepage Imersiva com Scroll (Hero + Destaques)
Feature Branch: 018-homepage-scroll-hero
Input: .specify/features/018-homepage-scroll-hero/plan.md, .specify/features/018-homepage-scroll-hero/spec.md
Prerequisites: plan.md ✅, spec.md ✅
Format: [ID] [P?] [Story?] Description — caminho
- [P]: Pode rodar em paralelo (arquivos diferentes, sem dependências de tarefas incompletas)
- [Story]: User story correspondente (US1–US5, mapeado ao spec.md)
Phase 1: Foundational — Backend + Frontend Types (Pré-requisitos bloqueantes)
Purpose: Expor hero_image_url no banco de dados, no backend e no frontend. Todas as fases de user story dependem desta fase.
⚠️ CRÍTICO: Nenhuma user story pode ser iniciada até esta fase estar completa.
- T001 Criar migration Alembic com
revision = "d1e2f3a4b5c6",down_revision = "c8d9e0f1a2b3":upgrade()executaop.add_column("homepage_config", sa.Column("hero_image_url", sa.String(512), nullable=True)),downgrade()executaop.drop_column—backend/migrations/versions/d1e2f3a4b5c6_add_hero_image_url_to_homepage_config.py - T002 [P] Adicionar campo
hero_image_url = db.Column(db.String(512), nullable=True)ao modeloHomepageConfigapósfeatured_properties_limit—backend/app/models/homepage.py - T003 [P] Adicionar
hero_image_url: str | None = NoneaHomepageConfigOuteHomepageConfigIn; emHomepageConfigInincluir@field_validator("hero_image_url", mode="before")que converte string vazia ou de apenas espaços paraNone—backend/app/schemas/homepage.py - T004 [P] Adicionar campo
hero_image_url?: string | nullà interfaceHomepageConfig; adicionarhero_image_url: nullao objetoFALLBACK_CONFIG—frontend/src/types/homepage.tsefrontend/src/pages/HomePage.tsx
Checkpoint: GET /api/v1/homepage-config retorna hero_image_url; interface TypeScript corretamente tipada.
Phase 2: User Story 1 — Visitante visualiza a hero fullscreen (P1) 🎯 MVP
Goal: Hero ocupa 100vh com imagem de fundo opcional, overlay escuro semitransparente e headline/subheadline/CTA centralizados; fallback para gradiente CSS quando sem imagem.
Independent Test: Acessar homepage com hero_image_url configurado e verificar imagem fullscreen com overlay escuro e texto branco legível. Acessar sem configuração e verificar exibição do gradiente padrão com texto igualmente legível.
- T005 [P] [US1] Adicionar prop
backgroundImage?: string | nullà interfaceHeroSectionProps; quandobackgroundImagepresente, aplicarstyle={{ backgroundImage: \url(${backgroundImage})`, backgroundSize: 'cover', backgroundPosition: 'center' }}ao(substituindo o gradiente); adicionar<div className="absolute inset-0 pointer-events-none" style={{ background: 'rgba(0,0,0,0.52)' }} aria-hidden="true" />dentro da section; garantir conteúdo composition: relative; z-index: 1—frontend/src/components/HeroSection.tsx` - T006 [P] [US1] Atualizar
HomeScrollScene.tsx: (a) expandir interfaceHomeScrollScenePropspara incluirheadline: string,subheadline?: string | null,ctaLabel?: string,ctaUrl?: string,backgroundImage?: string | null,isLoading?: boolean; (b) renomear propimageUrl→backgroundImageem toda a implementação; (c) adicionar<div className="absolute inset-0 pointer-events-none" style={{ background: 'rgba(0,0,0,0.50)' }} aria-hidden="true" />após o bloco{backgroundImage ? <img> : <div gradiente>}e antes do overlay de gradiente existente — apenas quandobackgroundImagepresente; (d) adicionar bloco de hero text com skeleton de loading e conteúdo real (h1, p,<a>) dentro do container sticky, posicionado comclassName="absolute inset-0 flex flex-col items-center justify-center px-6 text-center z-10 pt-14"—frontend/src/components/HomeScrollScene.tsx - T007 [US1] Remover import e uso de
<HeroSection>do JSX deHomePage; passar ao<HomeScrollScene>as props:headline={config.hero_headline},subheadline={config.hero_subheadline},ctaLabel={config.hero_cta_label},ctaUrl={config.hero_cta_url},backgroundImage={config.hero_image_url ?? null},isLoading={isLoading}—frontend/src/pages/HomePage.tsx
Checkpoint: Hero fullscreen com imagem de fundo (ou gradiente como fallback), overlay escuro e texto centralizado visível e legível.
Phase 3: User Story 2 — Cards de imóveis emergem durante o scroll (P1)
Goal: Imagem hero permanece sticky enquanto cards sobem por cima com animação opacity 0→1 + translateY 48px→0; stagger 60ms entre cards; usuários com prefers-reduced-motion não recebem animações de movimento.
Independent Test: Rolar a homepage com imóveis em destaque cadastrados; verificar sticky hero + animação de entrada por card com stagger. Ativar prefers-reduced-motion: reduce no DevTools e verificar ausência de translateY.
Nota: Animação
RiseCard(IntersectionObserverthreshold: 0.05, stagger viatransitionDelay: ${index * 60}ms, container stickyh-screen z-0) já está implementada. Esta fase adiciona apenas suporte aprefers-reduced-motion.
- T008 [US2] Adicionar classes
motion-reduce:transition-none motion-reduce:translate-y-0aoclassNamedo<div>animado dentro deRiseCardpara suprimir a transição CSS e otranslateYquandoprefers-reduced-motionestiver ativo —frontend/src/components/HomeScrollScene.tsx
Checkpoint: Cards animam com opacity + translateY normalmente; prefers-reduced-motion suprime o movimento mantendo a visibilidade.
Phase 4: User Story 3 — Indicador visual guia o visitante a rolar (P2)
Goal: ScrollHint exibe "Role para ver os destaques" com 3 chevrons animados em cascata enquanto o hero é visível; animação suprimida em prefers-reduced-motion.
Independent Test: Verificar label "Role para ver os destaques" e 3 chevrons animados enquanto hero visível; ativar prefers-reduced-motion: reduce e verificar que chevrons param de animar.
Nota:
ScrollHintcom 3 chevrons animados em cascata (keyframefadeDown,animation-delay: ${i * 0.2}s) já existe. Esta fase atualiza o label e adiciona suporte aprefers-reduced-motion.
- T009 [US3] Em
HomeScrollScene.tsx: alterar o label fixo passado ao<ScrollHint>de"Imóveis em destaque"para"Role para ver os destaques"; adicionarclassName="scroll-chevron"em cada<svg>dos chevrons dentro deScrollHint; acrescentar ao bloco<style>inline a regra@media (prefers-reduced-motion: reduce) { .scroll-chevron { animation: none !important; } }—frontend/src/components/HomeScrollScene.tsx
Checkpoint: Indicador exibe texto correto; chevrons animam normalmente e têm animação suprimida em prefers-reduced-motion.
Phase 5: User Story 4 — Redirecionamento automático ao fim dos destaques (P2)
Goal: Após 800ms com marcador final 100% visível, redirecionar para /imoveis com overlay blur + spinner.
Independent Test: Rolar até o fim dos cards de destaque; aguardar 800ms; verificar overlay com blur e spinner e redirect para /imoveis.
Nota: Implementação completa já existe em
HomeScrollScene.tsx:sentinelRef+IntersectionObserver threshold: 1.0+setTimeout 800ms+ estadonavigating+ overlay combackdropFilter: blur(8px)+ spinner animado +navigate('/imoveis'). Esta fase apenas melhora a acessibilidade do overlay.
- T010 [US4] Adicionar
role="status"earia-live="polite"ao<div>do overlay de transição (bloco{navigating && (...)}), earia-label="Redirecionando para a listagem de imóveis"ao spinner, para acessibilidade de screen readers —frontend/src/components/HomeScrollScene.tsx
Checkpoint: Redirect automático funcionando em 800ms com overlay blur+spinner e overlay acessível via aria-live.
Phase 6: User Story 5 — Administrador configura a imagem de fundo do hero (P3)
Goal: Admin persiste URL da imagem via painel; API retorna o valor; homepage exibe a imagem configurada.
Independent Test: Salvar URL válida via admin panel; consultar GET /api/v1/homepage-config e verificar hero_image_url na resposta; confirmar exibição na homepage. Salvar string vazia e verificar que API retorna null e fallback de gradiente é usado.
Nota: A spec assume que o painel admin para
HomepageConfigjá existe, mas nenhum endpointPUTnem página admin foram encontrados no codebase. Esta fase cria ambos. O schemaHomepageConfigIn(atualizado em T003) já valida o campo.
- T011 [P] [US5] Adicionar endpoint
PUT /api/v1/admin/homepage-configao blueprinthomepage_bp: requer JWT de admin (usar decorator de auth já existente embackend/app/utils/auth.py), valida payload comHomepageConfigIn, atualiza registro via SQLAlchemy (.query.first()+ atribuição de atributos +db.session.commit()), retornaHomepageConfigOut.model_validate(config).model_dump()—backend/app/routes/homepage.py - T012 [US5] Criar
AdminHomepagePage.tsxcom formulário controlado (campos:hero_headline,hero_subheadline,hero_cta_label,hero_cta_url,featured_properties_limit,hero_image_url); buscar config atual comGET /api/v1/homepage-configao montar; salvar comPUT /api/v1/admin/homepage-config; seguir padrão visual e de autenticação das demais páginas admin existentes —frontend/src/pages/admin/AdminHomepagePage.tsx
Checkpoint: Admin salva hero_image_url via painel; valor retornado pela API; homepage exibe imagem configurada.
Phase 7: Polish & Cross-Cutting Concerns
Purpose: Comportamentos de borda e qualidade transversal.
- T013 [P] Verificar fallback de imagem padrão: quando
backgroundImageénull,undefinedou URL que resulta em erro (addonErrorhandler no<img>deHomeScrollScene.tsxpara setarbackgroundImagestate comonullem caso de falha de carregamento) —frontend/src/components/HomeScrollScene.tsx - T014 [P] Verificar comportamento mobile: testar hero em viewports < 640px no Chrome DevTools garantindo
backgroundSize: coversem distorção; confirmar que hero text e CTA são legíveis em telas pequenas —frontend/src/components/HomeScrollScene.tsx
Dependencies & Execution Order
Phase Dependencies
- Foundational (Phase 1): Sem dependências — pode começar imediatamente
- User Stories (Phases 2–6): Todas dependem da conclusão da Phase 1
- US1 (Phase 2) e US2 (Phase 3): US2 depende de T006 estar completo; demais são independentes entre si
- US3 (Phase 4), US4 (Phase 5), US5 (Phase 6): podem rodar em paralelo entre si após Phase 1
- Polish (Phase 7): Após todas as user stories desejadas estarem completas
User Story Dependencies
- US1 (Phase 2, P1): Depende da Foundational (T001–T004)
- US2 (Phase 3, P1): Depende de T006 (props interface de HomeScrollScene já atualizada)
- US3 (Phase 4, P2): Depende apenas de Phase 1 — independente de US1/US2
- US4 (Phase 5, P2): Já implementado — apenas acessibilidade; depende de Phase 1
- US5 (Phase 6, P3): Depende de T002 e T003 (model e schema já prontos)
Parallel Execution
Phase 1 (Foundational):
T001 → [T002 ∥ T003 ∥ T004]
Phase 2 (US1):
[T005 ∥ T006] → T007
Após Phase 2 (US2–US5 em paralelo):
T008 (US2) ∥ T009 (US3) ∥ T010 (US4) ∥ [T011 → T012] (US5)
Phase 7 (Polish):
T013 ∥ T014
Implementation Strategy
MVP (entrega mínima — 7 tarefas): Phases 1 + 2 (T001–T007)
- Homepage já exibe hero imersivo fullscreen com imagem de fundo opcional, overlay escuro e hero text centralizado
- Sticky scroll + cards animados continuam funcionando (já implementados)
Incremental após MVP:
- T008 (US2) — reduced-motion support para cards
- T009 (US3) — label correto + reduced-motion no ScrollHint
- T010 (US4) — acessibilidade no overlay de redirect (núcleo já funcional)
- T011–T012 (US5) — admin panel para configurar
hero_image_url - T013–T014 (Polish) — fallback de imagem + validação mobile
Summary
| Fase | User Story | Tarefas | Prioridade |
|---|---|---|---|
| Foundational | — | T001–T004 | Bloqueante |
| Phase 2 | US1 — Hero Fullscreen | T005–T007 | P1 (MVP) |
| Phase 3 | US2 — Cards emergem | T008 | P1 |
| Phase 4 | US3 — Indicador scroll | T009 | P2 |
| Phase 5 | US4 — Auto-redirect | T010 | P2 |
| Phase 6 | US5 — Admin config | T011–T012 | P3 |
| Phase 7 | Polish | T013–T014 | — |
| Total | 14 tarefas |