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
117 changes: 5 additions & 112 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,119 +1,12 @@
import type { Metadata } from "next";
import { promises as fs } from "node:fs";
import path from "node:path";
import { redirect } from "next/navigation";

export const metadata: Metadata = {
title: "AdConsole",
description: "Amazon PPC teaching simulator",
title: "AdConsole - Amazon PPC Simulator",
description: "Amazon PPC teaching simulator - Migrating to React",
};

async function getLegacyStats() {
try {
const filePath = path.join(process.cwd(), "public", "adconsole.html");
const stat = await fs.stat(filePath);
return {
bytes: stat.size,
kb: Math.round(stat.size / 1024),
modified: stat.mtime.toLocaleString("sv-SE"),
};
} catch (e) {
if (process.env.NODE_ENV !== "production") {
console.warn("adconsole.html not found:", e);
}
return null;
}
}

export default async function Home() {
const stats = await getLegacyStats();

return (
<div className="min-h-screen bg-gradient-to-b from-zinc-50 to-white dark:from-black dark:to-zinc-950">
<main className="mx-auto flex min-h-screen w-full max-w-5xl flex-col items-stretch justify-center gap-10 px-6 py-16 sm:px-10">
<header className="flex flex-col gap-3">
<span className="inline-flex w-fit items-center gap-2 rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1 text-xs font-medium text-emerald-700 dark:border-emerald-900/50 dark:bg-emerald-950/40 dark:text-emerald-300">
<span className="size-1.5 rounded-full bg-emerald-500" />
Next.js shell ready
</span>
<h1 className="text-4xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-50 sm:text-5xl">
AdConsole
</h1>
<p className="max-w-2xl text-base leading-7 text-zinc-600 dark:text-zinc-400">
Amazon PPC teaching simulator. The legacy static page is preserved
at <code className="rounded bg-zinc-100 px-1.5 py-0.5 text-sm dark:bg-zinc-900">/adconsole.html</code>{" "}
while we port it into React components. Pick a path below to keep
moving.
</p>
</header>

<section className="grid gap-4 sm:grid-cols-2">
<a
href="/adconsole.html"
className="group flex flex-col gap-3 rounded-2xl border border-zinc-200 bg-white p-6 transition-all hover:border-zinc-300 hover:shadow-sm dark:border-zinc-800 dark:bg-zinc-950 dark:hover:border-zinc-700"
>
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-zinc-900 dark:text-zinc-50">
Open legacy page
</h2>
<span className="text-zinc-400 transition-transform group-hover:translate-x-0.5">
</span>
</div>
<p className="text-sm leading-6 text-zinc-600 dark:text-zinc-400">
Jump straight into the original AdConsole HTML — works exactly as
before, no rebuild required.
</p>
{stats && (
<dl className="mt-2 grid grid-cols-2 gap-3 border-t border-zinc-100 pt-3 text-xs text-zinc-500 dark:border-zinc-900">
<div>
<dt className="font-medium text-zinc-400">Size</dt>
<dd className="text-zinc-700 dark:text-zinc-300">
{stats.kb} KB
</dd>
</div>
<div>
<dt className="font-medium text-zinc-400">Last modified</dt>
<dd className="text-zinc-700 dark:text-zinc-300">
{stats.modified}
</dd>
</div>
</dl>
)}
</a>

<div className="flex flex-col gap-3 rounded-2xl border border-dashed border-zinc-300 bg-white/50 p-6 dark:border-zinc-800 dark:bg-zinc-950/40">
<h2 className="text-lg font-semibold text-zinc-900 dark:text-zinc-50">
Build the React port
</h2>
<p className="text-sm leading-6 text-zinc-600 dark:text-zinc-400">
Start migrating the simulator into <code className="rounded bg-zinc-100 px-1.5 py-0.5 text-sm dark:bg-zinc-900">app/</code>{" "}
route by route. The static HTML is the source of truth until the
port lands.
</p>
<ul className="mt-2 space-y-1.5 text-sm text-zinc-600 dark:text-zinc-400">
<li className="flex items-center gap-2">
<span className="size-1.5 rounded-full bg-zinc-300 dark:bg-zinc-700" />
<code>app/page.tsx</code> — landing shell
</li>
<li className="flex items-center gap-2">
<span className="size-1.5 rounded-full bg-zinc-300 dark:bg-zinc-700" />
<code>app/simulator/page.tsx</code> — main UI (TBD)
</li>
<li className="flex items-center gap-2">
<span className="size-1.5 rounded-full bg-zinc-300 dark:bg-zinc-700" />
<code>public/adconsole.html</code> — legacy reference
</li>
</ul>
</div>
</section>

<footer className="mt-4 flex flex-wrap items-center justify-between gap-3 border-t border-zinc-200 pt-6 text-xs text-zinc-500 dark:border-zinc-900">
<span>
Next.js · App Router · TypeScript · Tailwind v4
</span>
<span>Run <code className="rounded bg-zinc-100 px-1.5 py-0.5 dark:bg-zinc-900">npm run dev</code> to start</span>
</footer>
</main>
</div>
);
// Redirect directly to the legacy tool
redirect("/adconsole.html");
}
2 changes: 2 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const eslintConfig = defineConfig([
"out/**",
"build/**",
"next-env.d.ts",
// Project-specific ignores:
".ijfw/**",
]),
]);

