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
Binary file added public/images/tools/tool-bid-elevator.jpg
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/tools/tool-campaign-builder.jpg
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/tools/tool-keyword-research.jpg
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/tools/tool-listing-audit.jpg
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/tools/tool-str-triage.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
344 changes: 327 additions & 17 deletions src/app/home.module.css

Large diffs are not rendered by default.

224 changes: 147 additions & 77 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,81 @@
import Link from 'next/link';
import Image from 'next/image';
import { BRAND_NAME, BRAND_NAME_UPPER } from '@/lib/brand';
import { Badge } from '@/components/ui';
import { Icon, type PhosphorIconName } from '@/components/ui/Icon';
import { RevealSection } from '@/components/RevealSection';
import styles from './home.module.css';

interface SimMeta {
id: 'campaign-builder' | 'bid-elevator' | 'str-triage' | 'listing-audit' | 'keyword-research';
name: string;
description: string;
icon: PhosphorIconName;
screenshot: string;
}

const SIMULATORS: SimMeta[] = [
{
id: 'campaign-builder',
name: 'Campaign Builder',
description:
'Build Sponsored Products, Sponsored Brands, Sponsored Display, and Sponsored TV campaigns. Practice the full Amazon Ads Console campaign wizard, start to finish.',
icon: 'Rocket',
screenshot: '/images/tools/tool-campaign-builder.jpg',
},
{
id: 'bid-elevator',
name: 'Bid Elevator',
description:
'Adjust keyword bids against real performance data. Cut waste, raise your converters, defend ACoS at target.',
icon: 'ChartLine',
screenshot: '/images/tools/tool-bid-elevator.jpg',
},
{
id: 'str-triage',
name: 'Search Term Triage',
description:
'Keep, pause, negate, or re-bid search terms. Practice the weekly triage workflow every PPC specialist runs.',
icon: 'List',
screenshot: '/images/tools/tool-str-triage.jpg',
},
{
id: 'listing-audit',
name: 'Listing Audit',
description:
"Score a product listing on title, bullets, images, and A+ content. Find what's actually hurting conversion.",
icon: 'BookOpen',
screenshot: '/images/tools/tool-listing-audit.jpg',
},
{
id: 'keyword-research',
name: 'Keyword Research',
description:
'Categorize keywords as primary, secondary, or negative. Build the keyword list that drives every Sponsored Products campaign.',
icon: 'MagnifyingGlass',
screenshot: '/images/tools/tool-keyword-research.jpg',
},
];
Comment on lines +17 to +58

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n \
  -g '*.test.ts' -g '*.test.tsx' -g '*.spec.ts' -g '*.spec.tsx' \
  'HomePage|SIMULATORS|Campaign Builder|Bid Elevator|Search Term Triage|Listing Audit|Keyword Research' .

Repository: projectamazonph/amph-v2

Length of output: 161


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== page.tsx outline ==\n'
ast-grep outline src/app/page.tsx --view expanded || true

printf '\n== relevant tests/files ==\n'
git ls-files 'src/**/*test.*' 'src/**/*spec.*' | sed -n '1,200p'

printf '\n== search for homepage-related test coverage ==\n'
rg -n -S -g '*.test.ts' -g '*.test.tsx' -g '*.spec.ts' -g '*.spec.tsx' \
  'page\.tsx|SIMULATORS|Campaign Builder|Bid Elevator|Search Term Triage|Listing Audit|Keyword Research|HomePage|homepage' src test .

Repository: projectamazonph/amph-v2

Length of output: 1528


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== src/app/page.tsx ==\n'
wc -l src/app/page.tsx
sed -n '1,220p' src/app/page.tsx

printf '\n== files that look like homepage tests or snapshots ==\n'
fd -a -t f 'page.*(test|spec)\.(ts|tsx|js|jsx)$|homepage|home' src test . 2>/dev/null || true

Repository: projectamazonph/amph-v2

Length of output: 8837


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== grep for the simulator ids/descriptions in all repo files ==\n'
rg -n -S 'campaign-builder|bid-elevator|str-triage|listing-audit|keyword-research|Campaign Builder|Bid Elevator|Search Term Triage|Listing Audit|Keyword Research' .

printf '\n== list app tests directories ==\n'
find src -type d | sed -n '1,200p'

Repository: projectamazonph/amph-v2

Length of output: 38305


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== test files in repo ==\n'
git ls-files | rg '\.(test|spec)\.(ts|tsx|js|jsx)$' || true

printf '\n== page.tsx content ==\n'
cat -n src/app/page.tsx | sed -n '1,220p'

printf '\n== any tests near app/page ==\n'
rg -n -S 'render\(|screen\.|toMatchSnapshot|page\.tsx|Home|homepage|SIMULATORS' src/app src/components test . \
  -g '*.{test,spec}.{ts,tsx,js,jsx}' || true

Repository: projectamazonph/amph-v2

Length of output: 11258


