@nexpress/theme-portfolio 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/settings-helpers.ts","../src/settings.ts","../src/blocks.tsx","../src/components/project-card.tsx","../src/footer.tsx","../src/header.tsx","../src/members-not-found.tsx","../src/members-shell.tsx","../src/not-found.tsx","../src/routes/project-detail.tsx","../src/templates/project-detail.tsx","../src/shell.tsx","../src/styles.ts","../src/templates/page-default.tsx","../src/templates/page-gallery.tsx","../src/templates/project-index.tsx"],"sourcesContent":["import { defineTheme } from \"@nexpress/theme\";\n\nimport { portfolioBlocks } from \"./blocks.js\";\nimport { PortfolioMobileNav } from \"./components/mobile-nav.js\";\nimport {\n PortfolioProjectCard,\n type PortfolioProjectDoc,\n} from \"./components/project-card.js\";\nimport { PortfolioFooter } from \"./footer.js\";\nimport { PortfolioHeader } from \"./header.js\";\nimport { PortfolioMembersNotFound } from \"./members-not-found.js\";\nimport { PortfolioMembersShell } from \"./members-shell.js\";\nimport { PortfolioNotFound } from \"./not-found.js\";\nimport { PortfolioProjectDetailRoute } from \"./routes/project-detail.js\";\nimport { portfolioSettingsSchema } from \"./settings.js\";\nimport { PortfolioShell } from \"./shell.js\";\nimport { portfolioCss } from \"./styles.js\";\nimport { PageDefaultTemplate } from \"./templates/page-default.js\";\nimport { PageGalleryTemplate } from \"./templates/page-gallery.js\";\nimport { ProjectDetailTemplate } from \"./templates/project-detail.js\";\nimport { ProjectIndexTemplate } from \"./templates/project-index.js\";\n\n/**\n * `@nexpress/theme-portfolio` — image-led dark theme.\n *\n * Designed for designers / photographers / studios. Pages get a\n * centered text column or a gallery grid; \"posts\" are treated as\n * project case studies with a hero image, role / year / client\n * meta strip, and the standard block body underneath. The index\n * template renders the project archive as a 2- / 3-column grid\n * of square cards with hover-fade captions.\n *\n * Flips the surface palette: dark `--np-color-background` is\n * driven entirely from the theme's CSS (no admin override\n * required). Sites that want a light variant fork or override\n * tokens via the admin.\n */\nexport const portfolioTheme = defineTheme({\n manifest: {\n id: \"portfolio\",\n name: \"Portfolio\",\n version: \"0.1.0\",\n description:\n \"Image-led dark theme for studios and designers. Hero-led project detail template, archive grid, gallery and centered page templates.\",\n author: { name: \"NexPress\" },\n nexpress: { minVersion: \"0.1.0\" },\n // Phase F.1 — declared data-shape requirements. The CLI\n // (`pnpm nexpress theme:install @nexpress/theme-portfolio`)\n // patches operator collections to satisfy these.\n requires: {\n collections: {\n posts: {\n fields: {\n heroImage: { type: \"upload\" },\n client: { type: \"text\", hard: false },\n year: { type: \"number\", hard: false },\n role: { type: \"text\", hard: false },\n },\n },\n },\n },\n // Phase F.3 — operator-tunable settings. Stresses the\n // auto-form on deep schema (10 fields, range-constrained\n // numbers, color regex, nested array of objects).\n settingsSchema: portfolioSettingsSchema,\n },\n impl: {\n shell: PortfolioShell,\n slots: {\n header: PortfolioHeader,\n footer: PortfolioFooter,\n },\n // Dark palette is now token-driven — previously the dark\n // surface was hardcoded as `#0b0b0c` in `styles.ts`, so admin\n // overrides couldn't reach it. Tokens here flip background +\n // foreground for the whole shell; `styles.ts` reads them via\n // `var(--np-color-*)` so a single token change reflows the\n // entire theme. Light variant: override these in the admin's\n // theme settings tab — no fork required.\n tokens: {\n colors: {\n primary: \"oklch(0.985 0.001 106)\",\n primaryForeground: \"oklch(0.145 0.005 285)\",\n background: \"oklch(0.16 0.005 285)\",\n foreground: \"oklch(0.91 0.003 286)\",\n muted: \"oklch(0.22 0.006 286)\",\n mutedForeground: \"oklch(0.66 0.005 286)\",\n border: \"oklch(0.28 0.008 286)\",\n card: \"oklch(0.20 0.006 286)\",\n cardForeground: \"oklch(0.91 0.003 286)\",\n accent: \"oklch(0.32 0.012 286)\",\n accentForeground: \"oklch(0.985 0.001 106)\",\n },\n typography: {\n fontHeading:\n '\"Inter\", system-ui, -apple-system, \"Segoe UI\", sans-serif',\n fontBody:\n '\"Inter\", system-ui, -apple-system, \"Segoe UI\", sans-serif',\n },\n },\n css: portfolioCss,\n templates: {\n pages: {\n default: {\n label: \"Default\",\n description: \"Centered text column on dark background.\",\n component: PageDefaultTemplate,\n },\n gallery: {\n label: \"Gallery\",\n description:\n \"Two-column block grid for image-led project pages and case studies.\",\n component: PageGalleryTemplate,\n },\n },\n posts: {\n detail: {\n label: \"Project detail\",\n description:\n \"Hero image, centered title and excerpt, role / year / client meta strip, then the body blocks.\",\n component: ProjectDetailTemplate,\n },\n index: {\n label: \"Project index\",\n description:\n \"Archive grid of square project cards with hover-fade captions.\",\n component: ProjectIndexTemplate,\n },\n },\n },\n // F.2 — theme routes. `/work/:slug` dispatches a posts row\n // through `ProjectDetailTemplate` (#613). Without this, the\n // `/work/<slug>` URLs `PortfolioProjectCard` emits would\n // 404 — the framework catch-all only resolves `pages` rows\n // by URL, so case studies (`posts` collection) need a theme\n // route to be reachable.\n routes: [\n { pattern: \"/work/:slug\", component: PortfolioProjectDetailRoute },\n ],\n // Phase F.4 — portfolio-shipped block types.\n blocks: portfolioBlocks,\n // Phase F.6 — declared nav locations.\n navLocations: {\n primary: {\n label: \"Primary nav\",\n description: \"Top nav links (Work / About / Contact).\",\n maxItems: 5,\n },\n footerSocial: {\n label: \"Footer social links\",\n description: \"Social profile links shown in the footer.\",\n maxItems: 6,\n },\n },\n // Phase F.7 — error chrome.\n notFound: PortfolioNotFound,\n // M.* adoption (2026-05-11). Portfolio gains purpose-built\n // member chrome: narrow column wrapping the auth forms,\n // tonally matched 404 + error pages. The fallback chain in\n // `<ShellWrap surface=\"member\">` would have walked back to\n // `impl.shell` + the public slots, which would have stretched\n // a 320-wide login form across the image-led wide layout.\n // - `shell`: PortfolioMembersShell (narrow column, same\n // header/footer chrome so a masthead bump cascades).\n // - `notFound`: PortfolioMembersNotFound (stale-auth-link\n // framing with /members/login CTA).\n // - `error`: forward-compat type marker; the actual render\n // goes through `./components/members-error`'s client\n // subpath, lazy-imported by\n // `apps/web/src/app/(member)/error.tsx`'s registry\n // (F.7.1 delegation — Next mandates `error.tsx` is \"use\n // client\").\n members: {\n shell: PortfolioMembersShell,\n notFound: PortfolioMembersNotFound,\n },\n },\n});\n\nexport {\n PortfolioHeader,\n PortfolioFooter,\n PortfolioShell,\n PortfolioMembersShell,\n PortfolioMembersNotFound,\n PortfolioProjectCard,\n PortfolioMobileNav,\n PortfolioNotFound,\n};\nexport { portfolioCss };\nexport type { PortfolioProjectDoc };\nexport {\n portfolioSettingsSchema,\n type PortfolioSettings,\n} from \"./settings.js\";\n","import { getCachedThemeSettings } from \"@nexpress/next\";\n\nimport {\n portfolioSettingsSchema,\n type PortfolioSettings,\n} from \"./settings.js\";\n\n/**\n * Phase F.9.1-A — typed accessor over the cached theme settings\n * read.\n *\n * Uses `getCachedThemeSettings` so multiple resolveSettings()\n * calls in the same request (shell + header + footer + N\n * cards) share one DB hit through Next's `unstable_cache`.\n * The `nx:theme:<siteId>` tag invalidation handles freshness;\n * settings-save / theme-switch already bust it.\n */\nexport async function resolvePortfolioSettings(): Promise<PortfolioSettings> {\n const raw = await getCachedThemeSettings(\"portfolio\");\n const parsed = portfolioSettingsSchema.safeParse(raw);\n if (parsed.success) return parsed.data;\n return portfolioSettingsSchema.parse({});\n}\n","import { z } from \"zod\";\n\n/**\n * Phase F.9-C — operator-tunable portfolio settings.\n *\n * Stresses F.3's auto-form on **deep settings**: many fields,\n * mixed types, range-constrained numbers, color picker (hex\n * regex heuristic), nested array of objects with required\n * sub-fields. Combined with magazine's enum/array shape and\n * docs' text-heavy shape, this round-trips F.3's full\n * widget surface.\n */\nexport const portfolioSettingsSchema = z.object({\n // Layout\n gridColumns: z\n .number()\n .int()\n .min(1)\n .max(6)\n .default(3)\n .describe(\"Number of columns in the project archive grid (1–6).\"),\n cardAspect: z\n .enum([\"square\", \"portrait\", \"landscape\", \"golden\"])\n .default(\"square\")\n .describe(\n \"Aspect ratio of project cards: square (1:1), portrait (3:4), landscape (4:3), or golden (1:1.618).\",\n ),\n hoverStyle: z\n .enum([\"fade\", \"scale\", \"slide\", \"lift\"])\n .default(\"fade\")\n .describe(\n \"Hover effect on project cards. fade: caption fades in. scale: image zooms 1.05x. slide: caption slides up. lift: card lifts with shadow.\",\n ),\n galleryGutter: z\n .number()\n .int()\n .min(0)\n .max(64)\n .default(16)\n .describe(\"Gap between project cards in pixels (0–64).\"),\n // Project meta\n showProjectMeta: z\n .boolean()\n .default(true)\n .describe(\"Show role / year / client meta strip on project detail pages.\"),\n showProjectTags: z\n .boolean()\n .default(false)\n .describe(\"Show tag chips below project titles on the index grid.\"),\n // Brand\n accentColor: z\n .string()\n .regex(/^#[0-9a-f]{6}$/i)\n .optional()\n .describe(\n \"Optional accent color override (hex). Used for hover states and the masthead underline.\",\n ),\n studioName: z\n .string()\n .default(\"Studio\")\n .describe(\"Studio / personal name shown in the masthead and footer.\"),\n aboutCopy: z\n .string()\n .default(\"\")\n .meta({ widget: \"textarea\", rows: 4 })\n .describe(\n \"Optional short bio for the studio. Renders as a multi-line textarea in admin (4 rows) and as a small paragraph above the footer contact line on the public site.\",\n ),\n // Footer\n showFooterCredit: z\n .boolean()\n .default(true)\n .describe(\n \"Show 'Built with NexPress' credit in the footer. Some studios prefer an unbranded footer.\",\n ),\n copyrightYear: z\n .number()\n .int()\n .min(2000)\n .max(2100)\n .optional()\n .describe(\n \"Optional fixed copyright year. Defaults to the current year when omitted.\",\n ),\n // Client logos\n clientLogos: z\n .array(\n z.object({\n name: z.string().describe(\"Client name (alt text + caption)\"),\n logoUrl: z.string().url().describe(\"Logo image URL\"),\n link: z.string().url().optional().describe(\"Optional case-study link\"),\n }),\n )\n .default([])\n .describe(\n \"Client logos rendered in the homepage 'Selected clients' strip. Edit per project.\",\n ),\n});\n\nexport type PortfolioSettings = z.infer<typeof portfolioSettingsSchema>;\n","import * as React from \"react\";\nimport type { NpBlockDefinition } from \"@nexpress/blocks\";\n\nimport { resolvePortfolioSettings } from \"./settings-helpers.js\";\n\n/**\n * Phase F.9-C — portfolio-specific block types.\n *\n * Type prefix: `portfolio.*`. Bootstrap auto-stamps\n * `source: \"theme:portfolio\"` so multi-site processes scope\n * these correctly.\n *\n * Two blocks shipping with v0.2:\n * - `portfolio.case-study-hero`: full-bleed image + project\n * meta. Drop at the top of a case-study page.\n * - `portfolio.image-grid`: responsive image gallery. The\n * editorial alternative to a regular gridBlock for\n * image-heavy case studies.\n */\n\ninterface CaseStudyHeroProps {\n title: string;\n subtitle?: string;\n client?: string;\n year?: string;\n role?: string;\n imageUrl?: string;\n}\n\nfunction CaseStudyHero(props: Record<string, unknown>): React.ReactElement {\n const { title, subtitle, client, year, role, imageUrl } =\n props as unknown as CaseStudyHeroProps;\n return (\n <section\n className=\"np-portfolio-case-study-hero\"\n style={{\n position: \"relative\",\n margin: \"0 0 2rem\",\n padding: 0,\n minHeight: imageUrl ? \"60vh\" : \"auto\",\n backgroundImage: imageUrl ? `url(${imageUrl})` : undefined,\n backgroundSize: \"cover\",\n backgroundPosition: \"center\",\n color: imageUrl ? \"white\" : \"inherit\",\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"flex-end\",\n }}\n >\n <div\n style={{\n padding: \"3rem 1.5rem 2rem\",\n background: imageUrl\n ? \"linear-gradient(180deg, transparent, rgba(0,0,0,0.65))\"\n : undefined,\n }}\n >\n <h1\n style={{\n fontFamily: \"var(--np-font-heading)\",\n fontSize: \"clamp(2rem, 5vw, 3.75rem)\",\n fontWeight: 600,\n margin: 0,\n letterSpacing: \"-0.02em\",\n }}\n >\n {title}\n </h1>\n {subtitle ? (\n <p\n style={{\n margin: \"0.75rem 0 0\",\n fontSize: \"1.125rem\",\n maxWidth: \"60ch\",\n opacity: 0.9,\n }}\n >\n {subtitle}\n </p>\n ) : null}\n {(client || year || role) ? (\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"2rem\",\n marginTop: \"1.5rem\",\n fontSize: \"0.875rem\",\n opacity: 0.8,\n }}\n >\n {client ? (\n <div>\n <span style={{ display: \"block\", opacity: 0.6, fontSize: \"0.75rem\", textTransform: \"uppercase\", letterSpacing: \"0.08em\" }}>\n Client\n </span>\n {client}\n </div>\n ) : null}\n {year ? (\n <div>\n <span style={{ display: \"block\", opacity: 0.6, fontSize: \"0.75rem\", textTransform: \"uppercase\", letterSpacing: \"0.08em\" }}>\n Year\n </span>\n {year}\n </div>\n ) : null}\n {role ? (\n <div>\n <span style={{ display: \"block\", opacity: 0.6, fontSize: \"0.75rem\", textTransform: \"uppercase\", letterSpacing: \"0.08em\" }}>\n Role\n </span>\n {role}\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n </section>\n );\n}\n\ninterface ImageGridItem {\n url: string;\n alt?: string;\n caption?: string;\n}\n\ninterface ImageGridProps {\n columns?: number;\n items: ImageGridItem[];\n}\n\nfunction ImageGrid(props: Record<string, unknown>): React.ReactElement {\n const { columns, items } = props as unknown as ImageGridProps;\n const cols = typeof columns === \"number\" && columns > 0 ? columns : 2;\n return (\n <section\n className=\"np-portfolio-image-grid\"\n style={{\n margin: \"2rem 0\",\n display: \"grid\",\n gap: \"1rem\",\n gridTemplateColumns: `repeat(${cols}, 1fr)`,\n }}\n >\n {items.map((item, i) => (\n <figure key={i} style={{ margin: 0 }}>\n <img\n src={item.url}\n alt={item.alt ?? \"\"}\n style={{\n display: \"block\",\n width: \"100%\",\n height: \"auto\",\n borderRadius: \"0.25rem\",\n }}\n />\n {item.caption ? (\n <figcaption\n style={{\n fontSize: \"0.8125rem\",\n color: \"var(--np-color-muted-foreground)\",\n marginTop: \"0.5rem\",\n }}\n >\n {item.caption}\n </figcaption>\n ) : null}\n </figure>\n ))}\n </section>\n );\n}\n\nexport const portfolioBlocks: NpBlockDefinition[] = [\n {\n type: \"portfolio.case-study-hero\",\n label: \"Case study hero\",\n iconKind: \"lucide\",\n icon: \"image\",\n keywords: [\"hero\", \"case-study\", \"portfolio\", \"project\"],\n defaultProps: {\n title: \"Project name\",\n subtitle: \"One-sentence project summary.\",\n client: \"Client name\",\n year: \"2026\",\n role: \"Design + Engineering\",\n imageUrl: \"\",\n },\n propsSchema: [\n { name: \"title\", label: \"Project title\", type: \"text\" },\n { name: \"subtitle\", label: \"Subtitle\", type: \"textarea\" },\n { name: \"client\", label: \"Client\", type: \"text\" },\n { name: \"year\", label: \"Year\", type: \"text\" },\n { name: \"role\", label: \"Role\", type: \"text\" },\n { name: \"imageUrl\", label: \"Hero image URL\", type: \"url\" },\n ],\n render: (props) => <CaseStudyHero {...props} />,\n },\n {\n type: \"portfolio.image-grid\",\n label: \"Image grid\",\n iconKind: \"lucide\",\n icon: \"grid-3x3\",\n keywords: [\"images\", \"gallery\", \"grid\", \"portfolio\"],\n defaultProps: {\n columns: 2,\n items: [\n { url: \"https://placehold.co/800x600\", alt: \"\", caption: \"\" },\n { url: \"https://placehold.co/800x600\", alt: \"\", caption: \"\" },\n ],\n },\n propsSchema: [\n { name: \"columns\", label: \"Columns\", type: \"number\" },\n // `items` edited as JSON in v0.2; richer per-item editor\n // (drag-to-reorder, add/remove) tracked as F.5.1 polish.\n { name: \"items\", label: \"Items (JSON)\", type: \"textarea\" },\n ],\n render: (props) => <ImageGrid {...props} />,\n },\n {\n type: \"portfolio.client-logos\",\n label: \"Client logos strip\",\n iconKind: \"lucide\",\n icon: \"users\",\n keywords: [\"clients\", \"logos\", \"portfolio\", \"selected work\"],\n defaultProps: {\n heading: \"Selected clients\",\n },\n propsSchema: [\n { name: \"heading\", label: \"Section heading\", type: \"text\" },\n ],\n // `ClientLogosStrip` is itself an async server component —\n // it reads `settings.clientLogos` so the operator manages\n // logos in admin's Theme settings panel (a single canonical\n // source) rather than re-typing per block instance. The render\n // arrow itself is sync (just constructs the element); React\n // resolves the inner async at render time.\n render: (props) => <ClientLogosStrip {...(props as { heading?: string })} />,\n },\n];\n\ninterface ClientLogosStripProps {\n heading?: string;\n}\n\nasync function ClientLogosStrip({\n heading,\n}: ClientLogosStripProps): Promise<React.ReactElement | null> {\n const settings = await resolvePortfolioSettings();\n const logos = settings.clientLogos;\n // Public site never shows a \"configure in admin\" placeholder\n // — visitors don't need to see operator-facing copy. Empty\n // setting means render nothing; the block instance is invisible\n // until the operator populates the list. The page builder admin\n // surface DOES surface the empty state separately (block picker\n // shows the block's defaultProps); operators see it during\n // configuration, not visitors.\n if (logos.length === 0) return null;\n return (\n <section\n className=\"np-portfolio-client-logos\"\n style={{\n margin: \"3rem 0\",\n }}\n >\n {heading ? (\n <h2\n style={{\n margin: \"0 0 1.5rem\",\n fontSize: \"0.8125rem\",\n textTransform: \"uppercase\",\n letterSpacing: \"0.1em\",\n color: \"var(--np-color-muted-foreground)\",\n textAlign: \"center\",\n }}\n >\n {heading}\n </h2>\n ) : null}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: `repeat(auto-fit, minmax(140px, 1fr))`,\n gap: \"2rem\",\n alignItems: \"center\",\n justifyItems: \"center\",\n }}\n >\n {logos.map((logo, i) => {\n const img = (\n <img\n src={logo.logoUrl}\n alt={logo.name}\n style={{\n maxHeight: \"48px\",\n maxWidth: \"100%\",\n opacity: 0.7,\n transition: \"opacity 0.2s ease\",\n }}\n />\n );\n return (\n <div key={`portfolio-logo-${i.toString()}`}>\n {logo.link ? (\n <a href={logo.link} target=\"_blank\" rel=\"noreferrer\">\n {img}\n </a>\n ) : (\n img\n )}\n </div>\n );\n })}\n </div>\n </section>\n );\n}\n","import * as React from \"react\";\n\nimport { resolvePortfolioSettings } from \"../settings-helpers.js\";\n\n/**\n * Portfolio \"project\" card. Visual-first: a large image (1:1\n * by default) with an overlaid title that fades in on hover.\n * Defensive on the doc shape so collections of any kind can\n * be routed through this card.\n *\n * Phase F.9.1-B — `settings.showProjectTags` toggles the\n * category/tag chip below the title. Operators who want a\n * cleaner card grid flip it off.\n */\n\nexport interface PortfolioProjectDoc {\n id?: string;\n slug?: string;\n title?: string;\n category?: string;\n cover?: { url?: string; alt?: string } | string | null;\n}\n\nfunction coverUrl(value: PortfolioProjectDoc[\"cover\"]): string | null {\n if (!value) return null;\n if (typeof value === \"string\") return value;\n return value.url ?? null;\n}\n\nfunction coverAlt(value: PortfolioProjectDoc[\"cover\"], fallback: string): string {\n if (value && typeof value === \"object\" && value.alt) return value.alt;\n return fallback;\n}\n\nfunction projectHref(doc: PortfolioProjectDoc): string {\n if (doc.slug) {\n return doc.slug.startsWith(\"/\") ? doc.slug : `/work/${doc.slug}`;\n }\n return \"#\";\n}\n\nexport interface PortfolioProjectCardProps {\n doc: PortfolioProjectDoc;\n}\n\nexport async function PortfolioProjectCard({\n doc,\n}: PortfolioProjectCardProps): Promise<React.ReactElement> {\n const settings = await resolvePortfolioSettings();\n const href = projectHref(doc);\n const cover = coverUrl(doc.cover);\n const title = doc.title ?? \"Untitled\";\n return (\n <a href={href} className=\"np-portfolio-project-card\">\n <figure className=\"np-portfolio-project-cover\">\n {cover ? (\n <img src={cover} alt={coverAlt(doc.cover, title)} loading=\"lazy\" />\n ) : (\n <span className=\"np-portfolio-project-placeholder\" aria-hidden=\"true\" />\n )}\n <figcaption className=\"np-portfolio-project-caption\">\n <span className=\"np-portfolio-project-title\">{title}</span>\n {settings.showProjectTags && doc.category ? (\n <span className=\"np-portfolio-project-category\">{doc.category}</span>\n ) : null}\n </figcaption>\n </figure>\n </a>\n );\n}\n","import { resolvePortfolioSettings } from \"./settings-helpers.js\";\n\n/**\n * Studio footer — rows on a thin top rule:\n * 1. Optional bio (`settings.aboutCopy`) — short studio\n * description rendered above the contact line. Operators\n * who want a fuller about page do that through the page\n * builder; this is the ambient bio surfaced on every page.\n * 2. Contact line (NP_SOCIAL_EMAIL → mailto, or generic blurb)\n * 3. Social mini-strip (NP_SOCIAL_GITHUB / TWITTER / LINKEDIN /\n * MASTODON, all optional, hidden when none are configured)\n * 4. Colophon — year + framework credit, toggled by\n * `settings.showFooterCredit`. `settings.copyrightYear`\n * overrides the auto-detected year (some studios pin to\n * \"2024\" for an \"established\" feel).\n *\n * Stays minimal so the visual focus stays on the work above.\n */\nexport async function PortfolioFooter() {\n const settings = await resolvePortfolioSettings();\n const year = settings.copyrightYear ?? new Date().getFullYear();\n const email = process.env.NP_SOCIAL_EMAIL;\n const social = [\n { href: process.env.NP_SOCIAL_GITHUB, label: \"GitHub\" },\n { href: process.env.NP_SOCIAL_TWITTER, label: \"Twitter\" },\n { href: process.env.NP_SOCIAL_LINKEDIN, label: \"LinkedIn\" },\n { href: process.env.NP_SOCIAL_MASTODON, label: \"Mastodon\" },\n { href: process.env.NP_SOCIAL_DRIBBBLE, label: \"Dribbble\" },\n { href: process.env.NP_SOCIAL_INSTAGRAM, label: \"Instagram\" },\n ].filter((s): s is { href: string; label: string } => Boolean(s.href));\n const studio = settings.studioName;\n\n return (\n <footer className=\"np-site-footer np-portfolio-footer\">\n <div className=\"np-portfolio-footer-inner\">\n {settings.aboutCopy.length > 0 ? (\n <p\n className=\"np-portfolio-footer-bio\"\n style={{\n maxWidth: \"60ch\",\n fontSize: \"0.875rem\",\n color: \"var(--np-color-muted-foreground)\",\n margin: \"0 0 1.25rem\",\n }}\n >\n {settings.aboutCopy}\n </p>\n ) : null}\n <div className=\"np-portfolio-footer-contact\">\n {email ? (\n <a\n href={email.startsWith(\"mailto:\") ? email : `mailto:${email}`}\n className=\"np-portfolio-footer-email\"\n >\n {email.replace(/^mailto:/, \"\")}\n </a>\n ) : (\n <span className=\"np-portfolio-footer-email\">Available for select work</span>\n )}\n </div>\n {social.length > 0 ? (\n <ul className=\"np-portfolio-footer-social\">\n {social.map((s) => (\n <li key={s.href}>\n <a href={s.href} target=\"_blank\" rel=\"noopener noreferrer\">\n {s.label}\n </a>\n </li>\n ))}\n </ul>\n ) : null}\n <p className=\"np-portfolio-footer-mark\">\n © {year.toString()} · {studio}\n {settings.showFooterCredit ? \" · Built with NexPress\" : \"\"}\n </p>\n </div>\n </footer>\n );\n}\n","import type { NpNavItem } from \"@nexpress/core\";\nimport { getCachedNavigation } from \"@nexpress/next\";\n\nimport { PortfolioMobileNav } from \"./components/mobile-nav.js\";\nimport { resolvePortfolioSettings } from \"./settings-helpers.js\";\n\n/**\n * Slim sticky top bar. Studio name on the inline-start, primary\n * nav on the inline-end. The inline list hides below ~720px and\n * the mobile drawer takes over.\n *\n * Phase F.9.1-A — `settings.studioName` controls the brand\n * label. Default (\"Studio\") falls through if operator hasn't\n * customized.\n */\nexport async function PortfolioHeader() {\n const items = await getCachedNavigation(\"header\");\n const settings = await resolvePortfolioSettings();\n\n return (\n <header className=\"np-site-header np-portfolio-header\">\n <a href=\"/\" className=\"np-portfolio-logo\">\n {settings.studioName}\n </a>\n {items.length > 0 ? (\n <>\n <nav aria-label=\"Main\" className=\"np-portfolio-nav-desktop\">\n <ul className=\"np-portfolio-nav\">\n {items.map((item: NpNavItem, index: number) => (\n <li key={`portfolio-nav-${index.toString()}`} className=\"np-portfolio-nav-item\">\n <a href={item.url}>{item.label}</a>\n {item.children && item.children.length > 0 ? (\n <ul className=\"np-portfolio-subnav\">\n {item.children.map((child: NpNavItem, childIndex: number) => (\n <li key={`portfolio-nav-${index.toString()}-${childIndex.toString()}`}>\n <a href={child.url}>{child.label}</a>\n </li>\n ))}\n </ul>\n ) : null}\n </li>\n ))}\n </ul>\n </nav>\n <PortfolioMobileNav items={items} />\n </>\n ) : null}\n </header>\n );\n}\n","import * as React from \"react\";\n\n/**\n * Portfolio member-tree 404.\n *\n * Mirrors `PortfolioNotFound`'s minimal voice but tuned for the\n * member context — CTA points at `/members/login` rather than\n * the homepage, and the copy acknowledges stale auth links (the\n * dominant cause of 404s inside `/members/*`).\n *\n * Server component; rendered by `(member)/not-found.tsx` when\n * the active theme is portfolio and `impl.members.notFound` is\n * declared.\n *\n * Renders a `<div>`, not `<main>`, because the framework's\n * `<ShellWrap surface=\"member\">` already emits the page's\n * `<main className=\"np-member-main\">` landmark.\n */\nexport function PortfolioMembersNotFound(): React.ReactElement {\n return (\n <div\n className=\"np-portfolio-members-not-found\"\n style={{\n maxWidth: 480,\n margin: \"6rem auto\",\n padding: \"0 1.5rem\",\n textAlign: \"center\",\n }}\n >\n <p\n style={{\n margin: 0,\n fontSize: \"0.75rem\",\n textTransform: \"uppercase\",\n letterSpacing: \"0.18em\",\n color: \"var(--np-color-muted-foreground)\",\n fontFamily: \"var(--np-font-body)\",\n }}\n >\n Account\n </p>\n <h1\n style={{\n margin: \"1rem 0 0\",\n fontSize: \"clamp(1.75rem, 4vw, 2.5rem)\",\n fontFamily: \"var(--np-font-heading)\",\n fontWeight: 500,\n letterSpacing: \"-0.02em\",\n }}\n >\n Link no longer valid.\n </h1>\n <p\n style={{\n margin: \"1.25rem 0 0\",\n color: \"var(--np-color-muted-foreground)\",\n fontSize: \"0.9375rem\",\n lineHeight: 1.6,\n }}\n >\n Verification and password-reset links are single-use and short-lived.\n Request a fresh one from the sign-in page.\n </p>\n <a\n href=\"/members/login\"\n style={{\n display: \"inline-block\",\n marginTop: \"2rem\",\n padding: \"0.625rem 1.5rem\",\n borderRadius: \"0.25rem\",\n background: \"var(--np-color-primary)\",\n color: \"var(--np-color-primary-foreground)\",\n textDecoration: \"none\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n letterSpacing: \"0.02em\",\n }}\n >\n Go to sign in\n </a>\n </div>\n );\n}\n","import type { CSSProperties, ReactNode } from \"react\";\n\nimport { PortfolioFooter } from \"./footer.js\";\nimport { PortfolioHeader } from \"./header.js\";\nimport { resolvePortfolioSettings } from \"./settings-helpers.js\";\n\n/**\n * Portfolio member-tree shell. Same wrapping shape as\n * `PortfolioShell` (provides `np-portfolio` root + accent-color +\n * card-aspect CSS variables) but pulls the header/footer inline\n * because `<ShellWrap surface=\"member\">` opts OUT of the layout's\n * chrome-slot injection when a theme owns its own member shell\n * (F-track contract, see\n * `apps/web/src/components/shell-wrap.tsx`).\n *\n * Narrows the content column for auth forms (login / register /\n * reset / verify / notifications) — the portfolio public site\n * uses a wide image-led layout that would dwarf a 320-wide form.\n * Reuses `PortfolioHeader` / `PortfolioFooter` directly so a\n * theme-version bump touching the masthead reaches member pages\n * too — single source of truth for chrome.\n *\n * Does its own `<div className=\"np-portfolio\">` root because the\n * (site) `PortfolioShell` is bypassed when this shell takes over.\n */\nconst ASPECT_VALUES = {\n square: \"1 / 1\",\n portrait: \"3 / 4\",\n landscape: \"4 / 3\",\n golden: \"1 / 1.618\",\n} as const;\n\nexport async function PortfolioMembersShell({\n children,\n}: {\n children: ReactNode;\n}) {\n const settings = await resolvePortfolioSettings();\n const aspect = ASPECT_VALUES[settings.cardAspect];\n const styleVars: Record<string, string> = {\n \"--np-portfolio-card-aspect\": aspect,\n };\n if (settings.accentColor) {\n styleVars[\"--np-color-primary\"] = settings.accentColor;\n }\n return (\n <div\n className=\"np-portfolio\"\n data-hover-style={settings.hoverStyle}\n style={styleVars as CSSProperties}\n >\n <PortfolioHeader />\n <div className=\"np-portfolio-members\">\n <div className=\"np-portfolio-members-column\">{children}</div>\n </div>\n <PortfolioFooter />\n </div>\n );\n}\n","import * as React from \"react\";\n\n/**\n * Phase F.9-C — portfolio 404.\n *\n * Dark, sparse — matches the theme's surface palette. A single\n * line of copy + return-home link, centered.\n */\nexport function PortfolioNotFound(): React.ReactElement {\n // `<div>` — (site)/layout.tsx already emits the page's `<main>`.\n return (\n <div\n className=\"np-portfolio-not-found\"\n style={{\n minHeight: \"60vh\",\n maxWidth: 480,\n margin: \"0 auto\",\n padding: \"6rem 1.5rem\",\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n alignItems: \"center\",\n textAlign: \"center\",\n }}\n >\n <p\n style={{\n margin: 0,\n fontSize: \"0.75rem\",\n textTransform: \"uppercase\",\n letterSpacing: \"0.15em\",\n color: \"var(--np-color-muted-foreground)\",\n }}\n >\n 404\n </p>\n <h1\n style={{\n margin: \"1rem 0 0\",\n fontSize: \"clamp(1.75rem, 4vw, 2.5rem)\",\n fontWeight: 500,\n letterSpacing: \"-0.02em\",\n }}\n >\n Project not found\n </h1>\n <p\n style={{\n margin: \"1rem 0 2rem\",\n color: \"var(--np-color-muted-foreground)\",\n fontSize: \"0.9375rem\",\n }}\n >\n The page you're looking for moved or doesn't exist.\n </p>\n <a\n href=\"/\"\n style={{\n display: \"inline-block\",\n padding: \"0.5rem 1.5rem\",\n border: \"1px solid var(--np-color-border)\",\n borderRadius: \"0.25rem\",\n color: \"var(--np-color-foreground)\",\n textDecoration: \"none\",\n fontSize: \"0.875rem\",\n }}\n >\n See selected work →\n </a>\n </div>\n );\n}\n","import { findDocuments } from \"@nexpress/core\";\nimport type { NpRouteRenderProps, NpTemplateRenderProps } from \"@nexpress/theme\";\nimport { notFound } from \"next/navigation\";\nimport * as React from \"react\";\n\nimport { ProjectDetailTemplate } from \"../templates/project-detail.js\";\n\n/**\n * Theme route for `/work/:slug` — looks up a project row (the\n * `posts` collection, which portfolio uses for case studies) and\n * renders it through `templates.posts.detail`\n * (ProjectDetailTemplate) — #613.\n *\n * Without this route, `PortfolioProjectCard`'s `/work/<slug>`\n * links 404 in the reference app. The card emits the URL on its\n * own (`projectHref(doc)`) and the framework had no route to\n * back it.\n *\n * Defensive untyped `findDocuments<ProjectRow>` lookup — the\n * posts schema lives in the operator's project, not the theme.\n * `theme:install @nexpress/theme-portfolio` patches the\n * collection to include the fields ProjectDetailTemplate\n * expects (hero, role/year/client meta).\n *\n * Access / visibility: `findDocuments` already enforces\n * `access.read` and `community.visibility`, same as the\n * catch-all's `pages` lookup. We pass `status: \"published\"`\n * explicitly to hide drafts/pending from public URL access.\n */\n\ninterface ProjectRow {\n id: string;\n slug: string;\n title: string;\n body?: unknown;\n status?: string;\n hero?: unknown;\n excerpt?: string;\n category?: string;\n role?: string;\n year?: string;\n client?: string;\n}\n\nexport async function PortfolioProjectDetailRoute({\n params,\n blockCtx,\n}: NpRouteRenderProps): Promise<React.ReactElement> {\n const slug = typeof params.slug === \"string\" ? params.slug : \"\";\n if (!slug) notFound();\n\n const result = await findDocuments<ProjectRow>(\"posts\", {\n where: { slug, status: \"published\" },\n limit: 1,\n });\n const doc = result.docs[0];\n if (!doc) notFound();\n\n // `ProjectDetailTemplate`'s prop generic defaults to\n // `Record<string, unknown>` — cast through `unknown` so our\n // narrower `ProjectRow` shape (no index signature) matches.\n const templateProps: NpTemplateRenderProps = {\n doc: doc as unknown as Record<string, unknown>,\n blockCtx,\n };\n return <ProjectDetailTemplate {...templateProps} />;\n}\n","import * as React from \"react\";\nimport { renderBlocks } from \"@nexpress/blocks\";\nimport type { NpPageBlocks } from \"@nexpress/blocks\";\n\nimport type { NpTemplateRenderProps } from \"@nexpress/theme\";\n\nimport { resolvePortfolioSettings } from \"../settings-helpers.js\";\n\n/**\n * Project detail template. Big hero image, large title, optional\n * meta strip (role / year / client), then content blocks. The\n * content area uses a max-width column so prose stays readable\n * even on the dark surface; image blocks inside the body still\n * render edge-to-edge thanks to a nested full-bleed override.\n */\ninterface ProjectDoc {\n title?: string;\n excerpt?: string;\n cover?: { url?: string; alt?: string } | string | null;\n role?: string;\n year?: string | number;\n client?: string;\n blocks?: NpPageBlocks;\n}\n\nfunction coverUrl(value: ProjectDoc[\"cover\"]): string | null {\n if (!value) return null;\n if (typeof value === \"string\") return value;\n return value.url ?? null;\n}\n\nfunction coverAlt(value: ProjectDoc[\"cover\"], fallback: string): string {\n if (value && typeof value === \"object\" && value.alt) return value.alt;\n return fallback;\n}\n\nexport async function ProjectDetailTemplate({\n doc,\n blockCtx,\n}: NpTemplateRenderProps): Promise<React.ReactElement> {\n const project = doc as ProjectDoc;\n const settings = await resolvePortfolioSettings();\n const title = project.title ?? \"Untitled\";\n const cover = coverUrl(project.cover);\n return (\n <article className=\"np-portfolio-project-detail\">\n {cover ? (\n <figure className=\"np-portfolio-project-hero\">\n <img src={cover} alt={coverAlt(project.cover, title)} />\n </figure>\n ) : null}\n <header className=\"np-portfolio-project-header\">\n <h1>{title}</h1>\n {project.excerpt ? (\n <p className=\"np-portfolio-project-excerpt\">{project.excerpt}</p>\n ) : null}\n {settings.showProjectMeta &&\n (project.role || project.year || project.client) ? (\n <dl className=\"np-portfolio-project-meta\">\n {project.client ? (\n <>\n <dt>Client</dt>\n <dd>{project.client}</dd>\n </>\n ) : null}\n {project.role ? (\n <>\n <dt>Role</dt>\n <dd>{project.role}</dd>\n </>\n ) : null}\n {project.year ? (\n <>\n <dt>Year</dt>\n <dd>{String(project.year)}</dd>\n </>\n ) : null}\n </dl>\n ) : null}\n </header>\n {project.blocks && project.blocks.length > 0 ? (\n <div className=\"np-portfolio-project-body\">\n {renderBlocks(project.blocks, { ctx: blockCtx })}\n </div>\n ) : null}\n </article>\n );\n}\n","import type { CSSProperties, ReactNode } from \"react\";\n\nimport { resolvePortfolioSettings } from \"./settings-helpers.js\";\n\n/**\n * Phase F.9.1-B — shell renders inline `<style>` block that\n * threads operator settings into CSS custom properties:\n *\n * --np-color-primary — settings.accentColor override\n * --np-portfolio-aspect — settings.cardAspect (CSS aspect-ratio value)\n * --np-portfolio-hover — settings.hoverStyle (data attribute consumed\n * by styles.ts hover variants)\n *\n * The card / hover styles inside `styles.ts` read the variables\n * + a `[data-hover-style=\"<x>\"]` attribute set on this element\n * so the hover effect swaps without restructuring the cards\n * themselves.\n */\nconst ASPECT_VALUES = {\n square: \"1 / 1\",\n portrait: \"3 / 4\",\n landscape: \"4 / 3\",\n golden: \"1 / 1.618\",\n} as const;\n\nexport async function PortfolioShell({ children }: { children: ReactNode }) {\n const settings = await resolvePortfolioSettings();\n const aspect = ASPECT_VALUES[settings.cardAspect];\n const styleVars: Record<string, string> = {\n \"--np-portfolio-card-aspect\": aspect,\n };\n if (settings.accentColor) {\n styleVars[\"--np-color-primary\"] = settings.accentColor;\n }\n return (\n <div\n className=\"np-portfolio\"\n data-hover-style={settings.hoverStyle}\n style={styleVars as CSSProperties}\n >\n {children}\n </div>\n );\n}\n","/**\n * Theme-owned CSS for `@nexpress/theme-portfolio`. Reads from the\n * theme token system (background / foreground / primary / card /\n * muted) so admin token overrides reflow the whole shell. The\n * dark surface ships via `impl.tokens` in `index.ts`; that's the\n * single point of truth, this CSS just consumes it. Scoped under\n * `.np-portfolio-*` so swapping themes never leaves residue.\n *\n * Decorative dividers stay as `rgba(255, 255, 255, …)` since they're\n * tied to the dark assumption — flipping to a light palette is an\n * intentional fork that needs a fresh divider color anyway.\n */\nexport const portfolioCss = `\n.np-portfolio {\n background: var(--np-color-background);\n color: var(--np-color-foreground);\n min-height: 100vh;\n font-family: var(--np-font-body, \"Inter\", system-ui, sans-serif);\n}\n.np-portfolio a { color: inherit; }\n.np-portfolio ::selection {\n background: var(--np-color-primary);\n color: var(--np-color-primary-foreground);\n}\n\n/* ----------------------------------------------------------------\n * Header\n * --------------------------------------------------------------- */\n.np-portfolio-header {\n background: color-mix(in oklab, var(--np-color-background) 85%, transparent);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n border-bottom: 1px solid color-mix(in oklab, var(--np-color-foreground) 8%, transparent);\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 1rem 2rem;\n position: sticky;\n top: 0;\n z-index: 30;\n gap: 1rem;\n}\n.np-portfolio-logo {\n font-weight: 600;\n letter-spacing: 0.02em;\n text-decoration: none;\n font-size: 0.95rem;\n}\n.np-portfolio-nav {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n gap: 1.5rem;\n font-size: 0.875rem;\n}\n.np-portfolio-nav a {\n text-decoration: none;\n opacity: 0.75;\n transition: opacity 0.15s ease;\n}\n.np-portfolio-nav a:hover { opacity: 1; }\n.np-portfolio-nav-item {\n position: relative;\n}\n.np-portfolio-subnav {\n position: absolute;\n top: 100%;\n left: 0;\n display: none;\n min-width: 11rem;\n padding: 0.5rem 0;\n margin: 0;\n list-style: none;\n background: var(--np-color-card, #fff);\n border: 1px solid var(--np-color-border, #e5e7eb);\n border-radius: var(--np-radius-md, 0.5rem);\n box-shadow: 0 4px 16px -8px rgba(0, 0, 0, 0.08);\n z-index: 10;\n}\n.np-portfolio-nav-item:hover > .np-portfolio-subnav,\n.np-portfolio-nav-item:focus-within > .np-portfolio-subnav {\n display: block;\n}\n.np-portfolio-subnav a {\n display: block;\n padding: 0.4rem 1rem;\n font-size: 0.875rem;\n}\n.np-portfolio-mobile-subnav {\n list-style: none;\n margin: 0;\n padding-left: 1.25rem;\n}\n\n/* Mobile drawer */\n.np-portfolio-nav-toggle {\n display: none;\n align-items: center;\n justify-content: center;\n padding: 0.4rem 0.85rem;\n border: 1px solid color-mix(in oklab, var(--np-color-foreground) 20%, transparent);\n border-radius: 999px;\n background: transparent;\n color: inherit;\n font: inherit;\n font-size: 0.75rem;\n letter-spacing: 0.06em;\n cursor: pointer;\n}\n.np-portfolio-nav-toggle:hover {\n border-color: color-mix(in oklab, var(--np-color-foreground) 50%, transparent);\n}\n.np-portfolio-nav-drawer {\n position: fixed;\n inset: 0;\n background: color-mix(in oklab, var(--np-color-background) 95%, transparent);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n z-index: 50;\n display: flex;\n align-items: center;\n justify-content: center;\n opacity: 0;\n visibility: hidden;\n transition: opacity 0.25s ease, visibility 0.25s ease;\n}\n.np-portfolio-nav-drawer[data-open=\"true\"] {\n opacity: 1;\n visibility: visible;\n}\n.np-portfolio-nav-drawer-list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n text-align: center;\n font-size: clamp(1.4rem, 3vw, 2rem);\n font-weight: 500;\n letter-spacing: -0.01em;\n}\n.np-portfolio-nav-drawer-list a {\n color: inherit;\n text-decoration: none;\n opacity: 0.85;\n transition: opacity 0.15s ease;\n}\n.np-portfolio-nav-drawer-list a:hover { opacity: 1; }\n\n@media (max-width: 720px) {\n .np-portfolio-nav-desktop { display: none; }\n .np-portfolio-nav-toggle { display: inline-flex; }\n}\n@media (min-width: 721px) {\n .np-portfolio-nav-drawer { display: none; }\n}\n\n/* ----------------------------------------------------------------\n * Footer\n * --------------------------------------------------------------- */\n.np-portfolio-footer {\n border-top: 1px solid color-mix(in oklab, var(--np-color-foreground) 8%, transparent);\n margin-top: 6rem;\n background: transparent;\n text-align: center;\n}\n.np-portfolio-footer-inner {\n max-width: 960px;\n margin: 0 auto;\n padding: 2.5rem 1.5rem;\n display: flex;\n flex-direction: column;\n gap: 1rem;\n align-items: center;\n}\n.np-portfolio-footer-contact { font-size: 1.05rem; }\n.np-portfolio-footer-email {\n text-decoration: none;\n letter-spacing: 0.02em;\n border-bottom: 1px solid color-mix(in oklab, var(--np-color-foreground) 40%, transparent);\n padding-bottom: 0.15rem;\n}\n.np-portfolio-footer-email:hover {\n border-bottom-color: color-mix(in oklab, var(--np-color-foreground) 85%, transparent);\n}\n.np-portfolio-footer-social {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n gap: 1.5rem;\n font-size: 0.85rem;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.np-portfolio-footer-social a {\n text-decoration: none;\n opacity: 0.65;\n transition: opacity 0.15s ease;\n}\n.np-portfolio-footer-social a:hover { opacity: 1; }\n.np-portfolio-footer-mark {\n margin: 0;\n font-size: 0.78rem;\n opacity: 0.5;\n letter-spacing: 0.06em;\n}\n\n/* ----------------------------------------------------------------\n * Page templates\n * --------------------------------------------------------------- */\n.np-portfolio-page {\n max-width: 720px;\n margin: 0 auto;\n padding: 4rem 1.5rem;\n line-height: 1.7;\n}\n.np-portfolio-page h1,\n.np-portfolio-page h2,\n.np-portfolio-page h3 { letter-spacing: -0.01em; }\n\n.np-portfolio-gallery {\n max-width: 1280px;\n margin: 0 auto;\n padding: 3rem 1.5rem 4rem;\n}\n.np-portfolio-gallery > h1 {\n text-align: center;\n font-size: clamp(2rem, 4vw, 3.5rem);\n margin: 0 0 2.5rem;\n letter-spacing: -0.02em;\n}\n.np-portfolio-gallery-grid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 1.5rem;\n}\n@media (min-width: 720px) {\n .np-portfolio-gallery-grid { grid-template-columns: 1fr 1fr; }\n}\n.np-portfolio-gallery-grid img {\n width: 100%;\n height: auto;\n display: block;\n border-radius: 8px;\n}\n\n/* ----------------------------------------------------------------\n * Project index (grid of cards)\n * --------------------------------------------------------------- */\n.np-portfolio-index {\n max-width: 1320px;\n margin: 0 auto;\n padding: 3.5rem 1.5rem 4rem;\n}\n.np-portfolio-index-header {\n text-align: center;\n margin-bottom: 3rem;\n}\n.np-portfolio-index-header h1 {\n font-size: clamp(2.25rem, 4vw, 3rem);\n letter-spacing: -0.02em;\n margin: 0 0 0.65rem;\n font-weight: 600;\n}\n.np-portfolio-index-header p {\n margin: 0 auto;\n max-width: 38rem;\n opacity: 0.75;\n line-height: 1.6;\n}\n.np-portfolio-index-empty {\n text-align: center;\n padding: 4rem 1.5rem;\n opacity: 0.6;\n}\n.np-portfolio-index-grid {\n /* Phase F.9.1-A — operator's settings.gridColumns\n * sets --np-portfolio-grid-cols on this element via the\n * project-index template. Mobile clamps to 1 column\n * regardless; tablet caps at min(2, --np-portfolio-grid-cols);\n * desktop honors the operator's choice up to 6. Operator\n * stays in control without breaking responsive design.\n */\n --np-portfolio-grid-cols: 3;\n --np-portfolio-grid-gutter: 1.5rem;\n display: grid;\n grid-template-columns: 1fr;\n gap: var(--np-portfolio-grid-gutter);\n}\n@media (min-width: 640px) {\n .np-portfolio-index-grid {\n grid-template-columns: repeat(min(2, var(--np-portfolio-grid-cols, 3)), 1fr);\n }\n}\n@media (min-width: 1024px) {\n .np-portfolio-index-grid {\n grid-template-columns: repeat(var(--np-portfolio-grid-cols, 3), 1fr);\n }\n}\n\n/* ----------------------------------------------------------------\n * Project card\n * --------------------------------------------------------------- */\n.np-portfolio-project-card {\n display: block;\n text-decoration: none;\n color: inherit;\n position: relative;\n overflow: hidden;\n border-radius: 4px;\n background: var(--np-color-card);\n}\n.np-portfolio-project-cover {\n margin: 0;\n position: relative;\n /* Phase F.9.1-B — operator-tunable aspect via shell-set\n * --np-portfolio-card-aspect (square / portrait / landscape /\n * golden). Falls back to 4/3 when the variable isn't set. */\n aspect-ratio: var(--np-portfolio-card-aspect, 4 / 3);\n overflow: hidden;\n}\n.np-portfolio-project-cover img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n transition: transform 0.5s ease, filter 0.4s ease;\n}\n/* Phase F.9.1-B — hoverStyle variants. Selected via the shell's\n * data-hover-style attribute. Default (\"fade\") = caption fades in\n * + image scale; the others swap the image effect.\n * - scale: only the image zooms (caption stays subtle)\n * - slide: image stays put; caption slides up from below\n * - lift: card lifts with shadow; image static\n */\n.np-portfolio[data-hover-style=\"fade\"] .np-portfolio-project-card:hover .np-portfolio-project-cover img,\n.np-portfolio[data-hover-style=\"scale\"] .np-portfolio-project-card:hover .np-portfolio-project-cover img {\n transform: scale(1.04);\n}\n.np-portfolio[data-hover-style=\"lift\"] .np-portfolio-project-card {\n transition: transform 0.3s ease, box-shadow 0.3s ease;\n}\n.np-portfolio[data-hover-style=\"lift\"] .np-portfolio-project-card:hover {\n transform: translateY(-4px);\n box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35);\n}\n.np-portfolio[data-hover-style=\"slide\"] .np-portfolio-project-caption {\n /* Slide-up reveal — caption starts further below + opacity 0 */\n transform: translateY(24px);\n}\n.np-portfolio-project-placeholder {\n display: block;\n width: 100%;\n height: 100%;\n background: linear-gradient(\n 135deg,\n var(--np-color-muted) 0%,\n var(--np-color-accent) 100%\n );\n}\n.np-portfolio-project-caption {\n position: absolute;\n inset: auto 0 0 0;\n padding: 1rem 1.25rem;\n background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, transparent 100%);\n display: flex;\n flex-direction: column;\n gap: 0.2rem;\n opacity: 0;\n transform: translateY(8px);\n transition: opacity 0.25s ease, transform 0.25s ease;\n}\n.np-portfolio-project-card:hover .np-portfolio-project-caption {\n opacity: 1;\n transform: translateY(0);\n}\n.np-portfolio-project-title {\n font-weight: 600;\n letter-spacing: 0.01em;\n font-size: 1rem;\n}\n.np-portfolio-project-category {\n font-size: 0.72rem;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n opacity: 0.8;\n}\n\n/* ----------------------------------------------------------------\n * Project detail\n * --------------------------------------------------------------- */\n.np-portfolio-project-detail {\n margin: 0;\n padding: 0 0 4rem;\n}\n.np-portfolio-project-hero {\n margin: 0;\n width: 100%;\n aspect-ratio: 21 / 9;\n overflow: hidden;\n background: var(--np-color-card);\n}\n.np-portfolio-project-hero img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n.np-portfolio-project-header {\n max-width: 760px;\n margin: 0 auto;\n padding: 3rem 1.5rem 2rem;\n text-align: center;\n}\n.np-portfolio-project-header h1 {\n font-size: clamp(2rem, 4vw, 3rem);\n letter-spacing: -0.02em;\n margin: 0 0 0.85rem;\n font-weight: 600;\n}\n.np-portfolio-project-excerpt {\n margin: 0 auto;\n max-width: 38rem;\n opacity: 0.75;\n font-size: 1.075rem;\n line-height: 1.55;\n}\n.np-portfolio-project-meta {\n margin: 2rem auto 0;\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(8rem, max-content));\n justify-content: center;\n gap: 0 2rem;\n font-size: 0.85rem;\n text-align: start;\n}\n.np-portfolio-project-meta dt {\n text-transform: uppercase;\n letter-spacing: 0.16em;\n font-size: 0.7rem;\n opacity: 0.55;\n margin-bottom: 0.2rem;\n}\n.np-portfolio-project-meta dd {\n margin: 0 0 0.75rem;\n font-weight: 500;\n}\n.np-portfolio-project-body {\n max-width: 720px;\n margin: 0 auto;\n padding: 0 1.5rem;\n font-size: 1.05rem;\n line-height: 1.7;\n opacity: 0.92;\n}\n.np-portfolio-project-body img {\n max-width: 100%;\n height: auto;\n border-radius: 6px;\n margin: 1.5rem 0;\n}\n\n/* Re-cast the .np-page baseline so links pick up the theme's\n primary token. Dark theme: primary is light-on-dark, so the\n link reads correctly. */\n.np-portfolio .np-page a {\n color: var(--np-color-primary);\n}\n\n/* M.* member surface — narrow auth-form column under the\n masthead. The portfolio's public layout is image-led wide;\n stretching a login form across that would look weird. */\n.np-portfolio-members {\n display: flex;\n justify-content: center;\n min-height: 60vh;\n padding: 3rem 1.5rem;\n}\n.np-portfolio-members-column {\n width: 100%;\n max-width: 420px;\n}\n\n/* Member form token overrides — portfolio's minimal aesthetic:\n sharp corners, hairline borders, theme primary on focus. */\n.np-portfolio .np-members-form {\n --np-member-form-input-bg: transparent;\n --np-member-form-input-border: var(--np-color-border);\n --np-member-form-input-border-focus: var(--np-color-primary);\n --np-member-form-input-radius: 0.25rem;\n --np-member-form-button-radius: 0.25rem;\n}\n.np-portfolio .np-members-form .np-form-label {\n font-size: 0.75rem;\n text-transform: uppercase;\n letter-spacing: 0.12em;\n}\n`.trim();\n","import { renderBlocks } from \"@nexpress/blocks\";\nimport type { NpPageBlocks } from \"@nexpress/blocks\";\n\nimport type { NpTemplateRenderProps } from \"@nexpress/theme\";\n\nexport function PageDefaultTemplate({ doc, blockCtx }: NpTemplateRenderProps) {\n const blocks = (doc as { blocks?: NpPageBlocks }).blocks;\n const title = (doc as { title?: string }).title;\n return (\n <article className=\"np-page np-portfolio-page\">\n {title ? <h1>{title}</h1> : null}\n {blocks ? renderBlocks(blocks, { ctx: blockCtx }) : null}\n </article>\n );\n}\n","import { renderBlocks } from \"@nexpress/blocks\";\nimport type { NpPageBlocks } from \"@nexpress/blocks\";\n\nimport type { NpTemplateRenderProps } from \"@nexpress/theme\";\n\n/**\n * Gallery template — renders the page title centered, then\n * arranges the page's blocks in a two-column responsive grid.\n *\n * Block components themselves don't know about the grid; the\n * template just wraps them so individual `image` blocks share\n * row space. For richer arrangements, themes can branch on\n * the block type — but the simplest case is \"drop blocks in,\n * grid takes care of it\".\n */\nexport function PageGalleryTemplate({ doc, blockCtx }: NpTemplateRenderProps) {\n const blocks = (doc as { blocks?: NpPageBlocks }).blocks;\n const title = (doc as { title?: string }).title;\n return (\n <section className=\"np-portfolio-gallery\">\n {title ? <h1>{title}</h1> : null}\n <div className=\"np-portfolio-gallery-grid\">\n {blocks ? renderBlocks(blocks, { ctx: blockCtx }) : null}\n </div>\n </section>\n );\n}\n","import * as React from \"react\";\nimport type { NpTemplateRenderProps } from \"@nexpress/theme\";\n\nimport {\n PortfolioProjectCard,\n type PortfolioProjectDoc,\n} from \"../components/project-card.js\";\nimport { resolvePortfolioSettings } from \"../settings-helpers.js\";\n\n/**\n * Project-index template. Big square cards in a responsive grid.\n * Title + intro centered above. The grid collapses to one column\n * below ~640px.\n *\n * Phase F.9.1-A — `settings.gridColumns` (1–6) drives the grid\n * column count via inline `gridTemplateColumns`. Default is 3.\n * `settings.galleryGutter` drives the gap between cards.\n *\n * Doc shape: `{ docs: PortfolioProjectDoc[], heading?, intro? }`.\n */\ninterface IndexDoc {\n docs?: PortfolioProjectDoc[];\n heading?: string;\n intro?: string;\n}\n\nexport async function ProjectIndexTemplate({\n doc,\n}: NpTemplateRenderProps): Promise<React.ReactElement> {\n const data = doc as IndexDoc;\n const settings = await resolvePortfolioSettings();\n const heading = data.heading ?? \"Selected work\";\n const intro = data.intro;\n const docs = data.docs ?? [];\n // Pass settings as CSS custom properties — the styles.ts\n // media queries clamp the column count at narrower viewports\n // so a `gridColumns: 6` setting doesn't crush content on\n // mobile. Inline `gridTemplateColumns` would beat the media\n // queries; vars let CSS stay in control of breakpoints.\n const gridStyle = {\n \"--np-portfolio-grid-cols\": settings.gridColumns,\n \"--np-portfolio-grid-gutter\": `${settings.galleryGutter}px`,\n } as React.CSSProperties;\n return (\n <section className=\"np-portfolio-index\">\n <header className=\"np-portfolio-index-header\">\n <h1>{heading}</h1>\n {intro ? <p>{intro}</p> : null}\n </header>\n {docs.length === 0 ? (\n <p className=\"np-portfolio-index-empty\">\n Nothing on display yet. Add projects from the admin to fill the grid.\n </p>\n ) : (\n <div className=\"np-portfolio-index-grid\" style={gridStyle}>\n {docs.map((project) => (\n <PortfolioProjectCard\n key={project.id ?? project.slug ?? project.title}\n doc={project}\n />\n ))}\n </div>\n )}\n </section>\n );\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["PortfolioMobileNav","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","Fragment","jsx","jsxs","coverUrl","coverAlt","jsx","jsx","ASPECT_VALUES","renderBlocks","jsx","jsxs","renderBlocks","jsx","jsxs","jsx","jsxs"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/settings-helpers.ts","../src/settings.ts","../src/blocks.tsx","../src/components/project-card.tsx","../src/footer.tsx","../src/header.tsx","../src/members-not-found.tsx","../src/members-shell.tsx","../src/not-found.tsx","../src/routes/project-detail.tsx","../src/templates/project-detail.tsx","../src/shell.tsx","../src/styles.ts","../src/templates/page-default.tsx","../src/templates/page-gallery.tsx","../src/templates/project-index.tsx"],"sourcesContent":["import { defineTheme } from \"@nexpress/theme\";\n\nimport { portfolioBlocks } from \"./blocks.js\";\nimport { PortfolioMobileNav } from \"./components/mobile-nav.js\";\nimport {\n PortfolioProjectCard,\n type PortfolioProjectDoc,\n} from \"./components/project-card.js\";\nimport { PortfolioFooter } from \"./footer.js\";\nimport { PortfolioHeader } from \"./header.js\";\nimport { PortfolioMembersNotFound } from \"./members-not-found.js\";\nimport { PortfolioMembersShell } from \"./members-shell.js\";\nimport { PortfolioNotFound } from \"./not-found.js\";\nimport { PortfolioProjectDetailRoute } from \"./routes/project-detail.js\";\nimport { portfolioSettingsSchema } from \"./settings.js\";\nimport { PortfolioShell } from \"./shell.js\";\nimport { portfolioCss } from \"./styles.js\";\nimport { PageDefaultTemplate } from \"./templates/page-default.js\";\nimport { PageGalleryTemplate } from \"./templates/page-gallery.js\";\nimport { ProjectDetailTemplate } from \"./templates/project-detail.js\";\nimport { ProjectIndexTemplate } from \"./templates/project-index.js\";\n\n/**\n * `@nexpress/theme-portfolio` — image-led dark theme.\n *\n * Designed for designers / photographers / studios. Pages get a\n * centered text column or a gallery grid; \"posts\" are treated as\n * project case studies with a hero image, role / year / client\n * meta strip, and the standard block body underneath. The index\n * template renders the project archive as a 2- / 3-column grid\n * of square cards with hover-fade captions.\n *\n * Flips the surface palette: dark `--np-color-background` is\n * driven entirely from the theme's CSS (no admin override\n * required). Sites that want a light variant fork or override\n * tokens via the admin.\n */\nexport const portfolioTheme = defineTheme({\n manifest: {\n id: \"portfolio\",\n name: \"Portfolio\",\n version: \"0.1.0\",\n description:\n \"Image-led dark theme for studios and designers. Hero-led project detail template, archive grid, gallery and centered page templates.\",\n author: { name: \"NexPress\" },\n nexpress: { minVersion: \"0.1.0\" },\n // Phase F.1 — declared data-shape requirements. The CLI\n // (`pnpm nexpress theme:install @nexpress/theme-portfolio`)\n // patches operator collections to satisfy these.\n requires: {\n collections: {\n posts: {\n fields: {\n heroImage: { type: \"upload\" },\n client: { type: \"text\", hard: false },\n year: { type: \"number\", hard: false },\n role: { type: \"text\", hard: false },\n },\n },\n },\n },\n // Phase F.3 — operator-tunable settings. Stresses the\n // auto-form on deep schema (10 fields, range-constrained\n // numbers, color regex, nested array of objects).\n settingsSchema: portfolioSettingsSchema,\n },\n impl: {\n shell: PortfolioShell,\n slots: {\n header: PortfolioHeader,\n footer: PortfolioFooter,\n },\n // Dark palette is now token-driven — previously the dark\n // surface was hardcoded as `#0b0b0c` in `styles.ts`, so admin\n // overrides couldn't reach it. Tokens here flip background +\n // foreground for the whole shell; `styles.ts` reads them via\n // `var(--np-color-*)` so a single token change reflows the\n // entire theme. Light variant: override these in the admin's\n // theme settings tab — no fork required.\n tokens: {\n colors: {\n primary: \"oklch(0.985 0.001 106)\",\n primaryForeground: \"oklch(0.145 0.005 285)\",\n background: \"oklch(0.16 0.005 285)\",\n foreground: \"oklch(0.91 0.003 286)\",\n muted: \"oklch(0.22 0.006 286)\",\n mutedForeground: \"oklch(0.66 0.005 286)\",\n border: \"oklch(0.28 0.008 286)\",\n card: \"oklch(0.20 0.006 286)\",\n cardForeground: \"oklch(0.91 0.003 286)\",\n accent: \"oklch(0.32 0.012 286)\",\n accentForeground: \"oklch(0.985 0.001 106)\",\n },\n typography: {\n fontHeading:\n '\"Inter\", system-ui, -apple-system, \"Segoe UI\", sans-serif',\n fontBody:\n '\"Inter\", system-ui, -apple-system, \"Segoe UI\", sans-serif',\n },\n },\n css: portfolioCss,\n templates: {\n pages: {\n default: {\n label: \"Default\",\n description: \"Centered text column on dark background.\",\n component: PageDefaultTemplate,\n },\n gallery: {\n label: \"Gallery\",\n description:\n \"Two-column block grid for image-led project pages and case studies.\",\n component: PageGalleryTemplate,\n },\n },\n posts: {\n detail: {\n label: \"Project detail\",\n description:\n \"Hero image, centered title and excerpt, role / year / client meta strip, then the body blocks.\",\n component: ProjectDetailTemplate,\n },\n index: {\n label: \"Project index\",\n description:\n \"Archive grid of square project cards with hover-fade captions.\",\n component: ProjectIndexTemplate,\n },\n },\n },\n // F.2 — theme routes. `/work/:slug` dispatches a posts row\n // through `ProjectDetailTemplate` (#613). Without this, the\n // `/work/<slug>` URLs `PortfolioProjectCard` emits would\n // 404 — the framework catch-all only resolves `pages` rows\n // by URL, so case studies (`posts` collection) need a theme\n // route to be reachable.\n routes: [\n { pattern: \"/work/:slug\", component: PortfolioProjectDetailRoute },\n ],\n // Phase F.4 — portfolio-shipped block types.\n blocks: portfolioBlocks,\n // Phase F.6 — declared nav locations.\n navLocations: {\n primary: {\n label: \"Primary nav\",\n description: \"Top nav links (Work / About / Contact).\",\n maxItems: 5,\n },\n footerSocial: {\n label: \"Footer social links\",\n description: \"Social profile links shown in the footer.\",\n maxItems: 6,\n },\n },\n // Phase F.7 — error chrome.\n notFound: PortfolioNotFound,\n // M.* adoption (2026-05-11). Portfolio gains purpose-built\n // member chrome: narrow column wrapping the auth forms,\n // tonally matched 404 + error pages. The fallback chain in\n // `<ShellWrap surface=\"member\">` would have walked back to\n // `impl.shell` + the public slots, which would have stretched\n // a 320-wide login form across the image-led wide layout.\n // - `shell`: PortfolioMembersShell (narrow column, same\n // header/footer chrome so a masthead bump cascades).\n // - `notFound`: PortfolioMembersNotFound (stale-auth-link\n // framing with /members/login CTA).\n // - `error`: forward-compat type marker; the actual render\n // goes through `./components/members-error`'s client\n // subpath, lazy-imported by\n // `apps/web/src/app/(member)/error.tsx`'s registry\n // (F.7.1 delegation — Next mandates `error.tsx` is \"use\n // client\").\n members: {\n shell: PortfolioMembersShell,\n notFound: PortfolioMembersNotFound,\n },\n },\n});\n\nexport {\n PortfolioHeader,\n PortfolioFooter,\n PortfolioShell,\n PortfolioMembersShell,\n PortfolioMembersNotFound,\n PortfolioProjectCard,\n PortfolioMobileNav,\n PortfolioNotFound,\n};\nexport { portfolioCss };\nexport type { PortfolioProjectDoc };\nexport {\n portfolioSettingsSchema,\n type PortfolioSettings,\n} from \"./settings.js\";\n","import { getCachedThemeSettings } from \"@nexpress/next\";\n\nimport {\n portfolioSettingsSchema,\n type PortfolioSettings,\n} from \"./settings.js\";\n\n/**\n * Phase F.9.1-A — typed accessor over the cached theme settings\n * read.\n *\n * Uses `getCachedThemeSettings` so multiple resolveSettings()\n * calls in the same request (shell + header + footer + N\n * cards) share one DB hit through Next's `unstable_cache`.\n * The `nx:theme:<siteId>` tag invalidation handles freshness;\n * settings-save / theme-switch already bust it.\n */\nexport async function resolvePortfolioSettings(): Promise<PortfolioSettings> {\n const raw = await getCachedThemeSettings(\"portfolio\");\n const parsed = portfolioSettingsSchema.safeParse(raw);\n if (parsed.success) return parsed.data;\n return portfolioSettingsSchema.parse({});\n}\n","import { z } from \"zod\";\n\n/**\n * Phase F.9-C — operator-tunable portfolio settings.\n *\n * Stresses F.3's auto-form on **deep settings**: many fields,\n * mixed types, range-constrained numbers, color picker (hex\n * regex heuristic), nested array of objects with required\n * sub-fields. Combined with magazine's enum/array shape and\n * docs' text-heavy shape, this round-trips F.3's full\n * widget surface.\n */\nexport const portfolioSettingsSchema = z.object({\n // Layout\n gridColumns: z\n .number()\n .int()\n .min(1)\n .max(6)\n .default(3)\n .describe(\"Number of columns in the project archive grid (1–6).\"),\n cardAspect: z\n .enum([\"square\", \"portrait\", \"landscape\", \"golden\"])\n .default(\"square\")\n .describe(\n \"Aspect ratio of project cards: square (1:1), portrait (3:4), landscape (4:3), or golden (1:1.618).\",\n ),\n hoverStyle: z\n .enum([\"fade\", \"scale\", \"slide\", \"lift\"])\n .default(\"fade\")\n .describe(\n \"Hover effect on project cards. fade: caption fades in. scale: image zooms 1.05x. slide: caption slides up. lift: card lifts with shadow.\",\n ),\n galleryGutter: z\n .number()\n .int()\n .min(0)\n .max(64)\n .default(16)\n .describe(\"Gap between project cards in pixels (0–64).\"),\n // Project meta\n showProjectMeta: z\n .boolean()\n .default(true)\n .describe(\"Show role / year / client meta strip on project detail pages.\"),\n showProjectTags: z\n .boolean()\n .default(false)\n .describe(\"Show tag chips below project titles on the index grid.\"),\n // Brand\n accentColor: z\n .string()\n .regex(/^#[0-9a-f]{6}$/i)\n .optional()\n .describe(\n \"Optional accent color override (hex). Used for hover states and the masthead underline.\",\n ),\n studioName: z\n .string()\n .default(\"Studio\")\n .describe(\"Studio / personal name shown in the masthead and footer.\"),\n aboutCopy: z\n .string()\n .default(\"\")\n .meta({ widget: \"textarea\", rows: 4 })\n .describe(\n \"Optional short bio for the studio. Renders as a multi-line textarea in admin (4 rows) and as a small paragraph above the footer contact line on the public site.\",\n ),\n // Footer\n showFooterCredit: z\n .boolean()\n .default(true)\n .describe(\n \"Show 'Built with NexPress' credit in the footer. Some studios prefer an unbranded footer.\",\n ),\n copyrightYear: z\n .number()\n .int()\n .min(2000)\n .max(2100)\n .optional()\n .describe(\n \"Optional fixed copyright year. Defaults to the current year when omitted.\",\n ),\n // Client logos\n clientLogos: z\n .array(\n z.object({\n name: z.string().describe(\"Client name (alt text + caption)\"),\n logoUrl: z.string().url().describe(\"Logo image URL\"),\n link: z.string().url().optional().describe(\"Optional case-study link\"),\n }),\n )\n .default([])\n .describe(\n \"Client logos rendered in the homepage 'Selected clients' strip. Edit per project.\",\n ),\n});\n\nexport type PortfolioSettings = z.infer<typeof portfolioSettingsSchema>;\n","import * as React from \"react\";\nimport type { NpBlockDefinition } from \"@nexpress/blocks\";\n\nimport { resolvePortfolioSettings } from \"./settings-helpers.js\";\n\n/**\n * Phase F.9-C — portfolio-specific block types.\n *\n * Type prefix: `portfolio.*`. Bootstrap auto-stamps\n * `source: \"theme:portfolio\"` so multi-site processes scope\n * these correctly.\n *\n * Two blocks shipping with v0.2:\n * - `portfolio.case-study-hero`: full-bleed image + project\n * meta. Drop at the top of a case-study page.\n * - `portfolio.image-grid`: responsive image gallery. The\n * editorial alternative to a regular gridBlock for\n * image-heavy case studies.\n */\n\ninterface CaseStudyHeroProps {\n title: string;\n subtitle?: string;\n client?: string;\n year?: string;\n role?: string;\n imageUrl?: string;\n}\n\nfunction CaseStudyHero(props: Record<string, unknown>): React.ReactElement {\n const { title, subtitle, client, year, role, imageUrl } =\n props as unknown as CaseStudyHeroProps;\n return (\n <section\n className=\"np-portfolio-case-study-hero\"\n style={{\n position: \"relative\",\n margin: \"0 0 2rem\",\n padding: 0,\n minHeight: imageUrl ? \"60vh\" : \"auto\",\n backgroundImage: imageUrl ? `url(${imageUrl})` : undefined,\n backgroundSize: \"cover\",\n backgroundPosition: \"center\",\n color: imageUrl ? \"white\" : \"inherit\",\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"flex-end\",\n }}\n >\n <div\n style={{\n padding: \"3rem 1.5rem 2rem\",\n background: imageUrl\n ? \"linear-gradient(180deg, transparent, rgba(0,0,0,0.65))\"\n : undefined,\n }}\n >\n <h1\n style={{\n fontFamily: \"var(--np-font-heading)\",\n fontSize: \"clamp(2rem, 5vw, 3.75rem)\",\n fontWeight: 600,\n margin: 0,\n letterSpacing: \"-0.02em\",\n }}\n >\n {title}\n </h1>\n {subtitle ? (\n <p\n style={{\n margin: \"0.75rem 0 0\",\n fontSize: \"1.125rem\",\n maxWidth: \"60ch\",\n opacity: 0.9,\n }}\n >\n {subtitle}\n </p>\n ) : null}\n {(client || year || role) ? (\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"2rem\",\n marginTop: \"1.5rem\",\n fontSize: \"0.875rem\",\n opacity: 0.8,\n }}\n >\n {client ? (\n <div>\n <span style={{ display: \"block\", opacity: 0.6, fontSize: \"0.75rem\", textTransform: \"uppercase\", letterSpacing: \"0.08em\" }}>\n Client\n </span>\n {client}\n </div>\n ) : null}\n {year ? (\n <div>\n <span style={{ display: \"block\", opacity: 0.6, fontSize: \"0.75rem\", textTransform: \"uppercase\", letterSpacing: \"0.08em\" }}>\n Year\n </span>\n {year}\n </div>\n ) : null}\n {role ? (\n <div>\n <span style={{ display: \"block\", opacity: 0.6, fontSize: \"0.75rem\", textTransform: \"uppercase\", letterSpacing: \"0.08em\" }}>\n Role\n </span>\n {role}\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n </section>\n );\n}\n\ninterface ImageGridItem {\n url: string;\n alt?: string;\n caption?: string;\n}\n\ninterface ImageGridProps {\n columns?: number;\n items: ImageGridItem[];\n}\n\nfunction ImageGrid(props: Record<string, unknown>): React.ReactElement {\n const { columns, items } = props as unknown as ImageGridProps;\n const cols = typeof columns === \"number\" && columns > 0 ? columns : 2;\n return (\n <section\n className=\"np-portfolio-image-grid\"\n style={{\n margin: \"2rem 0\",\n display: \"grid\",\n gap: \"1rem\",\n gridTemplateColumns: `repeat(${cols}, 1fr)`,\n }}\n >\n {items.map((item, i) => (\n <figure key={i} style={{ margin: 0 }}>\n <img\n src={item.url}\n alt={item.alt ?? \"\"}\n style={{\n display: \"block\",\n width: \"100%\",\n height: \"auto\",\n borderRadius: \"0.25rem\",\n }}\n />\n {item.caption ? (\n <figcaption\n style={{\n fontSize: \"0.8125rem\",\n color: \"var(--np-color-muted-foreground)\",\n marginTop: \"0.5rem\",\n }}\n >\n {item.caption}\n </figcaption>\n ) : null}\n </figure>\n ))}\n </section>\n );\n}\n\nexport const portfolioBlocks: NpBlockDefinition[] = [\n {\n type: \"portfolio.case-study-hero\",\n label: \"Case study hero\",\n iconKind: \"lucide\",\n icon: \"image\",\n keywords: [\"hero\", \"case-study\", \"portfolio\", \"project\"],\n defaultProps: {\n title: \"Project name\",\n subtitle: \"One-sentence project summary.\",\n client: \"Client name\",\n year: \"2026\",\n role: \"Design + Engineering\",\n imageUrl: \"\",\n },\n propsSchema: [\n { name: \"title\", label: \"Project title\", type: \"text\" },\n { name: \"subtitle\", label: \"Subtitle\", type: \"textarea\" },\n { name: \"client\", label: \"Client\", type: \"text\" },\n { name: \"year\", label: \"Year\", type: \"text\" },\n { name: \"role\", label: \"Role\", type: \"text\" },\n { name: \"imageUrl\", label: \"Hero image URL\", type: \"url\" },\n ],\n render: (props) => <CaseStudyHero {...props} />,\n },\n {\n type: \"portfolio.image-grid\",\n label: \"Image grid\",\n iconKind: \"lucide\",\n icon: \"grid-3x3\",\n keywords: [\"images\", \"gallery\", \"grid\", \"portfolio\"],\n defaultProps: {\n columns: 2,\n items: [\n { url: \"https://placehold.co/800x600\", alt: \"\", caption: \"\" },\n { url: \"https://placehold.co/800x600\", alt: \"\", caption: \"\" },\n ],\n },\n propsSchema: [\n { name: \"columns\", label: \"Columns\", type: \"number\" },\n // `items` edited as JSON in v0.2; richer per-item editor\n // (drag-to-reorder, add/remove) tracked as F.5.1 polish.\n { name: \"items\", label: \"Items (JSON)\", type: \"textarea\" },\n ],\n render: (props) => <ImageGrid {...props} />,\n },\n {\n type: \"portfolio.client-logos\",\n label: \"Client logos strip\",\n iconKind: \"lucide\",\n icon: \"users\",\n keywords: [\"clients\", \"logos\", \"portfolio\", \"selected work\"],\n defaultProps: {\n heading: \"Selected clients\",\n },\n propsSchema: [\n { name: \"heading\", label: \"Section heading\", type: \"text\" },\n ],\n // `ClientLogosStrip` is itself an async server component —\n // it reads `settings.clientLogos` so the operator manages\n // logos in admin's Theme settings panel (a single canonical\n // source) rather than re-typing per block instance. The render\n // arrow itself is sync (just constructs the element); React\n // resolves the inner async at render time.\n render: (props) => <ClientLogosStrip {...(props as { heading?: string })} />,\n },\n];\n\ninterface ClientLogosStripProps {\n heading?: string;\n}\n\nasync function ClientLogosStrip({\n heading,\n}: ClientLogosStripProps): Promise<React.ReactElement | null> {\n const settings = await resolvePortfolioSettings();\n const logos = settings.clientLogos;\n // Public site never shows a \"configure in admin\" placeholder\n // — visitors don't need to see operator-facing copy. Empty\n // setting means render nothing; the block instance is invisible\n // until the operator populates the list. The page builder admin\n // surface DOES surface the empty state separately (block picker\n // shows the block's defaultProps); operators see it during\n // configuration, not visitors.\n if (logos.length === 0) return null;\n return (\n <section\n className=\"np-portfolio-client-logos\"\n style={{\n margin: \"3rem 0\",\n }}\n >\n {heading ? (\n <h2\n style={{\n margin: \"0 0 1.5rem\",\n fontSize: \"0.8125rem\",\n textTransform: \"uppercase\",\n letterSpacing: \"0.1em\",\n color: \"var(--np-color-muted-foreground)\",\n textAlign: \"center\",\n }}\n >\n {heading}\n </h2>\n ) : null}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: `repeat(auto-fit, minmax(140px, 1fr))`,\n gap: \"2rem\",\n alignItems: \"center\",\n justifyItems: \"center\",\n }}\n >\n {logos.map((logo, i) => {\n const img = (\n <img\n src={logo.logoUrl}\n alt={logo.name}\n style={{\n maxHeight: \"48px\",\n maxWidth: \"100%\",\n opacity: 0.7,\n transition: \"opacity 0.2s ease\",\n }}\n />\n );\n return (\n <div key={`portfolio-logo-${i.toString()}`}>\n {logo.link ? (\n <a href={logo.link} target=\"_blank\" rel=\"noreferrer\">\n {img}\n </a>\n ) : (\n img\n )}\n </div>\n );\n })}\n </div>\n </section>\n );\n}\n","import * as React from \"react\";\n\nimport { resolvePortfolioSettings } from \"../settings-helpers.js\";\n\n/**\n * Portfolio \"project\" card. Visual-first: a large image (1:1\n * by default) with an overlaid title that fades in on hover.\n * Defensive on the doc shape so collections of any kind can\n * be routed through this card.\n *\n * Phase F.9.1-B — `settings.showProjectTags` toggles the\n * category/tag chip below the title. Operators who want a\n * cleaner card grid flip it off.\n */\n\nexport interface PortfolioProjectDoc {\n id?: string;\n slug?: string;\n title?: string;\n category?: string;\n cover?: { url?: string; alt?: string } | string | null;\n}\n\nfunction coverUrl(value: PortfolioProjectDoc[\"cover\"]): string | null {\n if (!value) return null;\n if (typeof value === \"string\") return value;\n return value.url ?? null;\n}\n\nfunction coverAlt(value: PortfolioProjectDoc[\"cover\"], fallback: string): string {\n if (value && typeof value === \"object\" && value.alt) return value.alt;\n return fallback;\n}\n\nfunction projectHref(doc: PortfolioProjectDoc): string {\n if (doc.slug) {\n return doc.slug.startsWith(\"/\") ? doc.slug : `/work/${doc.slug}`;\n }\n return \"#\";\n}\n\nexport interface PortfolioProjectCardProps {\n doc: PortfolioProjectDoc;\n}\n\nexport async function PortfolioProjectCard({\n doc,\n}: PortfolioProjectCardProps): Promise<React.ReactElement> {\n const settings = await resolvePortfolioSettings();\n const href = projectHref(doc);\n const cover = coverUrl(doc.cover);\n const title = doc.title ?? \"Untitled\";\n return (\n <a href={href} className=\"np-portfolio-project-card\">\n <figure className=\"np-portfolio-project-cover\">\n {cover ? (\n <img src={cover} alt={coverAlt(doc.cover, title)} loading=\"lazy\" />\n ) : (\n <span className=\"np-portfolio-project-placeholder\" aria-hidden=\"true\" />\n )}\n <figcaption className=\"np-portfolio-project-caption\">\n <span className=\"np-portfolio-project-title\">{title}</span>\n {settings.showProjectTags && doc.category ? (\n <span className=\"np-portfolio-project-category\">{doc.category}</span>\n ) : null}\n </figcaption>\n </figure>\n </a>\n );\n}\n","import { resolvePortfolioSettings } from \"./settings-helpers.js\";\n\n/**\n * Studio footer — rows on a thin top rule:\n * 1. Optional bio (`settings.aboutCopy`) — short studio\n * description rendered above the contact line. Operators\n * who want a fuller about page do that through the page\n * builder; this is the ambient bio surfaced on every page.\n * 2. Contact line (NP_SOCIAL_EMAIL → mailto, or generic blurb)\n * 3. Social mini-strip (NP_SOCIAL_GITHUB / TWITTER / LINKEDIN /\n * MASTODON, all optional, hidden when none are configured)\n * 4. Colophon — year + framework credit, toggled by\n * `settings.showFooterCredit`. `settings.copyrightYear`\n * overrides the auto-detected year (some studios pin to\n * \"2024\" for an \"established\" feel).\n *\n * Stays minimal so the visual focus stays on the work above.\n */\nexport async function PortfolioFooter() {\n const settings = await resolvePortfolioSettings();\n const year = settings.copyrightYear ?? new Date().getFullYear();\n const email = process.env.NP_SOCIAL_EMAIL;\n const social = [\n { href: process.env.NP_SOCIAL_GITHUB, label: \"GitHub\" },\n { href: process.env.NP_SOCIAL_TWITTER, label: \"Twitter\" },\n { href: process.env.NP_SOCIAL_LINKEDIN, label: \"LinkedIn\" },\n { href: process.env.NP_SOCIAL_MASTODON, label: \"Mastodon\" },\n { href: process.env.NP_SOCIAL_DRIBBBLE, label: \"Dribbble\" },\n { href: process.env.NP_SOCIAL_INSTAGRAM, label: \"Instagram\" },\n ].filter((s): s is { href: string; label: string } => Boolean(s.href));\n const studio = settings.studioName;\n\n return (\n <footer className=\"np-site-footer np-portfolio-footer\">\n <div className=\"np-portfolio-footer-inner\">\n {settings.aboutCopy.length > 0 ? (\n <p\n className=\"np-portfolio-footer-bio\"\n style={{\n maxWidth: \"60ch\",\n fontSize: \"0.875rem\",\n color: \"var(--np-color-muted-foreground)\",\n margin: \"0 0 1.25rem\",\n }}\n >\n {settings.aboutCopy}\n </p>\n ) : null}\n <div className=\"np-portfolio-footer-contact\">\n {email ? (\n <a\n href={email.startsWith(\"mailto:\") ? email : `mailto:${email}`}\n className=\"np-portfolio-footer-email\"\n >\n {email.replace(/^mailto:/, \"\")}\n </a>\n ) : (\n <span className=\"np-portfolio-footer-email\">Available for select work</span>\n )}\n </div>\n {social.length > 0 ? (\n <ul className=\"np-portfolio-footer-social\">\n {social.map((s) => (\n <li key={s.href}>\n <a href={s.href} target=\"_blank\" rel=\"noopener noreferrer\">\n {s.label}\n </a>\n </li>\n ))}\n </ul>\n ) : null}\n <p className=\"np-portfolio-footer-mark\">\n © {year.toString()} · {studio}\n {settings.showFooterCredit ? \" · Built with NexPress\" : \"\"}\n </p>\n </div>\n </footer>\n );\n}\n","import type { NpNavItem } from \"@nexpress/core\";\nimport { getCachedNavigation } from \"@nexpress/next\";\n\nimport { PortfolioMobileNav } from \"./components/mobile-nav.js\";\nimport { resolvePortfolioSettings } from \"./settings-helpers.js\";\n\n/**\n * Slim sticky top bar. Studio name on the inline-start, primary\n * nav on the inline-end. The inline list hides below ~720px and\n * the mobile drawer takes over.\n *\n * Phase F.9.1-A — `settings.studioName` controls the brand\n * label. Default (\"Studio\") falls through if operator hasn't\n * customized.\n */\nexport async function PortfolioHeader() {\n const items = await getCachedNavigation(\"header\");\n const settings = await resolvePortfolioSettings();\n\n return (\n <header className=\"np-site-header np-portfolio-header\">\n <a href=\"/\" className=\"np-portfolio-logo\">\n {settings.studioName}\n </a>\n {items.length > 0 ? (\n <>\n <nav aria-label=\"Main\" className=\"np-portfolio-nav-desktop\">\n <ul className=\"np-portfolio-nav\">\n {items.map((item: NpNavItem, index: number) => (\n <li key={`portfolio-nav-${index.toString()}`} className=\"np-portfolio-nav-item\">\n <a href={item.url}>{item.label}</a>\n {item.children && item.children.length > 0 ? (\n <ul className=\"np-portfolio-subnav\">\n {item.children.map((child: NpNavItem, childIndex: number) => (\n <li key={`portfolio-nav-${index.toString()}-${childIndex.toString()}`}>\n <a href={child.url}>{child.label}</a>\n </li>\n ))}\n </ul>\n ) : null}\n </li>\n ))}\n </ul>\n </nav>\n <PortfolioMobileNav items={items} />\n </>\n ) : null}\n </header>\n );\n}\n","import * as React from \"react\";\n\n/**\n * Portfolio member-tree 404.\n *\n * Mirrors `PortfolioNotFound`'s minimal voice but tuned for the\n * member context — CTA points at `/members/login` rather than\n * the homepage, and the copy acknowledges stale auth links (the\n * dominant cause of 404s inside `/members/*`).\n *\n * Server component; rendered by `(member)/not-found.tsx` when\n * the active theme is portfolio and `impl.members.notFound` is\n * declared.\n *\n * Renders a `<div>`, not `<main>`, because the framework's\n * `<ShellWrap surface=\"member\">` already emits the page's\n * `<main className=\"np-member-main\">` landmark.\n */\nexport function PortfolioMembersNotFound(): React.ReactElement {\n return (\n <div\n className=\"np-portfolio-members-not-found\"\n style={{\n maxWidth: 480,\n margin: \"6rem auto\",\n padding: \"0 1.5rem\",\n textAlign: \"center\",\n }}\n >\n <p\n style={{\n margin: 0,\n fontSize: \"0.75rem\",\n textTransform: \"uppercase\",\n letterSpacing: \"0.18em\",\n color: \"var(--np-color-muted-foreground)\",\n fontFamily: \"var(--np-font-body)\",\n }}\n >\n Account\n </p>\n <h1\n style={{\n margin: \"1rem 0 0\",\n fontSize: \"clamp(1.75rem, 4vw, 2.5rem)\",\n fontFamily: \"var(--np-font-heading)\",\n fontWeight: 500,\n letterSpacing: \"-0.02em\",\n }}\n >\n Link no longer valid.\n </h1>\n <p\n style={{\n margin: \"1.25rem 0 0\",\n color: \"var(--np-color-muted-foreground)\",\n fontSize: \"0.9375rem\",\n lineHeight: 1.6,\n }}\n >\n Verification and password-reset links are single-use and short-lived.\n Request a fresh one from the sign-in page.\n </p>\n <a\n href=\"/members/login\"\n style={{\n display: \"inline-block\",\n marginTop: \"2rem\",\n padding: \"0.625rem 1.5rem\",\n borderRadius: \"0.25rem\",\n background: \"var(--np-color-primary)\",\n color: \"var(--np-color-primary-foreground)\",\n textDecoration: \"none\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n letterSpacing: \"0.02em\",\n }}\n >\n Go to sign in\n </a>\n </div>\n );\n}\n","import type { CSSProperties, ReactNode } from \"react\";\n\nimport { PortfolioFooter } from \"./footer.js\";\nimport { PortfolioHeader } from \"./header.js\";\nimport { resolvePortfolioSettings } from \"./settings-helpers.js\";\n\n/**\n * Portfolio member-tree shell. Same wrapping shape as\n * `PortfolioShell` (provides `np-portfolio` root + accent-color +\n * card-aspect CSS variables) but pulls the header/footer inline\n * because `<ShellWrap surface=\"member\">` opts OUT of the layout's\n * chrome-slot injection when a theme owns its own member shell\n * (F-track contract, see\n * `apps/web/src/components/shell-wrap.tsx`).\n *\n * Narrows the content column for auth forms (login / register /\n * reset / verify / notifications) — the portfolio public site\n * uses a wide image-led layout that would dwarf a 320-wide form.\n * Reuses `PortfolioHeader` / `PortfolioFooter` directly so a\n * theme-version bump touching the masthead reaches member pages\n * too — single source of truth for chrome.\n *\n * Does its own `<div className=\"np-portfolio\">` root because the\n * (site) `PortfolioShell` is bypassed when this shell takes over.\n */\nconst ASPECT_VALUES = {\n square: \"1 / 1\",\n portrait: \"3 / 4\",\n landscape: \"4 / 3\",\n golden: \"1 / 1.618\",\n} as const;\n\nexport async function PortfolioMembersShell({\n children,\n}: {\n children: ReactNode;\n}) {\n const settings = await resolvePortfolioSettings();\n const aspect = ASPECT_VALUES[settings.cardAspect];\n const styleVars: Record<string, string> = {\n \"--np-portfolio-card-aspect\": aspect,\n };\n if (settings.accentColor) {\n styleVars[\"--np-color-primary\"] = settings.accentColor;\n }\n return (\n <div\n className=\"np-portfolio\"\n data-hover-style={settings.hoverStyle}\n style={styleVars as CSSProperties}\n >\n <PortfolioHeader />\n <div className=\"np-portfolio-members\">\n <div className=\"np-portfolio-members-column\">{children}</div>\n </div>\n <PortfolioFooter />\n </div>\n );\n}\n","import * as React from \"react\";\n\n/**\n * Phase F.9-C — portfolio 404.\n *\n * Dark, sparse — matches the theme's surface palette. A single\n * line of copy + return-home link, centered.\n */\nexport function PortfolioNotFound(): React.ReactElement {\n // `<div>` — (site)/layout.tsx already emits the page's `<main>`.\n return (\n <div\n className=\"np-portfolio-not-found\"\n style={{\n minHeight: \"60vh\",\n maxWidth: 480,\n margin: \"0 auto\",\n padding: \"6rem 1.5rem\",\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n alignItems: \"center\",\n textAlign: \"center\",\n }}\n >\n <p\n style={{\n margin: 0,\n fontSize: \"0.75rem\",\n textTransform: \"uppercase\",\n letterSpacing: \"0.15em\",\n color: \"var(--np-color-muted-foreground)\",\n }}\n >\n 404\n </p>\n <h1\n style={{\n margin: \"1rem 0 0\",\n fontSize: \"clamp(1.75rem, 4vw, 2.5rem)\",\n fontWeight: 500,\n letterSpacing: \"-0.02em\",\n }}\n >\n Project not found\n </h1>\n <p\n style={{\n margin: \"1rem 0 2rem\",\n color: \"var(--np-color-muted-foreground)\",\n fontSize: \"0.9375rem\",\n }}\n >\n The page you're looking for moved or doesn't exist.\n </p>\n <a\n href=\"/\"\n style={{\n display: \"inline-block\",\n padding: \"0.5rem 1.5rem\",\n border: \"1px solid var(--np-color-border)\",\n borderRadius: \"0.25rem\",\n color: \"var(--np-color-foreground)\",\n textDecoration: \"none\",\n fontSize: \"0.875rem\",\n }}\n >\n See selected work →\n </a>\n </div>\n );\n}\n","import { findDocuments } from \"@nexpress/core\";\nimport type { NpRouteRenderProps, NpTemplateRenderProps } from \"@nexpress/theme\";\nimport { notFound } from \"next/navigation\";\nimport * as React from \"react\";\n\nimport { ProjectDetailTemplate } from \"../templates/project-detail.js\";\n\n/**\n * Theme route for `/work/:slug` — looks up a project row (the\n * `posts` collection, which portfolio uses for case studies) and\n * renders it through `templates.posts.detail`\n * (ProjectDetailTemplate) — #613.\n *\n * Without this route, `PortfolioProjectCard`'s `/work/<slug>`\n * links 404 in the reference app. The card emits the URL on its\n * own (`projectHref(doc)`) and the framework had no route to\n * back it.\n *\n * Defensive untyped `findDocuments<ProjectRow>` lookup — the\n * posts schema lives in the operator's project, not the theme.\n * `theme:install @nexpress/theme-portfolio` patches the\n * collection to include the fields ProjectDetailTemplate\n * expects (hero, role/year/client meta).\n *\n * Access / visibility: `findDocuments` already enforces\n * `access.read` and `community.visibility`, same as the\n * catch-all's `pages` lookup. We pass `status: \"published\"`\n * explicitly to hide drafts/pending from public URL access.\n */\n\ninterface ProjectRow {\n id: string;\n slug: string;\n title: string;\n body?: unknown;\n status?: string;\n hero?: unknown;\n excerpt?: string;\n category?: string;\n role?: string;\n year?: string;\n client?: string;\n}\n\nexport async function PortfolioProjectDetailRoute({\n params,\n blockCtx,\n}: NpRouteRenderProps): Promise<React.ReactElement> {\n const slug = typeof params.slug === \"string\" ? params.slug : \"\";\n if (!slug) notFound();\n\n const result = await findDocuments<ProjectRow>(\"posts\", {\n where: { slug, status: \"published\" },\n limit: 1,\n });\n const doc = result.docs[0];\n if (!doc) notFound();\n\n // `ProjectDetailTemplate`'s prop generic defaults to\n // `Record<string, unknown>` — cast through `unknown` so our\n // narrower `ProjectRow` shape (no index signature) matches.\n const templateProps: NpTemplateRenderProps = {\n doc: doc as unknown as Record<string, unknown>,\n blockCtx,\n };\n return <ProjectDetailTemplate {...templateProps} />;\n}\n","import * as React from \"react\";\nimport { renderBlocks } from \"@nexpress/blocks\";\nimport type { NpPageBlocks } from \"@nexpress/blocks\";\n\nimport type { NpTemplateRenderProps } from \"@nexpress/theme\";\n\nimport { resolvePortfolioSettings } from \"../settings-helpers.js\";\n\n/**\n * Project detail template. Big hero image, large title, optional\n * meta strip (role / year / client), then content blocks. The\n * content area uses a max-width column so prose stays readable\n * even on the dark surface; image blocks inside the body still\n * render edge-to-edge thanks to a nested full-bleed override.\n */\ninterface ProjectDoc {\n title?: string;\n excerpt?: string;\n cover?: { url?: string; alt?: string } | string | null;\n role?: string;\n year?: string | number;\n client?: string;\n blocks?: NpPageBlocks;\n}\n\nfunction coverUrl(value: ProjectDoc[\"cover\"]): string | null {\n if (!value) return null;\n if (typeof value === \"string\") return value;\n return value.url ?? null;\n}\n\nfunction coverAlt(value: ProjectDoc[\"cover\"], fallback: string): string {\n if (value && typeof value === \"object\" && value.alt) return value.alt;\n return fallback;\n}\n\nexport async function ProjectDetailTemplate({\n doc,\n blockCtx,\n}: NpTemplateRenderProps): Promise<React.ReactElement> {\n const project = doc as ProjectDoc;\n const settings = await resolvePortfolioSettings();\n const title = project.title ?? \"Untitled\";\n const cover = coverUrl(project.cover);\n return (\n <article className=\"np-portfolio-project-detail\">\n {cover ? (\n <figure className=\"np-portfolio-project-hero\">\n <img src={cover} alt={coverAlt(project.cover, title)} />\n </figure>\n ) : null}\n <header className=\"np-portfolio-project-header\">\n <h1>{title}</h1>\n {project.excerpt ? (\n <p className=\"np-portfolio-project-excerpt\">{project.excerpt}</p>\n ) : null}\n {settings.showProjectMeta &&\n (project.role || project.year || project.client) ? (\n <dl className=\"np-portfolio-project-meta\">\n {project.client ? (\n <>\n <dt>Client</dt>\n <dd>{project.client}</dd>\n </>\n ) : null}\n {project.role ? (\n <>\n <dt>Role</dt>\n <dd>{project.role}</dd>\n </>\n ) : null}\n {project.year ? (\n <>\n <dt>Year</dt>\n <dd>{String(project.year)}</dd>\n </>\n ) : null}\n </dl>\n ) : null}\n </header>\n {project.blocks && project.blocks.length > 0 ? (\n <div className=\"np-portfolio-project-body\">\n {renderBlocks(project.blocks, { ctx: blockCtx })}\n </div>\n ) : null}\n </article>\n );\n}\n","import type { CSSProperties, ReactNode } from \"react\";\n\nimport { resolvePortfolioSettings } from \"./settings-helpers.js\";\n\n/**\n * Phase F.9.1-B — shell renders inline `<style>` block that\n * threads operator settings into CSS custom properties:\n *\n * --np-color-primary — settings.accentColor override\n * --np-portfolio-aspect — settings.cardAspect (CSS aspect-ratio value)\n * --np-portfolio-hover — settings.hoverStyle (data attribute consumed\n * by styles.ts hover variants)\n *\n * The card / hover styles inside `styles.ts` read the variables\n * + a `[data-hover-style=\"<x>\"]` attribute set on this element\n * so the hover effect swaps without restructuring the cards\n * themselves.\n */\nconst ASPECT_VALUES = {\n square: \"1 / 1\",\n portrait: \"3 / 4\",\n landscape: \"4 / 3\",\n golden: \"1 / 1.618\",\n} as const;\n\nexport async function PortfolioShell({ children }: { children: ReactNode }) {\n const settings = await resolvePortfolioSettings();\n const aspect = ASPECT_VALUES[settings.cardAspect];\n const styleVars: Record<string, string> = {\n \"--np-portfolio-card-aspect\": aspect,\n };\n if (settings.accentColor) {\n styleVars[\"--np-color-primary\"] = settings.accentColor;\n }\n return (\n <div\n className=\"np-portfolio\"\n data-hover-style={settings.hoverStyle}\n style={styleVars as CSSProperties}\n >\n {children}\n </div>\n );\n}\n","/**\n * Theme-owned CSS for `@nexpress/theme-portfolio`. Reads from the\n * theme token system (background / foreground / primary / card /\n * muted) so admin token overrides reflow the whole shell. The\n * dark surface ships via `impl.tokens` in `index.ts`; that's the\n * single point of truth, this CSS just consumes it. Scoped under\n * `.np-portfolio-*` so swapping themes never leaves residue.\n *\n * Decorative dividers stay as `rgba(255, 255, 255, …)` since they're\n * tied to the dark assumption — flipping to a light palette is an\n * intentional fork that needs a fresh divider color anyway.\n */\nexport const portfolioCss = `\n.np-portfolio {\n background: var(--np-color-background);\n color: var(--np-color-foreground);\n min-height: 100vh;\n font-family: var(--np-font-body, \"Inter\", system-ui, sans-serif);\n}\n.np-portfolio a { color: inherit; }\n.np-portfolio ::selection {\n background: var(--np-color-primary);\n color: var(--np-color-primary-foreground);\n}\n\n/* ----------------------------------------------------------------\n * Header\n * --------------------------------------------------------------- */\n.np-portfolio-header {\n background: color-mix(in oklab, var(--np-color-background) 85%, transparent);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n border-bottom: 1px solid color-mix(in oklab, var(--np-color-foreground) 8%, transparent);\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 1rem 2rem;\n position: sticky;\n top: 0;\n z-index: 30;\n gap: 1rem;\n}\n.np-portfolio-logo {\n font-weight: 600;\n letter-spacing: 0.02em;\n text-decoration: none;\n font-size: 0.95rem;\n}\n.np-portfolio-nav {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n gap: 1.5rem;\n font-size: 0.875rem;\n}\n.np-portfolio-nav a {\n text-decoration: none;\n opacity: 0.75;\n transition: opacity 0.15s ease;\n}\n.np-portfolio-nav a:hover { opacity: 1; }\n.np-portfolio-nav-item {\n position: relative;\n}\n.np-portfolio-subnav {\n position: absolute;\n top: 100%;\n left: 0;\n display: none;\n min-width: 11rem;\n padding: 0.5rem 0;\n margin: 0;\n list-style: none;\n background: var(--np-color-card, #fff);\n border: 1px solid var(--np-color-border, #e5e7eb);\n border-radius: var(--np-radius-md, 0.5rem);\n box-shadow: 0 4px 16px -8px rgba(0, 0, 0, 0.08);\n z-index: 10;\n}\n.np-portfolio-nav-item:hover > .np-portfolio-subnav,\n.np-portfolio-nav-item:focus-within > .np-portfolio-subnav {\n display: block;\n}\n.np-portfolio-subnav a {\n display: block;\n padding: 0.4rem 1rem;\n font-size: 0.875rem;\n}\n.np-portfolio-mobile-subnav {\n list-style: none;\n margin: 0;\n padding-inline-start: 1.25rem;\n}\n\n/* Mobile drawer */\n.np-portfolio-nav-toggle {\n display: none;\n align-items: center;\n justify-content: center;\n padding: 0.4rem 0.85rem;\n border: 1px solid color-mix(in oklab, var(--np-color-foreground) 20%, transparent);\n border-radius: 999px;\n background: transparent;\n color: inherit;\n font: inherit;\n font-size: 0.75rem;\n letter-spacing: 0.06em;\n cursor: pointer;\n}\n.np-portfolio-nav-toggle:hover {\n border-color: color-mix(in oklab, var(--np-color-foreground) 50%, transparent);\n}\n.np-portfolio-nav-drawer {\n position: fixed;\n inset: 0;\n background: color-mix(in oklab, var(--np-color-background) 95%, transparent);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n z-index: 50;\n display: flex;\n align-items: center;\n justify-content: center;\n opacity: 0;\n visibility: hidden;\n transition: opacity 0.25s ease, visibility 0.25s ease;\n}\n.np-portfolio-nav-drawer[data-open=\"true\"] {\n opacity: 1;\n visibility: visible;\n}\n.np-portfolio-nav-drawer-list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n text-align: center;\n font-size: clamp(1.4rem, 3vw, 2rem);\n font-weight: 500;\n letter-spacing: -0.01em;\n}\n.np-portfolio-nav-drawer-list a {\n color: inherit;\n text-decoration: none;\n opacity: 0.85;\n transition: opacity 0.15s ease;\n}\n.np-portfolio-nav-drawer-list a:hover { opacity: 1; }\n\n@media (max-width: 720px) {\n .np-portfolio-nav-desktop { display: none; }\n .np-portfolio-nav-toggle { display: inline-flex; }\n}\n@media (min-width: 721px) {\n .np-portfolio-nav-drawer { display: none; }\n}\n\n/* ----------------------------------------------------------------\n * Footer\n * --------------------------------------------------------------- */\n.np-portfolio-footer {\n border-top: 1px solid color-mix(in oklab, var(--np-color-foreground) 8%, transparent);\n margin-top: 6rem;\n background: transparent;\n text-align: center;\n}\n.np-portfolio-footer-inner {\n max-width: 960px;\n margin: 0 auto;\n padding: 2.5rem 1.5rem;\n display: flex;\n flex-direction: column;\n gap: 1rem;\n align-items: center;\n}\n.np-portfolio-footer-contact { font-size: 1.05rem; }\n.np-portfolio-footer-email {\n text-decoration: none;\n letter-spacing: 0.02em;\n border-bottom: 1px solid color-mix(in oklab, var(--np-color-foreground) 40%, transparent);\n padding-bottom: 0.15rem;\n}\n.np-portfolio-footer-email:hover {\n border-bottom-color: color-mix(in oklab, var(--np-color-foreground) 85%, transparent);\n}\n.np-portfolio-footer-social {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n gap: 1.5rem;\n font-size: 0.85rem;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.np-portfolio-footer-social a {\n text-decoration: none;\n opacity: 0.65;\n transition: opacity 0.15s ease;\n}\n.np-portfolio-footer-social a:hover { opacity: 1; }\n.np-portfolio-footer-mark {\n margin: 0;\n font-size: 0.78rem;\n opacity: 0.5;\n letter-spacing: 0.06em;\n}\n\n/* ----------------------------------------------------------------\n * Page templates\n * --------------------------------------------------------------- */\n.np-portfolio-page {\n max-width: 720px;\n margin: 0 auto;\n padding: 4rem 1.5rem;\n line-height: 1.7;\n}\n.np-portfolio-page h1,\n.np-portfolio-page h2,\n.np-portfolio-page h3 { letter-spacing: -0.01em; }\n\n.np-portfolio-gallery {\n max-width: 1280px;\n margin: 0 auto;\n padding: 3rem 1.5rem 4rem;\n}\n.np-portfolio-gallery > h1 {\n text-align: center;\n font-size: clamp(2rem, 4vw, 3.5rem);\n margin: 0 0 2.5rem;\n letter-spacing: -0.02em;\n}\n.np-portfolio-gallery-grid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 1.5rem;\n}\n@media (min-width: 720px) {\n .np-portfolio-gallery-grid { grid-template-columns: 1fr 1fr; }\n}\n.np-portfolio-gallery-grid img {\n width: 100%;\n height: auto;\n display: block;\n border-radius: 8px;\n}\n\n/* ----------------------------------------------------------------\n * Project index (grid of cards)\n * --------------------------------------------------------------- */\n.np-portfolio-index {\n max-width: 1320px;\n margin: 0 auto;\n padding: 3.5rem 1.5rem 4rem;\n}\n.np-portfolio-index-header {\n text-align: center;\n margin-bottom: 3rem;\n}\n.np-portfolio-index-header h1 {\n font-size: clamp(2.25rem, 4vw, 3rem);\n letter-spacing: -0.02em;\n margin: 0 0 0.65rem;\n font-weight: 600;\n}\n.np-portfolio-index-header p {\n margin: 0 auto;\n max-width: 38rem;\n opacity: 0.75;\n line-height: 1.6;\n}\n.np-portfolio-index-empty {\n text-align: center;\n padding: 4rem 1.5rem;\n opacity: 0.6;\n}\n.np-portfolio-index-grid {\n /* Phase F.9.1-A — operator's settings.gridColumns\n * sets --np-portfolio-grid-cols on this element via the\n * project-index template. Mobile clamps to 1 column\n * regardless; tablet caps at min(2, --np-portfolio-grid-cols);\n * desktop honors the operator's choice up to 6. Operator\n * stays in control without breaking responsive design.\n */\n --np-portfolio-grid-cols: 3;\n --np-portfolio-grid-gutter: 1.5rem;\n display: grid;\n grid-template-columns: 1fr;\n gap: var(--np-portfolio-grid-gutter);\n}\n@media (min-width: 640px) {\n .np-portfolio-index-grid {\n grid-template-columns: repeat(min(2, var(--np-portfolio-grid-cols, 3)), 1fr);\n }\n}\n@media (min-width: 1024px) {\n .np-portfolio-index-grid {\n grid-template-columns: repeat(var(--np-portfolio-grid-cols, 3), 1fr);\n }\n}\n\n/* ----------------------------------------------------------------\n * Project card\n * --------------------------------------------------------------- */\n.np-portfolio-project-card {\n display: block;\n text-decoration: none;\n color: inherit;\n position: relative;\n overflow: hidden;\n border-radius: 4px;\n background: var(--np-color-card);\n}\n.np-portfolio-project-cover {\n margin: 0;\n position: relative;\n /* Phase F.9.1-B — operator-tunable aspect via shell-set\n * --np-portfolio-card-aspect (square / portrait / landscape /\n * golden). Falls back to 4/3 when the variable isn't set. */\n aspect-ratio: var(--np-portfolio-card-aspect, 4 / 3);\n overflow: hidden;\n}\n.np-portfolio-project-cover img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n transition: transform 0.5s ease, filter 0.4s ease;\n}\n/* Phase F.9.1-B — hoverStyle variants. Selected via the shell's\n * data-hover-style attribute. Default (\"fade\") = caption fades in\n * + image scale; the others swap the image effect.\n * - scale: only the image zooms (caption stays subtle)\n * - slide: image stays put; caption slides up from below\n * - lift: card lifts with shadow; image static\n */\n.np-portfolio[data-hover-style=\"fade\"] .np-portfolio-project-card:hover .np-portfolio-project-cover img,\n.np-portfolio[data-hover-style=\"scale\"] .np-portfolio-project-card:hover .np-portfolio-project-cover img {\n transform: scale(1.04);\n}\n.np-portfolio[data-hover-style=\"lift\"] .np-portfolio-project-card {\n transition: transform 0.3s ease, box-shadow 0.3s ease;\n}\n.np-portfolio[data-hover-style=\"lift\"] .np-portfolio-project-card:hover {\n transform: translateY(-4px);\n box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35);\n}\n.np-portfolio[data-hover-style=\"slide\"] .np-portfolio-project-caption {\n /* Slide-up reveal — caption starts further below + opacity 0 */\n transform: translateY(24px);\n}\n.np-portfolio-project-placeholder {\n display: block;\n width: 100%;\n height: 100%;\n background: linear-gradient(\n 135deg,\n var(--np-color-muted) 0%,\n var(--np-color-accent) 100%\n );\n}\n.np-portfolio-project-caption {\n position: absolute;\n inset: auto 0 0 0;\n padding: 1rem 1.25rem;\n background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, transparent 100%);\n display: flex;\n flex-direction: column;\n gap: 0.2rem;\n opacity: 0;\n transform: translateY(8px);\n transition: opacity 0.25s ease, transform 0.25s ease;\n}\n.np-portfolio-project-card:hover .np-portfolio-project-caption {\n opacity: 1;\n transform: translateY(0);\n}\n.np-portfolio-project-title {\n font-weight: 600;\n letter-spacing: 0.01em;\n font-size: 1rem;\n}\n.np-portfolio-project-category {\n font-size: 0.72rem;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n opacity: 0.8;\n}\n\n/* ----------------------------------------------------------------\n * Project detail\n * --------------------------------------------------------------- */\n.np-portfolio-project-detail {\n margin: 0;\n padding: 0 0 4rem;\n}\n.np-portfolio-project-hero {\n margin: 0;\n width: 100%;\n aspect-ratio: 21 / 9;\n overflow: hidden;\n background: var(--np-color-card);\n}\n.np-portfolio-project-hero img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n.np-portfolio-project-header {\n max-width: 760px;\n margin: 0 auto;\n padding: 3rem 1.5rem 2rem;\n text-align: center;\n}\n.np-portfolio-project-header h1 {\n font-size: clamp(2rem, 4vw, 3rem);\n letter-spacing: -0.02em;\n margin: 0 0 0.85rem;\n font-weight: 600;\n}\n.np-portfolio-project-excerpt {\n margin: 0 auto;\n max-width: 38rem;\n opacity: 0.75;\n font-size: 1.075rem;\n line-height: 1.55;\n}\n.np-portfolio-project-meta {\n margin: 2rem auto 0;\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(8rem, max-content));\n justify-content: center;\n gap: 0 2rem;\n font-size: 0.85rem;\n text-align: start;\n}\n.np-portfolio-project-meta dt {\n text-transform: uppercase;\n letter-spacing: 0.16em;\n font-size: 0.7rem;\n opacity: 0.55;\n margin-bottom: 0.2rem;\n}\n.np-portfolio-project-meta dd {\n margin: 0 0 0.75rem;\n font-weight: 500;\n}\n.np-portfolio-project-body {\n max-width: 720px;\n margin: 0 auto;\n padding: 0 1.5rem;\n font-size: 1.05rem;\n line-height: 1.7;\n opacity: 0.92;\n}\n.np-portfolio-project-body img {\n max-width: 100%;\n height: auto;\n border-radius: 6px;\n margin: 1.5rem 0;\n}\n\n/* Re-cast the .np-page baseline so links pick up the theme's\n primary token. Dark theme: primary is light-on-dark, so the\n link reads correctly. */\n.np-portfolio .np-page a {\n color: var(--np-color-primary);\n}\n\n/* M.* member surface — narrow auth-form column under the\n masthead. The portfolio's public layout is image-led wide;\n stretching a login form across that would look weird. */\n.np-portfolio-members {\n display: flex;\n justify-content: center;\n min-height: 60vh;\n padding: 3rem 1.5rem;\n}\n.np-portfolio-members-column {\n width: 100%;\n max-width: 420px;\n}\n\n/* Member form token overrides — portfolio's minimal aesthetic:\n sharp corners, hairline borders, theme primary on focus. */\n.np-portfolio .np-members-form {\n --np-member-form-input-bg: transparent;\n --np-member-form-input-border: var(--np-color-border);\n --np-member-form-input-border-focus: var(--np-color-primary);\n --np-member-form-input-radius: 0.25rem;\n --np-member-form-button-radius: 0.25rem;\n}\n.np-portfolio .np-members-form .np-form-label {\n font-size: 0.75rem;\n text-transform: uppercase;\n letter-spacing: 0.12em;\n}\n`.trim();\n","import { renderBlocks } from \"@nexpress/blocks\";\nimport type { NpPageBlocks } from \"@nexpress/blocks\";\n\nimport type { NpTemplateRenderProps } from \"@nexpress/theme\";\n\nexport function PageDefaultTemplate({ doc, blockCtx }: NpTemplateRenderProps) {\n const blocks = (doc as { blocks?: NpPageBlocks }).blocks;\n const title = (doc as { title?: string }).title;\n return (\n <article className=\"np-page np-portfolio-page\">\n {title ? <h1>{title}</h1> : null}\n {blocks ? renderBlocks(blocks, { ctx: blockCtx }) : null}\n </article>\n );\n}\n","import { renderBlocks } from \"@nexpress/blocks\";\nimport type { NpPageBlocks } from \"@nexpress/blocks\";\n\nimport type { NpTemplateRenderProps } from \"@nexpress/theme\";\n\n/**\n * Gallery template — renders the page title centered, then\n * arranges the page's blocks in a two-column responsive grid.\n *\n * Block components themselves don't know about the grid; the\n * template just wraps them so individual `image` blocks share\n * row space. For richer arrangements, themes can branch on\n * the block type — but the simplest case is \"drop blocks in,\n * grid takes care of it\".\n */\nexport function PageGalleryTemplate({ doc, blockCtx }: NpTemplateRenderProps) {\n const blocks = (doc as { blocks?: NpPageBlocks }).blocks;\n const title = (doc as { title?: string }).title;\n return (\n <section className=\"np-portfolio-gallery\">\n {title ? <h1>{title}</h1> : null}\n <div className=\"np-portfolio-gallery-grid\">\n {blocks ? renderBlocks(blocks, { ctx: blockCtx }) : null}\n </div>\n </section>\n );\n}\n","import * as React from \"react\";\nimport type { NpTemplateRenderProps } from \"@nexpress/theme\";\n\nimport {\n PortfolioProjectCard,\n type PortfolioProjectDoc,\n} from \"../components/project-card.js\";\nimport { resolvePortfolioSettings } from \"../settings-helpers.js\";\n\n/**\n * Project-index template. Big square cards in a responsive grid.\n * Title + intro centered above. The grid collapses to one column\n * below ~640px.\n *\n * Phase F.9.1-A — `settings.gridColumns` (1–6) drives the grid\n * column count via inline `gridTemplateColumns`. Default is 3.\n * `settings.galleryGutter` drives the gap between cards.\n *\n * Doc shape: `{ docs: PortfolioProjectDoc[], heading?, intro? }`.\n */\ninterface IndexDoc {\n docs?: PortfolioProjectDoc[];\n heading?: string;\n intro?: string;\n}\n\nexport async function ProjectIndexTemplate({\n doc,\n}: NpTemplateRenderProps): Promise<React.ReactElement> {\n const data = doc as IndexDoc;\n const settings = await resolvePortfolioSettings();\n const heading = data.heading ?? \"Selected work\";\n const intro = data.intro;\n const docs = data.docs ?? [];\n // Pass settings as CSS custom properties — the styles.ts\n // media queries clamp the column count at narrower viewports\n // so a `gridColumns: 6` setting doesn't crush content on\n // mobile. Inline `gridTemplateColumns` would beat the media\n // queries; vars let CSS stay in control of breakpoints.\n const gridStyle = {\n \"--np-portfolio-grid-cols\": settings.gridColumns,\n \"--np-portfolio-grid-gutter\": `${settings.galleryGutter}px`,\n } as React.CSSProperties;\n return (\n <section className=\"np-portfolio-index\">\n <header className=\"np-portfolio-index-header\">\n <h1>{heading}</h1>\n {intro ? <p>{intro}</p> : null}\n </header>\n {docs.length === 0 ? (\n <p className=\"np-portfolio-index-empty\">\n Nothing on display yet. Add projects from the admin to fill the grid.\n </p>\n ) : (\n <div className=\"np-portfolio-index-grid\" style={gridStyle}>\n {docs.map((project) => (\n <PortfolioProjectCard\n key={project.id ?? project.slug ?? project.title}\n doc={project}\n />\n ))}\n </div>\n )}\n </section>\n );\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["PortfolioMobileNav","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","Fragment","jsx","jsxs","coverUrl","coverAlt","jsx","jsx","ASPECT_VALUES","renderBlocks","jsx","jsxs","renderBlocks","jsx","jsxs","jsx","jsxs"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexpress/theme-portfolio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Portfolio theme for NexPress.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nexpress",
|
|
@@ -47,16 +47,16 @@
|
|
|
47
47
|
"react-dom": "^19.0.0"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"zod": "^4.3
|
|
51
|
-
"@nexpress/blocks": "0.1.
|
|
52
|
-
"@nexpress/
|
|
53
|
-
"@nexpress/
|
|
54
|
-
"@nexpress/
|
|
50
|
+
"zod": "^4.4.3",
|
|
51
|
+
"@nexpress/blocks": "0.1.1",
|
|
52
|
+
"@nexpress/core": "0.1.1",
|
|
53
|
+
"@nexpress/next": "0.1.1",
|
|
54
|
+
"@nexpress/theme": "0.1.1"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/react": "^19.0.0",
|
|
58
58
|
"@types/react-dom": "^19.0.0",
|
|
59
|
-
"next": "^16.2.
|
|
59
|
+
"next": "^16.2.6",
|
|
60
60
|
"react": "^19.0.0",
|
|
61
61
|
"react-dom": "^19.0.0",
|
|
62
62
|
"tsup": "^8.5.0",
|