diff --git a/app/page.tsx b/app/page.tsx index 3c7303b..a8dd538 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -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 ( -
-
-
- - - Next.js shell ready - -

- AdConsole -

-

- Amazon PPC teaching simulator. The legacy static page is preserved - at /adconsole.html{" "} - while we port it into React components. Pick a path below to keep - moving. -

-
- -
- -
-

- Open legacy page -

- - → - -
-

- Jump straight into the original AdConsole HTML — works exactly as - before, no rebuild required. -

- {stats && ( -
-
-
Size
-
- {stats.kb} KB -
-
-
-
Last modified
-
- {stats.modified} -
-
-
- )} -
- -
-

- Build the React port -

-

- Start migrating the simulator into app/{" "} - route by route. The static HTML is the source of truth until the - port lands. -

-
    -
  • - - app/page.tsx — landing shell -
  • -
  • - - app/simulator/page.tsx — main UI (TBD) -
  • -
  • - - public/adconsole.html — legacy reference -
  • -
-
-
- - -
-
- ); + // Redirect directly to the legacy tool + redirect("/adconsole.html"); } diff --git a/eslint.config.mjs b/eslint.config.mjs index 05e726d..f23fd1e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -12,6 +12,8 @@ const eslintConfig = defineConfig([ "out/**", "build/**", "next-env.d.ts", + // Project-specific ignores: + ".ijfw/**", ]), ]); diff --git a/next.config.ts b/next.config.ts index e9ffa30..1655fec 100644 --- a/next.config.ts +++ b/next.config.ts @@ -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; diff --git a/public/adconsole.html b/public/adconsole.html index 4c2de51..132d5c5 100644 --- a/public/adconsole.html +++ b/public/adconsole.html @@ -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} @@ -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)} @@ -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} @@ -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))} @@ -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} @@ -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} @@ -546,6 +550,11 @@ +
+ Migration in Progress: This simulator is being migrated to React. + View the new React version on GitHub → +
+
-