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 (add static.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 render call

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"

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).