Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions docs/superpowers/plans/2026-10-30-aplicativos-portfolio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Aplicativos Help DEV Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add a Portuguese portfolio page with six separate Google Play app cards and a CuidaZ “Em Breve” card.

**Architecture:** Keep app content in a static typed data module, render it with a focused portfolio card component, and use local image assets for every card. Add the `/apps` route to the existing navigation, footer, metadata, and static sitemap.

**Tech Stack:** Next.js 15 App Router, React 19, TypeScript, Tailwind CSS, static export.

**Spec:** `docs/superpowers/specs/2026-10-30-aplicativos-portfolio-design.md`

## Global Constraints

- Keep the page in Portuguese and maintain the site's current responsive Tailwind styling.
- Show seven cards: six published apps and one CuidaZ preview.
- Keep each Cronômetro listing separate, with a distinct Google Play link.
- Every card must display a description.
- Do not show a Google Play CTA on CuidaZ; display an “Em Breve” ribbon and its existing horizontal logo.
- Preserve the existing static export configuration and the untracked CuidaZ privacy-policy source file.
- Do not add a test framework or test files; the repository has no tests configured.

## Review Focus

- App-to-store mapping: check every published card points to its matching package ID from the spec.
- Card copy: check that all seven descriptions are rendered in full and remain readable.
- Coming-soon behavior: check that CuidaZ shows “Em Breve” and does not become a dead or misleading store link.
- Local imagery: check each image file exists, displays correctly, and uses the CuidaZ horizontal logo on its card.
- Responsive access: check the new navigation link appears beside Projetos in desktop and mobile menus and the grid fits narrow screens.

---

### Task 1: Prepare local app imagery

**Files:**
- Create: `public/images/apps/`
- Create: one Play Store promotional image and app icon per published listing, plus `cuidaz-logo.png`.

**Interfaces:**
- Produces the local image paths consumed by `src/data/apps.ts` in Task 2.

- [x] Download the promotional image from the developer profile and the app icon for each package listed in the spec. Save them under stable, readable filenames in `public/images/apps/`, keeping the correct file extensions.
- [x] Copy `/Volumes/gbz-ext/repositories/helpdev/cuidaz/assets/marca/logos/cuidaz-horizontal-transparente.png` to `public/images/apps/cuidaz-logo.png`.
- [x] Check all 13 files exist and are valid images; visually inspect the CuidaZ logo and one promotional image from each listing.

### Task 2: Build the app catalog and portfolio cards

**Files:**
- Create: `src/data/apps.ts`
- Create: `src/app/components/AppCard/index.tsx`
- Create: `src/app/apps/page.tsx`

**Interfaces:**
- `src/data/apps.ts` exports a `PortfolioApp` discriminated union and a `portfolioApps: PortfolioApp[]` in display order.
- A published app has its name, full short description, promotional image path, icon path, and exact Play Store URL. CuidaZ has its name, description, local logo path, and `kind: 'coming-soon'` with no store URL.
- `AppCard` accepts one `PortfolioApp` and renders the appropriate published or coming-soon treatment.

- [x] Add all six Play Store listings from the spec to `portfolioApps`, preserving their exact names and descriptions, with one unique store URL per listing.
- [x] Add CuidaZ with the description “Organize pacientes, agenda, atendimentos, evoluções clínicas e recebimentos.” and no store link.
- [x] Render published cards with a promotional image, app icon, full description, and “Ver na Play Store” external link; render CuidaZ with its horizontal logo and an “Em Breve” ribbon.
- [x] Build `/apps` with one H1, short introduction, and a responsive grid that shows all seven cards without truncating descriptions.
- [x] Define page metadata, canonical `/apps`, and Open Graph information in `src/app/apps/page.tsx` so it cannot affect nested privacy-policy metadata.

### Task 3: Add navigation and validate the static page

**Files:**
- Modify: `src/app/components/Navbar/index.tsx`
- Modify: `src/app/components/Footer/index.tsx`
- Modify: `src/app/sitemap.ts`

**Interfaces:**
- The existing Navbar uses `usePathname()` and `isActive()` for route state; reuse that behavior for `/apps`.