Expand Down
35 changes: 34 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,40 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "Content-Security-Policy",
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net",
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com",
"font-src 'self' https://cdn.jsdelivr.net https://fonts.gstatic.com",
"img-src 'self' data: https:",
"connect-src 'self'",
"frame-ancestors 'none'",
"form-action 'self'",
].join("; "),
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "X-Frame-Options",
value: "DENY",
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin",
},
],
},
];
},
};

export default nextConfig;
37 changes: 28 additions & 9 deletions public/adconsole.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
--sh2:0 4px 14px rgba(15,17,17,.14);
}
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
html{-webkit-text-size-adjust:100%}
html,body{height:100%}
body{font-family:'PT Sans',system-ui,sans-serif;background:var(--bg);color:var(--ink);font-size:14px;overflow:hidden}
Expand All @@ -45,12 +46,13 @@
.tb-left{display:flex;align-items:center;gap:12px;min-width:0}
.tb-icon{width:38px;height:38px;border-radius:6px;display:grid;place-items:center;color:#D5D9D9;border:1px solid transparent;transition:.15s;position:relative;flex:none}
.tb-icon:hover{border-color:#485769;color:#fff;background:rgba(255,255,255,.05)}
.tb-icon:focus-visible{outline:2px solid #FF9900;outline-offset:2px}
.logo{display:flex;align-items:center;gap:2px;padding:6px 8px;border-radius:6px;border:1px solid transparent;cursor:pointer;transition:.15s;text-decoration:none;flex:none}
.logo:hover{border-color:#485769}
.logo-t{color:#fff;font-family:Archivo;font-weight:700;font-size:19px;letter-spacing:-.3px;line-height:1;white-space:nowrap}
.logo-t{color:#fff;font-family:Archivo;font-weight:700;font-size:19px;letter-spacing:-.3px;line-height:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.logo-t b{color:var(--orange);font-weight:700}
.smile{display:block;margin-top:1px}
.tb-tag{font-size:11px;color:#99A5B4;border-left:1px solid #3A4553;padding-left:12px;line-height:1.25;white-space:nowrap}
.tb-tag{font-size:11px;color:#99A5B4;border-left:1px solid #3A4553;padding-left:12px;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tb-tag em{display:block;font-style:normal;color:#6E7B8B;font-size:10px;letter-spacing:.6px;text-transform:uppercase}
.tb-search{flex:1;max-width:560px;display:flex;height:38px;border-radius:6px;overflow:hidden;background:#fff;box-shadow:0 0 0 3px transparent;transition:.15s;min-width:0}
.tb-search:focus-within{box-shadow:0 0 0 3px rgba(255,153,0,.45)}
Expand All @@ -75,10 +77,11 @@
.nv-sec{font-family:Archivo;font-size:10.5px;font-weight:600;letter-spacing:1.1px;color:var(--faint);text-transform:uppercase;padding:14px 18px 6px}
.nv{display:flex;align-items:center;gap:11px;width:calc(100% - 14px);margin:1px 7px;padding:9.5px 11px;border-radius:6px;color:#3B4149;font-size:13.5px;font-weight:400;position:relative;transition:.13s;text-align:left;border-left:3px solid transparent}
.nv:hover{background:#F2F4F4;color:var(--ink)}
.nv:focus-visible{outline:2px solid #FF9900;outline-offset:-2px}
.nv.on{background:#FEF3E7;border-left-color:var(--orange);font-weight:700;color:var(--ink)}
.nv svg{flex:none;color:#5B6570}
.nv.on svg{color:var(--orange-d)}
.nv-l{flex:1;white-space:nowrap;overflow:hidden}
.nv-l{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nv-badge{font-family:Archivo;font-size:10.5px;font-weight:600;background:#EEF1F1;color:var(--sub);border-radius:9px;padding:1.5px 7px}
.nv.on .nv-badge{background:#FCE3C2;color:#8A5300}
.nv-sim{margin:18px 14px 4px;padding:11px 12px;border-radius:8px;background:linear-gradient(160deg,var(--navy) 0%,var(--navy2) 100%);color:#fff}
Expand Down Expand Up @@ -118,6 +121,7 @@
/* ---------- BUTTONS ---------- */
.btn{display:inline-flex;align-items:center;gap:7px;height:34px;padding:0 14px;border-radius:7px;border:1px solid var(--border);background:#fff;color:var(--ink);font-size:13px;font-weight:700;font-family:Archivo;transition:.15s;white-space:nowrap}
.btn:hover{background:#F7FAFA;box-shadow:0 1px 3px rgba(15,17,17,.12)}
.btn:focus-visible{outline:2px solid #FF9900;outline-offset:2px}
.btn:active{transform:translateY(1px)}
.btn-p{background:linear-gradient(180deg,#FFAD33,var(--orange));border-color:#E47911;color:var(--navy);position:relative;overflow:hidden}
.btn-p:hover{background:linear-gradient(180deg,#FFB84D,var(--orange-h))}
Expand All @@ -130,8 +134,8 @@

/* ---------- FORMS ---------- */
.inp,.sel,.tarea{height:34px;border:1px solid #8A939B;border-radius:7px;padding:0 10px;background:#fff;outline:none;transition:.13s;width:100%;min-width:0}
.inp:focus,.sel:focus,.tarea:focus,.rsel:focus{outline:2px solid #FF9900;outline-offset:1px;border-color:var(--orange);box-shadow:0 0 0 3px rgba(255,153,0,.28)}
.tarea{height:auto;padding:8px 10px;resize:vertical}
.inp:focus,.sel:focus,.tarea:focus{border-color:var(--orange);box-shadow:0 0 0 3px rgba(255,153,0,.28)}
.sel{cursor:pointer;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23565959' stroke-width='1.6' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;padding-right:28px}
label.fld{display:block;font-family:Archivo;font-size:12px;font-weight:600;color:#3B4149;margin:0 0 5px}
.fhint{font-size:11.5px;color:var(--faint);margin-top:4px}
Expand Down Expand Up @@ -207,7 +211,7 @@
.stbtn:hover{background:#EEF1F1}
.stbtn svg{color:#8A939B}

.editable{cursor:pointer;white-space:nowrap}
.editable{cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.editable .pen{opacity:0;transition:.13s;margin-left:5px;color:#8A939B;vertical-align:-1px}
.editable:hover{color:var(--link)}
.editable:hover .pen{opacity:1}
Expand Down Expand Up @@ -546,6 +550,11 @@
</head>
<body>

<div id="migration-banner" style="background: linear-gradient(90deg, #232F3E 0%, #131921 100%); color: #fff; padding: 12px 20px; text-align: center; font-size: 14px; position: relative; z-index: 100;">
<strong>Migration in Progress:</strong> This simulator is being migrated to React.
<a href="https://github.com/projectamazonph/Amazon-ad-console" target="_blank" rel="noopener noreferrer" style="color: #FF9900; text-decoration: underline; margin-left: 8px;">View the new React version on GitHub &rarr;</a>
</div>

<header id="topbar">
<div class="tb-left">
<button class="tb-icon" data-act="burger" aria-label="Toggle navigation">
Expand Down Expand Up @@ -577,7 +586,7 @@
</header>

<div id="shell">
<aside id="sidenav">
<aside id="sidenav" role="navigation" aria-label="Main navigation">
<div class="nv-sec">Advertising</div>
<button class="nv" data-act="nav" data-view="dashboard"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="1" width="6" height="6" rx="1" stroke="currentColor" stroke-width="1.6"/><rect x="9" y="1" width="6" height="6" rx="1" stroke="currentColor" stroke-width="1.6"/><rect x="1" y="9" width="6" height="6" rx="1" stroke="currentColor" stroke-width="1.6"/><rect x="9" y="9" width="6" height="6" rx="1" stroke="currentColor" stroke-width="1.6"/></svg><span class="nv-l">Dashboard</span></button>
<button class="nv" data-act="nav" data-view="campaigns"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M2 6v4l8 3V3L2 6Z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M12.5 5.5a2.5 2.5 0 0 1 0 5M4 10.5V13h2v-2" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg><span class="nv-l">Campaigns</span><span class="nv-badge" id="nvCampN">0</span></button>
Expand Down Expand Up @@ -1081,10 +1090,20 @@ <h3 style="font-size:19px;margin:8px 0 6px">All ${LESSONS.length} missions compl
function perfChart(id,campId){
const cv=document.getElementById(id);if(!cv)return;
const b=rangeBounds();const s=seriesFor(campId,b);
// Helper: create segment colorizer for day-over-day comparison
const segColor = (ctx, colorUp, colorDown) => {
const p0 = ctx.p0.parsed.y;
const p1 = ctx.p1.parsed.y;
if (p0 == null || p1 == null) return colorUp;
return p1 >= p0 ? colorUp : colorDown;
};
const ds=[
{label:'Sales',data:s.sal,borderColor:'#067D62',backgroundColor:'rgba(6,125,98,.10)',yAxisID:'y',fill:true},
{label:'Spend',data:s.sp,borderColor:'#E47911',backgroundColor:'rgba(228,121,17,.08)',yAxisID:'y'},
{label:'ACOS %',data:s.acos,borderColor:'#007185',borderDash:[5,4],yAxisID:'y1',pointStyle:'line'},
{label:'Sales',data:s.sal,borderColor:'#067D62',backgroundColor:'rgba(6,125,98,.10)',yAxisID:'y',fill:true,
segment:{borderColor:ctx=>segColor(ctx,'#067D62','#B12704')}},
{label:'Spend',data:s.sp,borderColor:'#E47911',backgroundColor:'rgba(228,121,17,.08)',yAxisID:'y',
segment:{borderColor:ctx=>segColor(ctx,'#E47911','#B12704')}},
{label:'ACOS %',data:s.acos,borderColor:'#007185',borderDash:[5,4],yAxisID:'y1',pointStyle:'line',
segment:{borderColor:ctx=>segColor(ctx,'#007185','#B12704')}},
];
charts[id]=new Chart(cv,{type:'line',data:{labels:s.labels,datasets:ds.map(d=>({...d,borderWidth:2,pointRadius:0,pointHitRadius:8,tension:.35,spanGaps:true}))},
options:{responsive:true,maintainAspectRatio:false,animation:{duration:250},interaction:{mode:'index',intersect:false},
Expand Down
Loading