Add a focused test for the homepage simulator cards. src/app/page.tsx:16-57 The only current coverage here is the homepage smoke check in tests/e2e/critical-path.spec.ts, which doesn’t assert the SIMULATORS data or rendered cards.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/page.tsx` around lines 16 - 57, Add focused test coverage for the
homepage simulator cards defined by SIMULATORS in the homepage component. Verify
the expected simulator metadata and that each configured simulator card is
rendered, rather than relying only on the existing smoke test in
critical-path.spec.ts.

Source: Coding guidelines


export default function HomePage() {
return (
<main id="main-content" className={styles.page}>
{/* ── Hero ─────────────────────────────────────────────── */}
<section className={styles.hero}>
<div className={styles.heroContent}>
<p className={styles.heroEyebrow}>{BRAND_NAME_UPPER}</p>
<h1 className={styles.heroTitle}>
Three courses. One outcome: become the Amazon ads specialist clients retain.
</h1>
<h1 className={styles.heroTitle}>Learn Amazon PPC the right way</h1>
<p className={styles.heroTagline}>We turn the theoretical into the practical.</p>
<p className={styles.heroSub}>
{`${BRAND_NAME} teaches the Amazon advertising skills Filipino VAs are charging `}
₱60,000–₱80,000/month for. Practice with real campaign tools, not just theory.
Not only do you learn Amazon PPC from scratch. You turn that knowledge into
workflows you can actually run on the job, before a client ever hands you
their account.
</p>
<div className={styles.heroActions}>
<Link
href="/pricing"
style={{
display: 'inline-flex',
alignItems: 'center',
padding: 'var(--space-3) var(--space-6)',
background: 'var(--accent)',
color: 'var(--accent-ink)',
borderRadius: 'var(--radius-md)',
fontWeight: 600,
}}
>
<Link href="/pricing" className={styles.btnPrimary}>
See pricing
</Link>
<Link
href="/tools/campaign-builder"
style={{
display: 'inline-flex',
alignItems: 'center',
padding: 'var(--space-3) var(--space-6)',
border: '1px solid var(--border-strong)',
color: 'var(--ink-900)',
borderRadius: 'var(--radius-md)',
fontWeight: 600,
}}
>
<Link href="/tools/campaign-builder" className={styles.btnSecondary}>
Try a tool
</Link>
</div>
Expand All @@ -53,78 +86,115 @@ export default function HomePage() {
</div>
</section>

{/* ── What You Learn ───────────────────────────────────── */}
<section className={styles.learnSection}>
<h2>What you learn</h2>
<ul className={styles.learnGrid}>
<li className={styles.learnItem}>
<h3>Campaign structure</h3>
<p>
How to build Sponsored Products campaigns that Amazon&apos;s
algorithm actually rewards.
</p>
{/* ── Pain Points ──────────────────────────────────────── */}
<RevealSection className={styles.painSection}>
<h2>Where most Amazon PPC training falls apart</h2>
<ul className={styles.painList}>
<li className={styles.painItem}>
You finish the lessons, but you&apos;ve never actually touched a campaign.
</li>
<li className={styles.learnItem}>
<h3>Bid optimization</h3>
<li className={styles.painItem}>
You know the vocabulary (ACoS, ROAS, CPC) but freeze the first time you have
to act on it.
</li>
<li className={styles.painItem}>
Clients want proof you&apos;ve used Seller Central. Nobody lets you practice
on one first.
</li>
<li className={styles.painItem}>
One mistake in a live account costs a client real money, so you never get to
make the mistakes that actually teach you something.
</li>
</ul>
</RevealSection>

{/* ── Simulators (zig-zag) ─────────────────────────────── */}
<RevealSection className={styles.simSection}>
<h2>Five simulators. Five real Amazon Ads Console workflows.</h2>
<p className={styles.simSub}>
Each one mirrors what you&apos;d actually do inside Seller Central. No client
account required.
</p>
<ul className={styles.simList}>
{SIMULATORS.map((sim) => (
<li key={sim.id} className={styles.simRow}>
<div className={styles.simScreenshot}>
<Image
src={sim.screenshot}
alt={`${sim.name} simulator screenshot`}
width={1000}
height={389}
sizes="(min-width: 768px) 50vw, 100vw"
/>
</div>
<div className={styles.simRowBody}>
<Icon name={sim.icon} size="lg" />
<h3>{sim.name}</h3>
<p>{sim.description}</p>
</div>
</li>
))}
</ul>
<p className={styles.simClose}>
Not only do you learn Amazon PPC from scratch. You turn that knowledge into
practical workflows you can apply the moment you&apos;re on the job.
</p>
</RevealSection>

{/* ── Coming Soon ──────────────────────────────────────── */}
<RevealSection className={styles.accessSection}>
<h2>Two more tools, only for enrolled students</h2>
<p className={styles.accessSub}>
Still in development. Enroll now to get access the moment they launch.
</p>
<ul className={styles.accessGrid}>
<li className={styles.accessItem}>
<div className={styles.accessItemHeader}>
<h3>Amazon Ads Console simulator</h3>
<Badge variant="default">Coming soon · Enrollees only</Badge>
</div>
<p>
When to raise bids, when to lower them, and when to leave the
campaign alone.
A full replica of the Amazon Ads Console: build campaigns, manage keywords
and bids, run reports, and get graded on scenario missions. The closest thing
to Seller Central access without a client account.
</p>
</li>
<li className={styles.learnItem}>
<h3>Search term triage</h3>
<li className={styles.accessItem}>
<div className={styles.accessItemHeader}>
<h3>Interview Lab</h3>
<Badge variant="default">Coming soon · Enrollees only</Badge>
</div>
<p>
Cut the wasted spend on irrelevant clicks without killing the
keywords that convert.
AI mock interviews, resume review, and cover letter drafts built for the
Amazon VA roles agencies actually hire for. Practice the interview, not just
the skill.
</p>
</li>
</ul>
</section>
</RevealSection>

{/* ── Pricing Preview ──────────────────────────────────── */}
<section className={styles.pricingSection}>
<RevealSection className={styles.pricingSection}>
<h2>Simple, one-time pricing</h2>
<p className={styles.pricingSub}>
No subscriptions. No hidden fees. Pay once, learn forever.
</p>
<Link
href="/pricing"
style={{
display: 'inline-flex',
alignItems: 'center',
padding: 'var(--space-3) var(--space-6)',
background: 'var(--accent)',
color: 'var(--accent-ink)',
borderRadius: 'var(--radius-md)',
fontWeight: 600,
}}
>
<Link href="/pricing" className={styles.btnPrimary}>
Compare tiers
</Link>
</section>
</RevealSection>

{/* ── CTA ──────────────────────────────────────────────── */}
<section className={styles.ctaSection}>
<RevealSection className={styles.ctaSection}>
<h2>Ready to learn the Amazon PPC work that&apos;s in demand right now?</h2>
<p>
Structured courses, real campaign tools, and a certificate that&apos;s part of the
A structured course, real campaign tools, and a certificate that&apos;s part of the
ProjectAmazonPH hiring pipeline.
</p>
<Link
href="/auth/signup"
style={{
display: 'inline-flex',
alignItems: 'center',
padding: 'var(--space-3) var(--space-6)',
background: 'var(--accent)',
color: 'var(--accent-ink)',
borderRadius: 'var(--radius-md)',
fontWeight: 600,
}}
>
<Link href="/auth/signup" className={styles.btnPrimary}>
Create your account
</Link>
</section>
</RevealSection>

{/* ── Footer ───────────────────────────────────────────── */}
<footer className={styles.footer}>
Expand Down
44 changes: 44 additions & 0 deletions src/components/RevealSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client';

import { useEffect, useRef, useState, type ReactNode } from 'react';

interface RevealSectionProps {
children: ReactNode;
className?: string;
}

/**
* Reveals once and stays revealed. Unobserving after the first match
* (instead of toggling visibility on every crossing) keeps the section
* from re-hiding if a user scrolls back up past it.
*/
export function RevealSection({ children, className }: RevealSectionProps) {
const ref = useRef<HTMLElement>(null);
const [visible, setVisible] = useState(false);

useEffect(() => {
const el = ref.current;
if (!el) return;
const observer = new IntersectionObserver(
([entry]) => {
if (!entry) return;
// Also reveal if the element is already above the viewport (e.g. a
// hash link landed past it). Without this, content scrolled past
// before ever intersecting would stay permanently invisible.
if (entry.isIntersecting || entry.boundingClientRect.top < 0) {
setVisible(true);
observer.unobserve(el);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
},
{ threshold: 0.15, rootMargin: '0px 0px -40px 0px' },
);
observer.observe(el);
return () => observer.disconnect();
}, []);
Comment on lines +15 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Add regression tests for the reveal behavior.

Cover intersection, the above-viewport fallback, cleanup, and an empty observer entry list.

As per coding guidelines, **/*.{ts,tsx,md,mdx}: “New features must include tests.”

🧰 Tools
🪛 GitHub Actions: CI / 2_Quality Gates.txt

[error] 28-28: Typecheck failed (tsc --noEmit). TS18048: 'entry' is possibly 'undefined'.

🪛 GitHub Actions: CI / Quality Gates

[error] 28-28: TypeScript (tsc) error TS18048: 'entry' is possibly 'undefined'.

🪛 GitHub Check: Quality Gates

[failure] 28-28:
'entry' is possibly 'undefined'.


[failure] 28-28:
'entry' is possibly 'undefined'.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/RevealSection.tsx` around lines 15 - 37, Add regression tests
for RevealSection covering visibility when an observer entry intersects,
visibility when boundingClientRect.top is above the viewport, cleanup via
observer disconnect/unobserve, and the empty observer entry list without
throwing. Use mocks for IntersectionObserver and verify the rendered content’s
visibility behavior.

Source: Coding guidelines


return (
<section ref={ref} className={`${className ?? ''}${visible ? ' is-visible' : ''}`}>
{children}
</section>
);
}
1 change: 1 addition & 0 deletions src/components/ui/Badge.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.badge {
display: inline-flex;
align-items: center;
align-self: flex-start;
gap: var(--space-1);
height: 22px;
padding: 0 var(--space-2);
Expand Down