- [x] Add an “Aplicativos” link immediately after “Projetos” in both desktop and mobile navigation, with active styling on `/apps` and mobile menu close behavior.
- [x] Add an “Aplicativos” link to the footer and `/apps` to the sitemap route list.
- [x] Run `git diff --check` and `npm run build`; confirm a generated `/apps` static page is present under `out/` and all local image paths are emitted.
- [x] Review the final diff to confirm only the approved portfolio scope changed and the pre-existing untracked CuidaZ privacy-policy file is untouched.
61 changes: 61 additions & 0 deletions docs/superpowers/specs/2026-10-30-aplicativos-portfolio-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Aplicativos Help DEV — Design

## Intent

Create a portfolio page that makes Help DEV's Google Play apps easy to discover and open. Add an “Aplicativos” item beside “Projetos” in the site navigation.

## Goals

- Publish a responsive `/apps` page with seven portfolio cards: one for each of the six current Help DEV listings on Google Play, plus CuidaZ marked “Em Breve”.
- Put a description on every card. Published apps also get their store icon, the promotional image shown on the Play Store developer profile, and a clear link to that exact listing.
- Use the CuidaZ brand logo from its app repository for the upcoming card.
- Match the existing Next.js, Tailwind, and static-export setup.
- Include the page in the sitemap and give it page-specific metadata.

## Non-goals and anti-goals

- Do not group the three LapsCounter listings; running, cycling, and swimming each get their own card and store link.
- Do not imply that CuidaZ is already published or provide a Play Store link for it.
- Do not add app detail pages, install flows, ratings, reviews, or data that is not needed for this portfolio.
- Do not change the existing privacy-policy pages, including the untracked CuidaZ policy already in the worktree.

## Confirmed listings

The developer profile currently returns these six listings. Titles and short descriptions are from their Play Store pages.

| App | Play Store package | Short description |
| --- | --- | --- |
| Minha Obra | `br.com.helpdev.minhareforma` | Organize orçamento, gastos e pagamentos da sua obra ou reforma. |
| AtendFlow | `com.helpdev.atendflow` | Organize clientes, agendamentos e cobranças em um só lugar. |
| Velocímetro Alerta | `br.com.helpdev.velocimetroalerta.v2` | Alertas de velocidade por voz por tempo ou distância, sem olhar para a tela. |
| Cronômetro de corrida | `br.com.helpdev.lapscounter.running` | Marque trechos e acompanhe ritmo, distância e tempo total. |
| Cronômetro de ciclismo | `br.com.helpdev.lapscounter.cycling` | Marque trechos e acompanhe distância, tempo e velocidade média. |
| Cronômetro de Natação | `br.com.helpdev.lapscounter.swimming` | Marque voltas e acompanhe distância e ritmo na natação. |
| CuidaZ (Em Breve) | Ainda sem listagem pública | Organize pacientes, agenda, atendimentos, evoluções clínicas e recebimentos. |

## Approaches

1. **Six independent published-app cards plus a CuidaZ preview (chosen).** Each Play Store listing has its own card, promotional image, app icon, description, and Google Play CTA. CuidaZ has a separate card with its repository logo, description, and an “Em Breve” ribbon, without a store CTA. This follows the user's preference to keep all three Cronômetro apps separate and gives every listing equal visibility.
2. **Group the three Cronômetro listings under LapsCounter.** This would reduce repetition, but it does not match the user's preference to present each app separately.
3. **Compact list of store links.** This is the smallest change, but it would not give the page the requested portfolio presentation.

## Design and implementation calls

- The new route is `/apps`, matching the existing English route segments and the existing `src/app/apps/` privacy-policy routes.
- Add the navigation link in desktop and mobile menus, next to “Projetos”. Mark it active on `/apps`.
- Present a short page heading and introduction, followed by a responsive grid of seven cards. Each published-app card uses the promotional image from its Play Store developer-profile listing, the app icon alongside the name, the store description, and a “Ver na Play Store” link that opens the exact listing in a new tab.
- Give CuidaZ a separate “Em Breve” ribbon and use `cuidaz/assets/marca/logos/cuidaz-horizontal-transparente.png` from the CuidaZ repository. Its description is “Organize pacientes, agenda, atendimentos, evoluções clínicas e recebimentos.” Do not make the card clickable or show an unavailable store link.
- Copy the Play Store promotional images and icons, and the CuidaZ logo, into `public/images/apps/` so the static site does not depend on external image URLs at runtime.
- Add `/apps` to `src/app/sitemap.ts` and define metadata in `src/app/apps/page.tsx` so portfolio metadata does not propagate to nested privacy-policy routes.
- The existing footer lists primary site pages; add “Aplicativos” there to keep site navigation consistent.

