From 2e330b0dfd5a3684fc7da8c59b20983699c40010 Mon Sep 17 00:00:00 2001 From: codemenschendev Date: Sat, 26 Sep 2026 15:20:12 +0700 Subject: [PATCH] design(web): app idea page in the home page look /apps/[slug] was the old layout: an emoji icon that shows as a box on machines without an emoji font, three reason cards that left an empty fourth cell, a plain table and a thin price box. - Head band with the idea's line icon (the same map as the home page cards, now exported from AppIdeas), name, badges and the lede. - Reasons are numbered cards in one row, the market is a card list, the audience gets green ticks instead of emojis. - The price card carries the idea's picture from the home page, the price large on a tinted row and the CTA with an arrow. On a phone it moves up under the head. Project: Appwerk --- .../app/(site)/[locale]/apps/[slug]/page.tsx | 158 +++++++++--------- apps/web/src/app/detail.css | 66 ++++++++ apps/web/src/components/AppIdeas.tsx | 2 +- 3 files changed, 147 insertions(+), 79 deletions(-) create mode 100644 apps/web/src/app/detail.css diff --git a/apps/web/src/app/(site)/[locale]/apps/[slug]/page.tsx b/apps/web/src/app/(site)/[locale]/apps/[slug]/page.tsx index 2e99a35..b61fbd2 100644 --- a/apps/web/src/app/(site)/[locale]/apps/[slug]/page.tsx +++ b/apps/web/src/app/(site)/[locale]/apps/[slug]/page.tsx @@ -3,6 +3,10 @@ import { notFound } from "next/navigation"; import { CATALOG, getEntry } from "@/lib/catalog"; import { DELIVERY_DAYS_HI, DELIVERY_DAYS_LO, HOSTING_MONTHLY } from "@ai-factory/pricing"; import { LOCALES, eur, getDict, isLocale, t, type Locale } from "@/lib/i18n"; +import { IDEA_ICONS } from "@/components/AppIdeas"; +import { Icon } from "@/components/LineIcon"; +import "../../../../prototype.css"; +import "../../../../detail.css"; export function generateStaticParams() { return LOCALES.flatMap((locale) => @@ -29,108 +33,106 @@ export default async function AppDetail({ const hosting = HOSTING_MONTHLY[app.appType ?? "B"]; return ( -
-

- - {d.detail.back} - -

- -
- - {app.icon} - -

{app.name}

- {t(app.cat, locale)} - {d.detail.sample} +
+
+
+ + {d.detail.back} + +
+ + + +
+

{app.name}

+

+ {t(app.cat, locale)} + {d.detail.sample} +

+
+
+ {app.lede &&

{t(app.lede, locale)}

} +
-
-
- {app.lede &&

{t(app.lede, locale)}

} - +
+
{app.why && ( - <> -

{d.detail.why}

-
- {app.why.map((w) => ( -
+
+

{d.detail.why}

+
+ {app.why.map((w, i) => ( +
+ {i + 1}

{t(w.h, locale)}

-

- {t(w.p, locale)} -

+

{t(w.p, locale)}

))}
- +
)} - {/* Guarded — the appwerk prototype crashed on entries without market + {/* Guarded: the appwerk prototype crashed on entries without market data (rechni bug); here the section simply doesn't render. */} {app.market && app.market.length > 0 && ( - <> -

{d.detail.market}

-
- - - {app.market.map((row) => ( - - - - - ))} - -
{t(row[0], locale)}{t(row[1], locale)}
-
- +
+

{d.detail.market}

+
+ {app.market.map((row) => ( +
+
{t(row[0], locale)}
+
{t(row[1], locale)}
+
+ ))} +
+
)} {app.aud && ( - <> -

{d.detail.aud}

-
    +
    +

    {d.detail.aud}

    +
      {app.aud.map((a) => (
    • - {a.i} {locale === "de" ? a.de : a.en} + + {locale === "de" ? a.de : a.en}
    • ))}
    - +
    )} -

    - {d.detail.estimateNote} -

    +

    {d.detail.estimateNote}

-
diff --git a/apps/web/src/app/detail.css b/apps/web/src/app/detail.css new file mode 100644 index 0000000..d576dcf --- /dev/null +++ b/apps/web/src/app/detail.css @@ -0,0 +1,66 @@ +/* An app idea (/[locale]/apps/[slug]) in the look of the home page: head band with the idea's + icon, the reasons and the market in cards, the price card with the idea's picture beside them. + Scoped under `.ad`; tokens come from globals.css. */ + +.ad { padding-bottom: 80px; } +.ad p { margin: 0; } +.ad .wrap { max-width: 1200px; } + +/* ---------- Head ---------- */ +.ad-band { padding: 28px 0 40px; } +.ad-back { display: inline-block; font-size: 0.88rem; font-weight: 600; color: var(--ink-soft); text-decoration: none; margin-bottom: 18px; } +.ad-back:hover { color: var(--accent); } +.ad-head { display: flex; align-items: center; gap: 18px; } +.ad-head h1 { font-size: clamp(2.1rem, 4vw, 3rem); letter-spacing: -0.04em; margin: 0 0 8px; } +.ad-ico { flex: none; width: 64px; height: 64px; border-radius: 18px; display: grid; place-items: center; background: #fff; color: var(--accent); box-shadow: 0 10px 26px rgba(15, 23, 42, 0.1); } +.ad-ico .ico { width: 30px; height: 30px; } +.ad-badges { display: flex; flex-wrap: wrap; gap: 8px; } +.ad-badge { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-soft); background: #fff; border: 1px solid var(--border); border-radius: 99px; padding: 4px 11px; } +.ad-badge-sample { color: var(--warn-ink); background: var(--warn-bg); border-color: transparent; } +.ad .ad-lede { font-size: 1.12rem; color: var(--ink-soft); line-height: 1.6; max-width: 46em; margin-top: 22px; } + +/* ---------- Body ---------- */ +.ad-cols { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: 32px; align-items: start; } +.ad-main { min-width: 0; } +.ad .ad-sec { margin-top: 40px; padding: 0; } +.ad .ad-sec:first-child { margin-top: 8px; } +.ad-sec h2 { font-size: 1.5rem; margin: 0 0 16px; } + +.ad-why { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; } +.ad-card { background: #fff; border: 1px solid var(--border); border-radius: 16px; padding: 20px; box-shadow: var(--shadow); } +.ad-card h3 { font-size: 1rem; line-height: 1.3; margin: 12px 0 8px; } +.ad-card p { font-size: 0.88rem; color: var(--ink-soft); line-height: 1.55; } +.ad-num { width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; background: linear-gradient(135deg, var(--accent), var(--violet)); color: #fff; font-size: 0.8rem; font-weight: 800; } + +.ad-market { margin: 0; background: #fff; border: 1px solid var(--border); border-radius: 16px; box-shadow: var(--shadow); overflow: hidden; } +.ad-market div { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 16px; padding: 14px 20px; border-top: 1px solid var(--border); } +.ad-market div:first-child { border-top: 0; } +.ad-market dt { font-weight: 600; font-size: 0.92rem; } +.ad-market dd { margin: 0; font-size: 0.92rem; color: var(--ink-soft); line-height: 1.55; } + +.ad-aud { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; } +.ad-aud li { display: flex; align-items: center; gap: 10px; font-size: 0.98rem; } +.ad .ad-note { margin-top: 32px; background: var(--tint); border-radius: 12px; padding: 12px 16px; font-size: 0.84rem; color: var(--ink-soft); line-height: 1.5; } + +/* ---------- Price card ---------- */ +.ad-price { position: sticky; top: 92px; margin-top: 8px; background: #fff; border: 1px solid var(--border); border-radius: 20px; overflow: hidden; box-shadow: 0 30px 70px rgba(15, 23, 42, 0.1); } +.ad-price img { display: block; width: 100%; height: 180px; object-fit: cover; } +.ad-price-body { padding: 20px 22px 22px; display: grid; gap: 12px; } +.ad-price-fig { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; background: var(--tint); border-radius: 12px; padding: 14px 16px; } +.ad-price-fig span { font-size: 0.88rem; color: var(--ink-soft); } +.ad-price-fig strong { white-space: nowrap; font-family: var(--font-display); font-size: 1.7rem; font-weight: 800; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; } +.ad-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; font-size: 0.92rem; } +.ad-row span { color: var(--ink-soft); } +.ad-row strong { font-variant-numeric: tabular-nums; } +.ad-cta { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 4px; padding: 14px 20px; } + +@media (max-width: 900px) { + .ad-cols { grid-template-columns: minmax(0, 1fr); } + .ad-price { position: static; order: -1; } +} +@media (max-width: 640px) { + .ad-head { align-items: flex-start; } + .ad-ico { width: 52px; height: 52px; border-radius: 14px; } + .ad-market div { grid-template-columns: minmax(0, 1fr); gap: 4px; } + .ad-badge { border-radius: 8px; font-size: 0.68rem; } +} diff --git a/apps/web/src/components/AppIdeas.tsx b/apps/web/src/components/AppIdeas.tsx index 9b2d49b..b005c63 100644 --- a/apps/web/src/components/AppIdeas.tsx +++ b/apps/web/src/components/AppIdeas.tsx @@ -4,7 +4,7 @@ import { eur, t, type Dict, type Locale } from "@/lib/i18n"; import { Icon } from "@/components/LineIcon"; /** One icon per app idea, matching what the app does. */ -const IDEA_ICONS: Record = { +export const IDEA_ICONS: Record = { formpilot: "doc", mealgrid: "fork", countbee: "box",