diff --git a/e2e/add-person-role.spec.ts b/e2e/add-person-role.spec.ts index 8b64ef6..1e87e04 100644 --- a/e2e/add-person-role.spec.ts +++ b/e2e/add-person-role.spec.ts @@ -17,7 +17,7 @@ test("Add person submits the role that was selected", async ({ page }) => { const url = page.url(); test.skip( !url.includes("/admin") || url.includes("/sign-in") || url.includes("/auth/"), - "Dev bypass unavailable (production or flag off) — skipping.", + "Dev bypass unavailable (production or flag off), skipping.", ); await page.goto("/admin/users"); @@ -37,7 +37,7 @@ test("Add person submits the role that was selected", async ({ page }) => { // Any POST to this route carries the form. A hydrated page sends a server // action (Next-Action header, multipart body); before hydration the same - // form posts natively (url-encoded), so both shapes are accepted — the point + // form posts natively (url-encoded), so both shapes are accepted, the point // is what `role` the form submitted, not which transport carried it. let submittedBody: string | null = null; await page.route("**/admin/users**", async (route) => { diff --git a/e2e/authenticated.spec.ts b/e2e/authenticated.spec.ts index a9e6ff6..80dbdcc 100644 --- a/e2e/authenticated.spec.ts +++ b/e2e/authenticated.spec.ts @@ -97,7 +97,7 @@ async function assertRouteHealthy(page: Page, route: string) { expect( response?.status() ?? 0, - `${route} returned a server error — this is the signature of a missing migration or a broken query.`, + `${route} returned a server error, this is the signature of a missing migration or a broken query.`, ).toBeLessThan(500); const body = (await page.locator("body").innerText()).toLowerCase(); @@ -113,7 +113,7 @@ async function assertRouteHealthy(page: Page, route: string) { test.describe("operator surfaces", () => { test.beforeEach(async ({ page }) => { const signedIn = await signInAs(page, "admin"); - test.skip(!signedIn, "Dev bypass unavailable (production or flag off) — skipping."); + test.skip(!signedIn, "Dev bypass unavailable (production or flag off), skipping."); }); for (const route of ADMIN_ROUTES) { @@ -132,7 +132,7 @@ test.describe("operator surfaces", () => { // environment it means migrations are pending and must be applied. expect( body, - "Site content editor is in its degraded state — the database is missing migrations. Run `npm run db:migrate:deploy`.", + "Site content editor is in its degraded state, the database is missing migrations. Run `npm run db:migrate:deploy`.", ).not.toContain("Site content unavailable"); await expect(page.getByText("Search & SEO")).toBeVisible(); @@ -142,7 +142,7 @@ test.describe("operator surfaces", () => { test.describe("buyer portal", () => { test.beforeEach(async ({ page }) => { const signedIn = await signInAs(page, "buyer"); - test.skip(!signedIn, "Dev bypass unavailable (production or flag off) — skipping."); + test.skip(!signedIn, "Dev bypass unavailable (production or flag off), skipping."); }); for (const route of PORTAL_ROUTES) { diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts index 2baa504..5a27976 100644 --- a/e2e/smoke.spec.ts +++ b/e2e/smoke.spec.ts @@ -72,7 +72,7 @@ for (const route of PUBLIC_ROUTES) { // A 5xx is the exact signature of the production incident. expect( response?.status() ?? 0, - `${route} returned a server error — check the server logs for the underlying exception.`, + `${route} returned a server error, check the server logs for the underlying exception.`, ).toBeLessThan(500); const body = (await page.locator("body").innerText()).toLowerCase(); @@ -145,7 +145,7 @@ test("footer navigation links stack instead of running together", async ({ page expect(result.linkCount, "expected grouped footer navigation links").toBeGreaterThan(2); expect( result.collisions, - "footer links in the same column share a line — inline-level boxes in a non-flex container again", + "footer links in the same column share a line, inline-level boxes in a non-flex container again", ).toEqual([]); // The 44px tap target applies on phones only: above `sm` the links diff --git a/scripts/check-migrations.mjs b/scripts/check-migrations.mjs index c893333..7222235 100644 --- a/scripts/check-migrations.mjs +++ b/scripts/check-migrations.mjs @@ -1,6 +1,6 @@ // Fails (exit 1) if the target database has migrations that have not been applied, // or has drifted from the Prisma schema. Run this against a database BEFORE serving -// application code that depends on the latest schema — it is the guard that would +// application code that depends on the latest schema, it is the guard that would // have caught the production "column does not exist" error. // // Usage (reads DATABASE_URL/DIRECT_URL from the environment): diff --git a/scripts/generate-migration-manifest.mjs b/scripts/generate-migration-manifest.mjs index ae99b2b..1876577 100644 --- a/scripts/generate-migration-manifest.mjs +++ b/scripts/generate-migration-manifest.mjs @@ -9,7 +9,7 @@ * live database against a list of expected migrations. That list used to be * hand-maintained and silently went stale: it stopped at 0035 while the repo * grew to 0049, so readyz reported `migrations: ok` while production was - * missing 14 migrations — including 0042, whose absent columns produced a + * missing 14 migrations, including 0042, whose absent columns produced a * P2022 "column does not exist" 500 in production. * * A generated manifest cannot go stale. `--check` fails CI when someone adds @@ -47,7 +47,7 @@ function readMigrationNames() { function renderManifest(names) { const entries = names.map((name) => ` ${JSON.stringify(name)},`).join("\n"); - return `// GENERATED FILE — DO NOT EDIT BY HAND. + return `// GENERATED FILE, DO NOT EDIT BY HAND. // Regenerate with: node scripts/generate-migration-manifest.mjs // // Every migration directory in prisma/migrations, used by the readiness diff --git a/src/app/(admin)/admin/announcements/page.tsx b/src/app/(admin)/admin/announcements/page.tsx index 68d823a..28b9dfe 100644 --- a/src/app/(admin)/admin/announcements/page.tsx +++ b/src/app/(admin)/admin/announcements/page.tsx @@ -15,7 +15,7 @@ export default async function AdminAnnouncementsPage() {
diff --git a/src/app/(admin)/admin/benchmarks/page.tsx b/src/app/(admin)/admin/benchmarks/page.tsx index 02125a8..d75a672 100644 --- a/src/app/(admin)/admin/benchmarks/page.tsx +++ b/src/app/(admin)/admin/benchmarks/page.tsx @@ -75,7 +75,7 @@ function BenchmarkBar({ if (band.sampleSize < 3) { return (

- Platform data unavailable — not enough agencies in distribution yet. + Platform data unavailable, not enough agencies in distribution yet.

); } @@ -181,7 +181,7 @@ function MetricComparisonPanel({ Percentile
- {band.sampleSize >= 3 ? `${band.tenantPercentileRank}th` : "—"} + {band.sampleSize >= 3 ? `${band.tenantPercentileRank}th` : "-"}
@@ -361,7 +361,7 @@ export default async function AdminBenchmarksPage() { value: report.topPropertyTypes[0]?.type ? report.topPropertyTypes[0].type.replaceAll("_", " ").toLowerCase() - : "—", + : "-", hint: report.topPropertyTypes[0] ? `${report.topPropertyTypes[0].tenantDealCount} deal${report.topPropertyTypes[0].tenantDealCount === 1 ? "" : "s"} · ${formatCurrency(report.topPropertyTypes[0].tenantRevenue)} revenue` @@ -373,7 +373,7 @@ export default async function AdminBenchmarksPage() { {/* Anonymisation notice */} - Conversion rate — above + Conversion rate, above the median means your team qualifies and closes buyer interest more effectively than most. @@ -444,7 +444,7 @@ export default async function AdminBenchmarksPage() {
  • - Deal velocity — lower + Deal velocity, lower is better. Faster closures reduce buyer drop-off risk and improve cash collection. @@ -452,7 +452,7 @@ export default async function AdminBenchmarksPage() {
  • - Default rate — lower + Default rate, lower is better. A high default rate signals collection problems or over-stretched buyers. @@ -461,7 +461,7 @@ export default async function AdminBenchmarksPage() { The shaded bar is the - interquartile range — the middle 50% of all agencies. Being + interquartile range, the middle 50% of all agencies. Being inside the band is normal; above p75 is strong performance.
  • diff --git a/src/app/(admin)/admin/contracts/page.tsx b/src/app/(admin)/admin/contracts/page.tsx index cc41e25..bf1c49c 100644 --- a/src/app/(admin)/admin/contracts/page.tsx +++ b/src/app/(admin)/admin/contracts/page.tsx @@ -94,7 +94,7 @@ function UploadContractForm({ transactions }: { transactions: TransactionWithout const buyer = [tx.user.firstName, tx.user.lastName].filter(Boolean).join(" ") || "Buyer"; return ( ); })} diff --git a/src/app/(admin)/admin/feasibility/[calculationId]/report/page.tsx b/src/app/(admin)/admin/feasibility/[calculationId]/report/page.tsx index b00124b..522c0ca 100644 --- a/src/app/(admin)/admin/feasibility/[calculationId]/report/page.tsx +++ b/src/app/(admin)/admin/feasibility/[calculationId]/report/page.tsx @@ -81,7 +81,7 @@ export default async function AdminFeasibilityReportPage({ } aiNarrative = chunks.join(""); } catch { - // non-critical — omit from report if generation fails + // non-critical, omit from report if generation fails } } diff --git a/src/app/(admin)/admin/invoices/page.tsx b/src/app/(admin)/admin/invoices/page.tsx index f927734..0ef4f3a 100644 --- a/src/app/(admin)/admin/invoices/page.tsx +++ b/src/app/(admin)/admin/invoices/page.tsx @@ -15,7 +15,7 @@ export default async function AdminInvoicesPage() {
    diff --git a/src/app/(admin)/admin/leads/actions.ts b/src/app/(admin)/admin/leads/actions.ts index 7f6e625..639f68a 100644 --- a/src/app/(admin)/admin/leads/actions.ts +++ b/src/app/(admin)/admin/leads/actions.ts @@ -22,7 +22,7 @@ export async function sendDraftReplyAction( html: `

    ${draftText.replace(/&/g, "&").replace(//g, ">")}

    `, }); - // Sending a reply is a follow-up action — update lastFollowedUpAt on any + // Sending a reply is a follow-up action, update lastFollowedUpAt on any // active transaction belonging to this inquiry's buyer. if (inquiryId && featureFlags.hasDatabase) { const inquiry = await prisma.inquiry.findFirst({ diff --git a/src/app/(admin)/admin/listings/marketplace/page.tsx b/src/app/(admin)/admin/listings/marketplace/page.tsx index 0d50b32..9f62e44 100644 --- a/src/app/(admin)/admin/listings/marketplace/page.tsx +++ b/src/app/(admin)/admin/listings/marketplace/page.tsx @@ -22,7 +22,7 @@ export default async function AdminMarketplacePage() {
    @@ -40,7 +40,7 @@ export default async function AdminMarketplacePage() { {properties.length > 0 ? ( @@ -76,7 +76,7 @@ export default async function AdminMarketplacePage() { {property.title}
    - {property.locationSummary ?? "—"} + {property.locationSummary ?? "-"}
    diff --git a/src/app/(admin)/admin/marketer/page.tsx b/src/app/(admin)/admin/marketer/page.tsx index 83ac8c8..d6ca290 100644 --- a/src/app/(admin)/admin/marketer/page.tsx +++ b/src/app/(admin)/admin/marketer/page.tsx @@ -12,7 +12,7 @@ export default async function AdminMarketerPage() { diff --git a/src/app/(admin)/admin/overview/page.tsx b/src/app/(admin)/admin/overview/page.tsx index 3a62b19..617beb1 100644 --- a/src/app/(admin)/admin/overview/page.tsx +++ b/src/app/(admin)/admin/overview/page.tsx @@ -24,7 +24,7 @@ export default async function AdminOverviewPage() { diff --git a/src/app/(admin)/admin/settings/page.tsx b/src/app/(admin)/admin/settings/page.tsx index 5cd0907..37686c1 100644 --- a/src/app/(admin)/admin/settings/page.tsx +++ b/src/app/(admin)/admin/settings/page.tsx @@ -45,7 +45,7 @@ export default async function AdminSettingsPage() { title="Settings" subtitle="Manage tenant branding, defaults, payment display rules, and public staff visibility without developer intervention." > - {/* Domain card — top priority */} + {/* Domain card, top priority */} {readiness ? ( diff --git a/src/app/(admin)/admin/team/commission-rules/actions.ts b/src/app/(admin)/admin/team/commission-rules/actions.ts index ec8aa96..c301929 100644 --- a/src/app/(admin)/admin/team/commission-rules/actions.ts +++ b/src/app/(admin)/admin/team/commission-rules/actions.ts @@ -21,7 +21,7 @@ export async function createCommissionRuleAction(formData: FormData) { const propertyId = (formData.get("propertyId") as string | null)?.trim() || null; if (!name || !feeType || !["FLAT", "PERCENTAGE"].includes(feeType)) { - return; // basic guard — real validation happens client-side + return; // basic guard, real validation happens client-side } const flatAmount = diff --git a/src/app/(admin)/admin/users/page.tsx b/src/app/(admin)/admin/users/page.tsx index 2847acf..db6a6e2 100644 --- a/src/app/(admin)/admin/users/page.tsx +++ b/src/app/(admin)/admin/users/page.tsx @@ -15,7 +15,7 @@ export default async function AdminUsersPage() { diff --git a/src/app/(marketing)/about/page.tsx b/src/app/(marketing)/about/page.tsx index 2bb815b..3eb3251 100644 --- a/src/app/(marketing)/about/page.tsx +++ b/src/app/(marketing)/about/page.tsx @@ -15,7 +15,7 @@ const VALUES: { icon: LucideIcon; title: string; body: string }[] = [ { icon: Eye, title: "Clarity", - body: "Listing discovery, transaction progress, payments, and documents stay visible and structured — for buyers and the team alike.", + body: "Listing discovery, transaction progress, payments, and documents stay visible and structured, for buyers and the team alike.", }, { icon: Zap, diff --git a/src/app/(marketing)/brochure/page.tsx b/src/app/(marketing)/brochure/page.tsx index f553e74..8424330 100644 --- a/src/app/(marketing)/brochure/page.tsx +++ b/src/app/(marketing)/brochure/page.tsx @@ -16,7 +16,7 @@ const INSIDE = [ { icon: ShieldCheck, title: "How we protect buyers", - body: "Our verification, documentation, and payment process — so you know exactly how your money and paperwork are handled.", + body: "Our verification, documentation, and payment process, so you know exactly how your money and paperwork are handled.", }, { icon: FileText, @@ -32,7 +32,7 @@ export default function BrochurePage() { diff --git a/src/app/(marketing)/how-it-works/page.tsx b/src/app/(marketing)/how-it-works/page.tsx new file mode 100644 index 0000000..dd427ee --- /dev/null +++ b/src/app/(marketing)/how-it-works/page.tsx @@ -0,0 +1,70 @@ +import Link from "next/link"; + +import { Container } from "@/components/shared/container"; +import { Reveal } from "@/components/shared/reveal"; +import { Button } from "@/components/ui/button"; +import { requirePublicTenantContext } from "@/lib/tenancy/context"; +import { getPublicTenantPresentation } from "@/modules/branding/service"; +import { resolveTenantSiteContent } from "@/modules/cms/site-content"; +import { getPublishedSiteContent } from "@/modules/cms/site-content-service"; + +export const dynamic = "force-dynamic"; + +/** + * The buyer journey on its own page, driven by the same CMS content the + * homepage summarises (siteContent.journey). No new copy is introduced here: + * a tenant edits the steps once and both surfaces follow. + */ +export default async function HowItWorksPage() { + const tenant = await requirePublicTenantContext(); + const presentation = await getPublicTenantPresentation(tenant); + const storedContent = await getPublishedSiteContent(tenant); + const content = resolveTenantSiteContent({ + companyName: presentation.companyName, + startPurchaseHref: "/portal", + stored: storedContent, + }); + + return ( + + +
    +
    + {presentation.companyName} +
    +

    + {content.journey.heading} +

    +

    {content.hero.subhead}

    +
    +
    + + +
      + {content.journey.steps.map((step, index) => ( +
    1. +
      + 0{index + 1} +
      +

      + {step.title} +

      +

      {step.description}

      +
    2. + ))} +
    +
    + + +
    + + + + + + +
    +
    +
    + ); +} diff --git a/src/app/(platform-marketing)/platform/properties/page.tsx b/src/app/(platform-marketing)/platform/properties/page.tsx index fcf405b..c53a607 100644 --- a/src/app/(platform-marketing)/platform/properties/page.tsx +++ b/src/app/(platform-marketing)/platform/properties/page.tsx @@ -74,7 +74,7 @@ export default async function MarketplacePage({ ) : ( "top agencies" )}{" "} - across Nigeria — all on one platform. + across Nigeria, all on one platform.

    @@ -231,7 +231,7 @@ export default async function MarketplacePage({

    EstateOS agencies can opt any verified listing into the marketplace from - their admin dashboard — zero extra cost. + their admin dashboard, zero extra cost.

    diff --git a/src/app/(portal)/portal/contracts/page.tsx b/src/app/(portal)/portal/contracts/page.tsx index 6511bd3..c38ac00 100644 --- a/src/app/(portal)/portal/contracts/page.tsx +++ b/src/app/(portal)/portal/contracts/page.tsx @@ -72,7 +72,7 @@ function ContractCard({ contract }: { contract: BuyerContractRow }) { {contract.document.fileName}

    - Sent {contract.sentAt ? formatDate(contract.sentAt, "PP") : "—"} + Sent {contract.sentAt ? formatDate(contract.sentAt, "PP") : "-"}

    {entry.type} {entry.amount > 0 ? `+${entry.amount}` : entry.amount} {entry.balanceAfter} - {entry.reference ?? "—"} + {entry.reference ?? "-"} {formatDate(entry.createdAt, "PPP p")} ))} diff --git a/src/app/api/admin/domain/route.ts b/src/app/api/admin/domain/route.ts index 740a38d..4f3df83 100644 --- a/src/app/api/admin/domain/route.ts +++ b/src/app/api/admin/domain/route.ts @@ -15,7 +15,7 @@ const schema = z.object({ customDomain: z.string().trim().optional().nullable().or(z.literal("")), }); -// ─── PATCH — save custom domain ────────────────────────────────────────────── +// ─── PATCH, save custom domain ────────────────────────────────────────────── export async function PATCH(request: Request) { let tenant: Awaited>; diff --git a/src/app/api/admin/inquiries/[inquiryId]/draft-reply/route.ts b/src/app/api/admin/inquiries/[inquiryId]/draft-reply/route.ts index fe4ddba..28bc665 100644 --- a/src/app/api/admin/inquiries/[inquiryId]/draft-reply/route.ts +++ b/src/app/api/admin/inquiries/[inquiryId]/draft-reply/route.ts @@ -64,7 +64,7 @@ export async function POST( const propertyContext = inquiry.property ? `Property enquired about: "${inquiry.property.title}"${inquiry.property.shortDescription ? `\nProperty summary: ${inquiry.property.shortDescription}` : ""}` - : "General inquiry — no specific property selected."; + : "General inquiry, no specific property selected."; const genAI = new GoogleGenerativeAI(env.GEMINI_API_KEY!); const model = genAI.getGenerativeModel({ @@ -76,10 +76,10 @@ Guidelines: - Acknowledge their specific inquiry and the property if applicable - Be helpful and express genuine interest in assisting them - Propose a clear next step (e.g. schedule a viewing or call) -- Keep it concise — 150 to 200 words +- Keep it concise, 150 to 200 words - Sign off as "The ${companyName} Team" -Output only the email body text starting with "Dear [Name]," — no subject line, no extra commentary.`, +Output only the email body text starting with "Dear [Name],", no subject line, no extra commentary.`, }); const prompt = `Buyer name: ${inquiry.fullName} diff --git a/src/app/api/admin/payment-account/route.ts b/src/app/api/admin/payment-account/route.ts index 7e2e505..8ce896f 100644 --- a/src/app/api/admin/payment-account/route.ts +++ b/src/app/api/admin/payment-account/route.ts @@ -17,7 +17,7 @@ import { } from "@/lib/rate-limit"; export const runtime = "nodejs"; -// ─── GET — fetch existing account ──────────────────────────────────────────── +// ─── GET, fetch existing account ──────────────────────────────────────────── export async function GET() { let tenant: Awaited>; @@ -39,7 +39,7 @@ export async function GET() { return ok({ account, paystackConfigured: featureFlags.hasPaystack }); } -// ─── POST — create subaccount ───────────────────────────────────────────────── +// ─── POST, create subaccount ───────────────────────────────────────────────── export async function POST(request: Request) { let tenant: Awaited>; @@ -116,7 +116,7 @@ export async function POST(request: Request) { } } -// ─── PATCH — update existing subaccount ─────────────────────────────────────── +// ─── PATCH, update existing subaccount ─────────────────────────────────────── export async function PATCH(request: Request) { let tenant: Awaited>; diff --git a/src/app/api/admin/team-invitations/route.ts b/src/app/api/admin/team-invitations/route.ts index 98f9380..415d3f0 100644 --- a/src/app/api/admin/team-invitations/route.ts +++ b/src/app/api/admin/team-invitations/route.ts @@ -23,7 +23,7 @@ const inviteSchema = z.object({ role: z.enum(["STAFF", "ADMIN", "FINANCE", "LEGAL"] as const), }); -// ─── GET — list pending invitations ────────────────────────────────────────── +// ─── GET, list pending invitations ────────────────────────────────────────── export async function GET() { let tenant: Awaited>; @@ -58,7 +58,7 @@ export async function GET() { return ok({ invitations }); } -// ─── POST — create invitation ───────────────────────────────────────────────── +// ─── POST, create invitation ───────────────────────────────────────────────── export async function POST(request: Request) { let tenant: Awaited>; diff --git a/src/app/api/dev/session/route.ts b/src/app/api/dev/session/route.ts index 655a324..5f04f9f 100644 --- a/src/app/api/dev/session/route.ts +++ b/src/app/api/dev/session/route.ts @@ -48,7 +48,7 @@ export async function GET(request: Request) { if (role && ALLOWED_ROLES.has(role as DemoSessionRole)) { // Set the role cookie first. The demo session's roles come from code, not the // database, so switching roles in dev must not depend on the DB-provisioning - // step below succeeding — otherwise a transient write hiccup silently leaves + // step below succeeding, otherwise a transient write hiccup silently leaves // the previous role in place. response.cookies.set(DEV_SESSION_COOKIE, role, { httpOnly: true, diff --git a/src/app/api/realtime/version/route.ts b/src/app/api/realtime/version/route.ts index 859e635..2ed8507 100644 --- a/src/app/api/realtime/version/route.ts +++ b/src/app/api/realtime/version/route.ts @@ -11,7 +11,7 @@ import { } from "@/lib/realtime/change-counter"; /** - * Conditional-polling version check — the cheap replacement for blind + * Conditional-polling version check, the cheap replacement for blind * 30-second router.refresh() polling. Returns the current change counter for * the caller's surface (one Redis GET). Clients re-render only when the * number moves. Auth mirrors /api/realtime/stream: the company id comes from diff --git a/src/app/app/access/page.tsx b/src/app/app/access/page.tsx index b36a6a8..41ef7fd 100644 --- a/src/app/app/access/page.tsx +++ b/src/app/app/access/page.tsx @@ -34,14 +34,14 @@ function resolveCopy(status: string | undefined, reason: string | undefined): Ac "Superadmin access is private and requires both an allowlisted email address and a persisted platform role.", reason: "Use the normal admin or buyer workspace for this account.", // Send tenant operators back to their own workspace, not the marketing - // homepage — this page is reached by an ordinary admin who simply hit a + // homepage, this page is reached by an ordinary admin who simply hit a // /superadmin URL. actionHref: "/admin", actionLabel: "Go to your dashboard", // The default bullets describe a SUSPENDED company, which is wrong here: // nothing is blocked for this user, they just aren't a platform owner. points: [ - "Your company workspace is unaffected — admin, portal, and payment actions all still work.", + "Your company workspace is unaffected, admin, portal, and payment actions all still work.", "The platform owner dashboard is reserved for EstateOS staff.", "If you reached this by mistake, continue in your own admin dashboard.", ], diff --git a/src/app/global-error.tsx b/src/app/global-error.tsx index 2f82925..469e64b 100644 --- a/src/app/global-error.tsx +++ b/src/app/global-error.tsx @@ -11,7 +11,7 @@ import { captureException } from "@/lib/sentry"; * `global-error.tsx` only renders when the ROOT layout itself throws, which * means the app's normal layout, providers, and (critically) globals.css are * NOT applied. It must therefore render its own / and rely on inline - * styles only — design tokens/Tailwind classes are unavailable here. Styling is + * styles only, design tokens/Tailwind classes are unavailable here. Styling is * kept intentionally minimal and on-brand (the same sand/ink palette values used * in globals.css) so it still reads as part of the product. * diff --git a/src/app/globals.css b/src/app/globals.css index 3597e90..68454dc 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -55,7 +55,7 @@ --radius-xl: 1.5rem; --radius-2xl: 1.75rem; --transition-fast: 160ms ease; - /* Layered elevation scale — softer, deeper, and more modern. Two stacked + /* Layered elevation scale, softer, deeper, and more modern. Two stacked shadows per tier give surfaces genuine depth instead of a flat drop. */ --shadow-xs: 0 1px 2px rgba(16, 24, 40, 0.05); --shadow-sm: 0 1px 3px rgba(16, 24, 40, 0.08), 0 1px 2px rgba(16, 24, 40, 0.05); @@ -716,7 +716,7 @@ html { } /* ---------------------------------------------------------------------------- - Dark mode — operator + buyer dashboards (system-aware). + Dark mode, operator + buyer dashboards (system-aware). First visit follows prefers-color-scheme; an explicit toggle choice is persisted and wins thereafter. `theme-dark` is toggled on ; the visual @@ -761,7 +761,7 @@ html.theme-dark .app-dark-scope { --text-secondary: #b1bccd; --text-tertiary: #96a1b4; --brand-100: #12362c; - /* Semantic status tints — dark, translucent versions so tone-colored cards + /* Semantic status tints, dark, translucent versions so tone-colored cards and banners stay legible (light text on a dark tint, not a pastel block). */ --success-50: rgba(34, 197, 94, 0.14); --success-200: rgba(34, 197, 94, 0.32); @@ -794,7 +794,7 @@ html.theme-dark .app-dark-scope { /* Remap hardcoded white surfaces inside the dashboard subtree. Translucent white overlays (bg-white/40 etc.) and white-on-dark text are intentionally - left alone — they read correctly on dark. */ + left alone, they read correctly on dark. */ html.theme-dark .app-dark-scope .admin-surface, html.theme-dark .app-dark-scope .admin-empty-surface, html.theme-dark .app-dark-scope .admin-table-shell { @@ -850,8 +850,8 @@ html.theme-dark .app-dark-scope :is(.border-red-200, .border-rose-200) { border-color: rgba(244, 63, 94, 0.3); } -/* Translucent white surfaces (bg-white/40..95) — filter bars, sidebar chips, - the search trigger, nav buttons — read as bright patches in dark mode. Remap +/* Translucent white surfaces (bg-white/40..95), filter bars, sidebar chips, + the search trigger, nav buttons, read as bright patches in dark mode. Remap the surface-weight ones to a subtle elevation. The faint overlays (bg-white/5..15) are intentionally left alone; they read fine on dark. */ html.theme-dark .app-dark-scope :is( @@ -873,7 +873,7 @@ html.theme-dark .app-dark-scope :is(.admin-skeleton, .skeleton)::after { /* Extended status palette incl. opacity variants. The deal-board stage columns use pastel surfaces like bg-orange-50/80, bg-sky-50/80, bg-amber-50/80 with - matching borders/text — these must become dark tints + light status text. */ + matching borders/text, these must become dark tints + light status text. */ html.theme-dark .app-dark-scope :is( .bg-emerald-50\/70, .bg-emerald-50\/80, .bg-emerald-50\/90, .bg-green-50\/70, .bg-green-50\/80, .bg-teal-50, .bg-teal-50\/80, .bg-teal-100 @@ -919,8 +919,8 @@ html.theme-dark .app-dark-scope :is(.border-teal-200) { border-color: rgba(16, 185, 129, 0.3); } -/* Darkest status text shades (-900/-950) — used in warning/alert headings like - the KYC "complete your profile" card — must also flip to a light, legible +/* Darkest status text shades (-900/-950), used in warning/alert headings like + the KYC "complete your profile" card, must also flip to a light, legible variant, not stay near-black on a dark tinted surface. */ html.theme-dark .app-dark-scope :is(.text-amber-900, .text-amber-950, .text-orange-900, .text-orange-950, .text-yellow-900) { color: #fcd34d; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1668009..0b16a86 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -10,7 +10,7 @@ import { THEME_INIT_SCRIPT } from "@/lib/security/csp"; import "./globals.css"; // Web-loaded, self-hosted fonts (next/font downloads + serves them from our own -// origin at build time — no external CDN, so CSP `font-src 'self'` already +// origin at build time, no external CDN, so CSP `font-src 'self'` already // covers them). These replace the previous Windows/Office-only font assumptions // (Aptos / Baskerville Old Face) that failed to load on Mac, iOS, Android, and // Linux. `display: "swap"` + next/font's automatic size-adjusted fallback keep @@ -72,7 +72,7 @@ export default function RootLayout({ {/* Pre-hydration theme bootstrap. The text lives in @/lib/security/csp - because the enforced CSP authorizes it by SHA-256 hash — keeping the + because the enforced CSP authorizes it by SHA-256 hash, keeping the script and its hash in one module (verified by csp.test.ts) means editing one without the other fails tests instead of breaking prod. */}