## Constraints and success criteria

- Preserve the existing static export configuration and the user's current untracked CuidaZ privacy-policy file.
- Keep the page in Portuguese and maintain the site's current responsive Tailwind styling.
- Success means the page shows six cards with correct local imagery, descriptions, and exact Google Play targets, plus the CuidaZ logo, description, and “Em Breve” ribbon; the desktop and mobile navigation both reach `/apps`; the page is present in the sitemap and static production build.

## Remaining implementation choices

- The builder may choose the exact card spacing, color accents, and image crop to fit the existing site components.
- Prefer semantic links, descriptive image alt text, and a responsive layout with no horizontal overflow.
Binary file added public/images/apps/atendflow-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/apps/atendflow-promotional.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/apps/cronometro-ciclismo-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/apps/cronometro-corrida-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/apps/cronometro-natacao-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/apps/cuidaz-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/apps/minha-obra-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/apps/minha-obra-promotional.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/apps/velocimetro-alerta-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 57 additions & 0 deletions src/app/apps/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { Metadata } from 'next';
import { Navbar } from '../components/Navbar';
import { AppCard } from '../components/AppCard';
import { portfolioApps } from '@/data/apps';

export const metadata: Metadata = {
title: 'Aplicativos',
description: 'Conheça os aplicativos da Help DEV para organização, mobilidade e esporte, além do próximo lançamento CuidaZ.',
keywords: ['aplicativos', 'Google Play', 'Help DEV', 'Minha Obra', 'AtendFlow', 'Velocímetro Alerta', 'CuidaZ'],
authors: [{ name: 'Guilherme Biff Zarelli' }],
alternates: { canonical: '/apps' },
openGraph: {
url: '/apps',
title: 'Aplicativos | HelpDev',
description: 'Aplicativos da Help DEV para organização, mobilidade e esporte, além do próximo lançamento CuidaZ.',
type: 'website',
images: [{
url: 'https://helpdev.com.br/images/logo.png',
width: 1200,
height: 630,
alt: 'Aplicativos HelpDev',
}],
},
twitter: {
card: 'summary_large_image',
title: 'Aplicativos | HelpDev',
description: 'Aplicativos da Help DEV para organização, mobilidade e esporte, além do próximo lançamento CuidaZ.',
images: ['https://helpdev.com.br/images/logo.png'],
creator: '@gbzarelli',
site: '@_helpdev',
},
};

export default function AppsPage() {
return (
<main className="min-h-screen bg-gray-100">
<Navbar />
<div className="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8">
<header className="mb-8">
<h1 className="text-3xl font-bold text-gray-900">Aplicativos</h1>
<p className="mt-2 max-w-2xl text-base leading-7 text-gray-600">
Aplicativos da Help DEV para organização, mobilidade e esporte — e o próximo lançamento CuidaZ.
</p>
</header>

<section
aria-label="Portfólio de aplicativos"
className="grid grid-cols-1 gap-6 sm:grid-cols-2 xl:grid-cols-3"
>
{portfolioApps.map((app) => (
<AppCard key={app.id} app={app} />
))}
</section>
</div>
</main>
);
}
70 changes: 70 additions & 0 deletions src/app/components/AppCard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Image from 'next/image';
import type { PortfolioApp } from '@/data/apps';

interface AppCardProps {
app: PortfolioApp;
}

