Landing Page Enhancement 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: Extend app/views/static/index.html.erb with five new sections (Year Heatmap, Vision, Focus Timer, Leaderboard, Gamer Dashboard, Levels & Badges) and refactor existing sections to a shared _landing_section.html.erb partial so the page stays maintainable.
Architecture: All work is in views and assets. No new controllers, models, routes. Existing six finance/lifestyle sections are migrated to a shared partial. Five new sections are added through the same partial. Mockup illustrations ship as PNGs under app/assets/images/landing/. New copy moves into config/locales/pt-BR.yml under a static.landing.* namespace.
Tech Stack: Rails 8.1 ERB views, Tailwind CSS v4, Propshaft static assets, image generation tool (for mockup PNGs).
Cross-cutting principles enforced in every task:
- DRY: every new section uses
_landing_section.html.erb. Year heatmap is its own reusable partial. - Performance: PNGs are <200KB, all below-the-fold images are
loading="lazy". Heatmap is pure DOM (~371 small divs, <16KB HTML). - Security: This is a public unauthenticated page; no user input flows in. The change set is strictly view-and-asset.
File Structure
- Modify:
app/views/static/index.html.erb - Create:
app/views/static/_landing_section.html.erb - Create:
app/views/static/_year_heatmap.html.erb - Create:
app/views/static/_vision_preview.html.erb - Create:
app/views/static/_focus_timer_preview.html.erb - Create:
app/views/static/_leaderboard_preview.html.erb - Create:
app/views/static/_gamer_dashboard_preview.html.erb - Create:
app/views/static/_levels_badges_strip.html.erb - Modify:
app/views/static/_navbar.html.erb - Modify:
app/views/static/_footer.html.erb(anchor links) - Modify:
app/assets/tailwind/application.css(heatmap utilities, scroll behavior, badge tile) - Create:
app/assets/images/landing/vision-mockup.png - Create:
app/assets/images/landing/focus-timer-mockup.png - Create:
app/assets/images/landing/leaderboard-mockup.png - Create:
app/assets/images/landing/gamer-dashboard-mockup.png - Create:
app/assets/images/landing/badge-tiles.png - Modify:
config/locales/pt-BR.yml(addstatic.landing.*namespace) - Modify:
test/system/static_index_test.rb(or create if absent)
Task 1: Build the Shared _landing_section.html.erb Partial
Files:
-
Create:
app/views/static/_landing_section.html.erb -
Step 1: Define the partial contract
The partial accepts these locals:
| Local | Required | Description |
|---|---|---|
anchor |
yes | DOM id for jump-to-anchor (e.g., "vision") |
tag |
yes | Small chip text (e.g., "Visão") |
headline |
yes | H2 heading |
subhead |
yes | Paragraph beneath the headline |
mockup |
no | Asset path under landing/ for the mockup image |
tag_color |
no | Color name from a fixed allowlist: emerald (default), violet, amber, sky. The partial maps the name to a hard-coded Tailwind class string so JIT can see it at scan time. |
align |
no | "center" (default) or "left" |
cta_text |
no | If present, render a button |
cta_path |
no | href for the CTA |
Body block (yield) renders inline content beneath the subhead and above the mockup.
- Step 2: Implement
<%# locals: anchor:, tag:, headline:, subhead:, mockup: nil, tag_color: "emerald", align: "center", cta_text: nil, cta_path: nil %>
<%
chip_classes = {
"emerald" => "border-emerald-200 bg-emerald-50 text-emerald-700",
"violet" => "border-violet-200 bg-violet-50 text-violet-700",
"amber" => "border-amber-200 bg-amber-50 text-amber-700",
"sky" => "border-sky-200 bg-sky-50 text-sky-700"
}.fetch(tag_color, "border-emerald-200 bg-emerald-50 text-emerald-700")
%>
<section id="<%= anchor %>" class="relative pt-24 pb-12 sm:pt-28 sm:pb-18 overflow-hidden">
<div class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
<div class="<%= align == "center" ? "max-w-3xl mx-auto text-center" : "max-w-3xl" %>">
<span class="inline-flex items-center gap-2 rounded-full border <%= chip_classes %> px-3 py-1 text-xs font-medium mb-6"><%= tag %></span>
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-extrabold tracking-tight leading-[1.1] mb-4 text-gray-900">
<%= headline %>
</h2>
<p class="text-lg sm:text-xl text-gray-500 max-w-2xl <%= "mx-auto" if align == "center" %> mb-8 leading-relaxed">
<%= subhead %>
</p>
<% if block_given? %>
<div class="<%= "mx-auto" if align == "center" %> mb-10"><%= yield %></div>
<% end %>
<% if cta_text && cta_path %>
<%= link_to cta_text, cta_path,
class: "inline-flex items-center gap-2 rounded-xl px-6 py-3 text-base font-semibold text-white bg-gray-900 hover:bg-gray-800 transition" %>
<% end %>
</div>
<% if mockup %>
<div class="mt-10 sm:mt-14 max-w-5xl mx-auto">
<%= image_tag mockup, alt: headline, loading: "lazy",
class: "rounded-2xl border border-gray-200 bg-white shadow-2xl shadow-gray-200/50 w-full h-auto" %>
</div>
<% end %>
</div>
</section>
- Step 3: Commit
git add app/views/static/_landing_section.html.erb
git commit -m "feat: shared landing section partial"
Task 2: Migrate Existing Six Sections to the Shared Partial
Files:
-
Modify:
app/views/static/index.html.erb -
Step 1: Identify the existing sections
Open app/views/static/index.html.erb and locate the existing feature blocks. The hero stays inline (it's not a feature section). Each existing block follows roughly this pattern:
<section class="...">
<div>
<span class="...">Tag</span>
<h2>Headline</h2>
<p>Subhead</p>
<% inline-content %>
<%= image_tag "..." %>
</div>
</section>
- Step 2: Replace each section with a
rendercall
For each existing feature section, swap the markup for:
<%= render "landing_section",
anchor: "<existing-anchor>",
tag: "<existing-tag>",
headline: "<existing-headline>",
subhead: "<existing-subhead>",
mockup: "<existing-image-path>",
cta_text: nil,
cta_path: nil do %>
<%# Move any inline content (lists, callouts) here %>
<% end %>
- Step 3: Visual parity check
Open / in a browser (run bin/dev if not already running). Confirm each migrated section matches the previous render — same text, same layout, same image. The Tailwind classes baked into the partial should reproduce the rhythm.
If a specific section had unique inline content (e.g., a feature-grid or callout), that content goes inside the block. Don't lose any copy in the migration.
- Step 4: Commit
git commit -am "refactor: migrate landing sections to shared partial"
Task 3: Year Heatmap Hero Strip
Files:
- Create:
app/views/static/_year_heatmap.html.erb - Modify:
app/assets/tailwind/application.css -
Modify:
app/views/static/index.html.erb - Step 1: Add heatmap utility classes
Append to app/assets/tailwind/application.css:
.heatmap {
display: grid;
grid-template-columns: repeat(53, minmax(0, 1fr));
gap: 3px;
}
.heatmap-cell {
width: 12px; height: 12px; border-radius: 2px;
background: rgb(243 244 246); /* gray-100 */
}
.heatmap-cell.lvl-1 { background: rgb(167 243 208); } /* emerald-200 */
.heatmap-cell.lvl-2 { background: rgb(110 231 183); } /* emerald-300 */
.heatmap-cell.lvl-3 { background: rgb(52 211 153); } /* emerald-400 */
.heatmap-cell.lvl-4 { background: rgb(16 185 129); } /* emerald-500 */
.heatmap-cell.lvl-5 { background: rgb(5 150 105); } /* emerald-600 */
- Step 2: Build the partial
The partial generates 53 weeks × 7 days of cells with realistic-looking density:
<%# locals: %>
<div class="heatmap" aria-hidden="true">
<% 53.times do |week| %>
<% 7.times do |day| %>
<% lvl = pseudo_intensity(week, day) %>
<span class="heatmap-cell <%= "lvl-#{lvl}" if lvl > 0 %>"></span>
<% end %>
<% end %>
</div>
Add a helper method in app/helpers/static_helper.rb (create if missing):
module StaticHelper
def pseudo_intensity(week, day)
seed = (week * 7 + day) * 2654435761
return 0 if (seed % 100) < 30 # 30% empty days
1 + (seed >> 16) % 5
end
end
- Step 3: Wire into index.html.erb
Add a section call right after the hero:
<%= render "landing_section",
anchor: "veja-seu-ano",
tag: t("static.landing.year_heatmap.tag"),
headline: t("static.landing.year_heatmap.headline"),
subhead: t("static.landing.year_heatmap.subhead") do %>
<%= render "year_heatmap" %>
<% end %>
- Step 4: Translations
Add to config/locales/pt-BR.yml:
pt-BR:
static:
landing:
year_heatmap:
tag: "Veja seu ano"
headline: "Cada dia conta. Cada hábito ganha cor."
subhead: "Acompanhe sua consistência ao longo de 365 dias e veja onde sua vida está acendendo."
- Step 5: Commit
git commit -am "feat: year heatmap hero strip on landing page"
Task 4: Generate Mockup PNGs
Files:
- Create:
app/assets/images/landing/vision-mockup.png - Create:
app/assets/images/landing/focus-timer-mockup.png - Create:
app/assets/images/landing/leaderboard-mockup.png - Create:
app/assets/images/landing/gamer-dashboard-mockup.png -
Create:
app/assets/images/landing/badge-tiles.png - Step 1: Ensure folder exists
mkdir -p app/assets/images/landing
- Step 2: Generate each mockup
Use the built-in image generation tool. Common base prompt:
Use case: stylized-concept
Asset type: SaaS product mockup screenshot for marketing page, saved as PNG.
Style: Modern dark dashboard, clean Tailwind aesthetic, balanced color palette, no real brand references, no copyrighted imagery, no text watermarks. Crisp typography placeholders.
Aspect ratio: 16:10 (~1600x1000), suitable for max-w-5xl marketing display.
Avoid: photographic faces (use abstract avatars), brand logos, watermarks, blur, gradient noise.
Per-mockup variant:
| File | Prompt addition |
|---|---|
vision-mockup.png |
"Vision page with 6 contemplative section cards stacked: Letter from 100, Bucket List checklist, Mission textarea, 2x3 Definition of Success grid, 3 Odyssey path cards, Future Calendar with two day-cards. Public/Private chips per section." |
focus-timer-mockup.png |
"Focus timer dashboard. Top: large total hours number with 365-day emerald heatmap. Below: session list (5-6 rows w/ duration). Right: 4 stat cards. Active timer widget bottom-right with countdown." |
leaderboard-mockup.png |
"Leaderboard page. Top: 3-avatar podium with #1 elevated and crown. Below: 'Your Position' green callout. Then a ranked list of 12 users: avatar, @handle, level, points, rank-delta chip." |
gamer-dashboard-mockup.png |
"Pixel-art-influenced dashboard. Left: player profile w/ pixel character + level + XP bar. Center: Lifehub world map with clickable zones. Right: quest feed and finance vault summary. Subtle pixel chrome." |
badge-tiles.png |
"Horizontal strip of 16 badge medals at varied rarities (bronze, silver, gold, gem, mythic) on a transparent or dark background. Distinct icons per badge. Crisp, balanced palette. No text." |
- Step 3: Verify
ls -lh app/assets/images/landing/
Each PNG should be <200KB. If any exceed 200KB, run pngquant --quality=70-85 <file> or regenerate at lower complexity.
- Step 4: Commit
git add app/assets/images/landing
git commit -m "feat: landing page mockup assets"
Task 5: Vision, Focus Timer, Leaderboard, Gamer Dashboard, Levels & Badges Sections
Files:
- Modify:
app/views/static/index.html.erb - Create:
app/views/static/_vision_preview.html.erb - Create:
app/views/static/_focus_timer_preview.html.erb - Create:
app/views/static/_leaderboard_preview.html.erb - Create:
app/views/static/_gamer_dashboard_preview.html.erb - Create:
app/views/static/_levels_badges_strip.html.erb -
Modify:
config/locales/pt-BR.yml - Step 1: Add translations
pt-BR:
static:
landing:
vision:
tag: "Visão"
headline: "Defina sua estrela-norte. Construa a vida que você quer."
subhead: "Carta dos 100, lista dos sonhos, missão pessoal, definição de sucesso e plano Odyssey — todos em um lugar."
bullets:
letter_from_100: "Carta dos 100"
bucket_list: "Lista dos sonhos"
mission: "Sua missão pessoal"
definition_of_success: "Definição de sucesso"
odyssey_plan: "Plano Odyssey"
future_calendar: "Calendário futuro"
focus_timer:
tag: "Foco"
headline: "Pomodoro com propósito."
subhead: "Sessões ligadas a tarefas, hábitos ou objetivos. Cada minuto vira XP. Veja seus picos e padrões."
highlights:
presets: "Presets Pomodoro 25/50/custom"
heatmap: "Heatmap anual"
distribution: "Distribuição por tarefa, hábito ou meta"
leaderboard:
tag: "Comunidade"
headline: "Você não está sozinho. Veja onde você está."
subhead: "Ranking mensal, hall da fama e atividade da comunidade. Mantenha-se motivado vendo seu progresso ao lado dos outros."
cta: "Veja o leaderboard ao vivo"
gamer_dashboard:
tag: "Painel Gamer"
headline: "Sua vida com cara de RPG."
subhead: "Personagens pixel, mapa do mundo Lifehub, missões diárias e próximas conquistas — tudo no painel /gamer."
levels_badges:
tag: "Conquistas"
headline: "50 níveis. 11 personagens. 65+ medalhas."
subhead: "Suba de nível enquanto vive. Cada hábito mantido, cada tarefa concluída, cada sessão de foco — tudo ganha XP."
- Step 2: Build inline-content partials
Each _<feature>_preview.html.erb is a small fragment used as the block-content of the shared section. They contain the in-section bullets/highlights, NOT the surrounding chrome. Examples:
_vision_preview.html.erb:
<ul class="grid grid-cols-2 gap-3 max-w-md mx-auto text-left">
<% %i[letter_from_100 bucket_list mission definition_of_success odyssey_plan future_calendar].each do |bullet| %>
<li class="flex items-center gap-2 text-gray-700">
<span class="size-2 rounded-full bg-emerald-500"></span>
<%= t("static.landing.vision.bullets.#{bullet}") %>
</li>
<% end %>
</ul>
_focus_timer_preview.html.erb: 3-column highlight cards with simple icons.
_leaderboard_preview.html.erb: a faux activity strip showing 3 avatar circles + actions.
_gamer_dashboard_preview.html.erb: small character + level chip preview.
_levels_badges_strip.html.erb: row of image_tag "gamer/characters/<tier>.png" + image_tag "landing/badge-tiles.png".
- Step 3: Wire into
index.html.erb
Insert in this order, after the existing migrated sections:
<%= render "landing_section",
anchor: "visao",
tag: t("static.landing.vision.tag"),
headline: t("static.landing.vision.headline"),
subhead: t("static.landing.vision.subhead"),
mockup: "landing/vision-mockup.png",
cta_text: t("static.landing.cta.start_free", default: "Começar grátis"),
cta_path: new_user_registration_path do %>
<%= render "vision_preview" %>
<% end %>
<%= render "landing_section",
anchor: "foco",
tag: t("static.landing.focus_timer.tag"),
headline: t("static.landing.focus_timer.headline"),
subhead: t("static.landing.focus_timer.subhead"),
mockup: "landing/focus-timer-mockup.png" do %>
<%= render "focus_timer_preview" %>
<% end %>
<%= render "landing_section",
anchor: "comunidade",
tag: t("static.landing.leaderboard.tag"),
headline: t("static.landing.leaderboard.headline"),
subhead: t("static.landing.leaderboard.subhead"),
mockup: "landing/leaderboard-mockup.png",
cta_text: t("static.landing.leaderboard.cta"),
cta_path: leaderboard_path_or_signup do %>
<%= render "leaderboard_preview" %>
<% end %>
<%= render "landing_section",
anchor: "gamer",
tag: t("static.landing.gamer_dashboard.tag"),
headline: t("static.landing.gamer_dashboard.headline"),
subhead: t("static.landing.gamer_dashboard.subhead"),
mockup: "landing/gamer-dashboard-mockup.png" %>
<%= render "landing_section",
anchor: "conquistas",
tag: t("static.landing.levels_badges.tag"),
headline: t("static.landing.levels_badges.headline"),
subhead: t("static.landing.levels_badges.subhead") do %>
<%= render "levels_badges_strip" %>
<% end %>
- Step 4: Helper for the leaderboard CTA path
Add to app/helpers/static_helper.rb:
def leaderboard_path_or_signup
user_signed_in? ? leaderboard_path : new_user_registration_path
end
- Step 5: Commit
git add app/views/static app/helpers/static_helper.rb config/locales/pt-BR.yml
git commit -m "feat: landing page sections for vision, focus, leaderboard, gamer, conquests"
Task 6: Update Navbar & Footer Anchors
Files:
- Modify:
app/views/static/_navbar.html.erb -
Modify:
app/views/static/_footer.html.erb - Step 1: Add new anchor links
Add Visão, Foco, Comunidade, Painel Gamer, Conquistas to the navbar's middle section. Each link is href="#anchor". On mobile, they live inside the hamburger drawer.
- Step 2: Update footer
Add the same links to the footer's "Funcionalidades" column.
- Step 3: Commit
git commit -am "feat: landing navbar and footer anchors"
Task 7: System Tests
Files:
-
Create:
test/system/static_index_test.rb -
Step 1: Author the test
require "application_system_test_case"
class StaticIndexTest < ApplicationSystemTestCase
test "landing page renders all sections without errors" do
visit root_path
%w[veja-seu-ano visao foco comunidade gamer conquistas].each do |anchor|
assert_selector "##{anchor}"
end
end
test "navbar contains anchors to all new sections" do
visit root_path
%w[#visao #foco #comunidade #gamer #conquistas].each do |href|
assert_selector "a[href='#{href}']"
end
end
test "below-the-fold mockups are lazy-loaded" do
visit root_path
assert_selector "img[loading=lazy]", minimum: 4
end
end
- Step 2: Run
bin/rails test:system test/system/static_index_test.rb
- Step 3: Commit
git commit -am "test: landing page system coverage"
Task 8: Final Verification
- Step 1: Run full CI
bin/ci
- Step 2: Visual smoke test (optional)
Open / in a browser. Walk top-to-bottom: hero → migrated finance/lifestyle sections → year heatmap → vision → focus → leaderboard → gamer → conquests → existing CTA → footer. Confirm:
- Anchor links jump to the right places.
- Mockup PNGs render and scale.
- Heatmap cells are visible.
- No console errors.
- Page weight under 1MB above the fold (DevTools → Network).