export function AppCard({ app }: AppCardProps) {
return (
<article className="group flex h-full flex-col overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition-all duration-300 hover:-translate-y-1 hover:shadow-xl">
{app.kind === 'published' ? (
<div className="relative aspect-[2/1] overflow-hidden bg-gray-100">
<Image
src={app.promotionalImage}
alt={`Imagem promocional do aplicativo ${app.name}`}
fill
sizes="(max-width: 640px) 100vw, (max-width: 1280px) 50vw, 33vw"
className="object-cover transition-transform duration-300 group-hover:scale-105"
/>
</div>
) : (
<div className="relative flex aspect-[2/1] flex-col items-center justify-center overflow-hidden bg-gradient-to-br from-sky-50 via-white to-emerald-50 px-8 py-10">
<span className="absolute inset-x-0 top-0 bg-gradient-to-r from-blue-700 to-emerald-600 py-2 text-center text-sm font-semibold tracking-wide text-white">
{app.statusLabel}
</span>
<Image
src={app.logo}
alt="Logo do CuidaZ"
width={1135}
height={340}
sizes="(max-width: 640px) 75vw, 300px"
className="mt-6 h-auto w-full max-w-[300px] object-contain"
/>
</div>
)}

<div className="flex flex-1 flex-col p-5">
<div className="flex items-center gap-3">
{app.kind === 'published' && (
<Image
src={app.icon}
alt={`Ícone do aplicativo ${app.name}`}
width={52}
height={52}
className="h-13 w-13 rounded-xl object-cover shadow-sm"
/>
)}
<h2 className="text-lg font-semibold leading-snug text-gray-900 group-hover:text-blue-700">
{app.name}
</h2>
</div>

<p className="mt-4 flex-1 text-sm leading-6 text-gray-600">
{app.description}
</p>

{app.kind === 'published' && (
<a
href={app.storeUrl}
target="_blank"
rel="noopener noreferrer"
className="mt-6 inline-flex min-h-11 items-center justify-center rounded-lg bg-blue-700 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-blue-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2"
>
Ver na Play Store
</a>
)}
</div>
</article>
);
}
1 change: 1 addition & 0 deletions src/app/components/Footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const Footer: React.FC = () => {
<li><Link href="/about" className="text-gray-400 hover:text-white transition-colors">Sobre</Link></li>
<li><Link href="/articles" className="text-gray-400 hover:text-white transition-colors">Artigos</Link></li>
<li><Link href="/projects" className="text-gray-400 hover:text-white transition-colors">Projetos</Link></li>
<li><Link href="/apps" className="text-gray-400 hover:text-white transition-colors">Aplicativos</Link></li>
<li><Link href="/blog" className="text-gray-400 hover:text-white transition-colors">Blog</Link></li>
<li><Link href="/#contato" className="text-gray-400 hover:text-white transition-colors">Contato</Link></li>
<li><Link href="/privacidade" className="text-gray-400 hover:text-white transition-colors">Privacidade</Link></li>
Expand Down
27 changes: 24 additions & 3 deletions src/app/components/Navbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const Navbar = React.memo(() => {
/>
</Link>
{/* Desktop Menu */}
<div className="hidden sm:ml-6 sm:flex sm:space-x-8">
<div className="hidden md:ml-6 md:flex md:space-x-3">
<Link
href="/"
className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${
Expand Down Expand Up @@ -68,6 +68,16 @@ export const Navbar = React.memo(() => {
>
Projetos
</Link>
<Link
href="/apps"
className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${
isActive('/apps')
? 'border-blue-500 text-gray-900'
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'
}`}
>
Aplicativos
</Link>
<Link
href="/blog"
className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${
Expand Down Expand Up @@ -98,7 +108,7 @@ export const Navbar = React.memo(() => {
</div>

{/* Mobile menu button */}
<div className="flex items-center sm:hidden">
<div className="flex items-center md:hidden">
<button
onClick={toggleMenu}
className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500"
Expand Down Expand Up @@ -140,7 +150,7 @@ export const Navbar = React.memo(() => {
</div>

{/* Mobile menu */}
<div className={`${isMenuOpen ? 'block' : 'hidden'} sm:hidden`}>
<div className={`${isMenuOpen ? 'block' : 'hidden'} md:hidden`}>
<div className="pt-2 pb-3 space-y-1">
<Link
href="/"
Expand Down Expand Up @@ -175,6 +185,17 @@ export const Navbar = React.memo(() => {
>
Projetos
</Link>
<Link
href="/apps"
className={`block pl-3 pr-4 py-2 border-l-4 text-base font-medium ${
isActive('/apps')
? 'border-blue-500 text-blue-700 bg-blue-50'
: 'border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700'
}`}
onClick={closeMenu}
>
Aplicativos
</Link>
<Link
href="/blog"
className={`block pl-3 pr-4 py-2 border-l-4 text-base font-medium ${
Expand Down
1 change: 1 addition & 0 deletions src/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
'/about',
'/articles',
'/projects',
'/apps',
'/blog',
'/privacidade',
];
Expand Down
Loading
Loading