@nexpress/theme-portfolio 0.1.0
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/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/components/error.d.ts +26 -0
- package/dist/components/error.js +122 -0
- package/dist/components/error.js.map +1 -0
- package/dist/components/members-error.d.ts +29 -0
- package/dist/components/members-error.js +121 -0
- package/dist/components/members-error.js.map +1 -0
- package/dist/components/mobile-nav.d.ts +14 -0
- package/dist/components/mobile-nav.js +57 -0
- package/dist/components/mobile-nav.js.map +1 -0
- package/dist/index.d.ts +167 -0
- package/dist/index.js +1396 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nexpress
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @nexpress/theme-portfolio
|
|
2
|
+
|
|
3
|
+
Portfolio theme for [NexPress](https://github.com/nexpress-cms/nexpress).
|
|
4
|
+
Project-grid landing page, case-study layouts, image-led detail
|
|
5
|
+
pages. Suited for design / studio / agency sites.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @nexpress/theme-portfolio
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// nexpress.config.ts
|
|
15
|
+
import portfolioTheme from "@nexpress/theme-portfolio";
|
|
16
|
+
|
|
17
|
+
export default defineConfig({
|
|
18
|
+
// ...
|
|
19
|
+
themes: [portfolioTheme],
|
|
20
|
+
defaultTheme: portfolioTheme.manifest.id,
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For authoring your own theme see
|
|
25
|
+
[`@nexpress/theme`](https://www.npmjs.com/package/@nexpress/theme).
|
|
26
|
+
|
|
27
|
+
## License
|
|
28
|
+
|
|
29
|
+
MIT
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Portfolio theme's public-site error boundary fallback.
|
|
5
|
+
*
|
|
6
|
+
* Same delegation pattern as `./members-error.tsx`: Next requires
|
|
7
|
+
* `(site)/error.tsx` to be a client component, so theme error UI
|
|
8
|
+
* ships as a separate client subpath that the host's error.tsx
|
|
9
|
+
* lazy-imports based on the active theme.
|
|
10
|
+
*
|
|
11
|
+
* Imported as `@nexpress/theme-portfolio/components/error` by
|
|
12
|
+
* `apps/web/src/app/(site)/error.tsx`'s registry.
|
|
13
|
+
*
|
|
14
|
+
* Tone matches the portfolio aesthetic — minimal, restrained.
|
|
15
|
+
* No "Back to sign in" CTA here (that's the member-side concern);
|
|
16
|
+
* just a "Try again" button + a link back to home.
|
|
17
|
+
*/
|
|
18
|
+
interface PortfolioErrorProps {
|
|
19
|
+
error: Error & {
|
|
20
|
+
digest?: string;
|
|
21
|
+
};
|
|
22
|
+
reset: () => void;
|
|
23
|
+
}
|
|
24
|
+
declare function PortfolioError({ error, reset, }: PortfolioErrorProps): react_jsx_runtime.JSX.Element;
|
|
25
|
+
|
|
26
|
+
export { PortfolioError as default };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/components/error.tsx
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
function PortfolioError({
|
|
7
|
+
error,
|
|
8
|
+
reset
|
|
9
|
+
}) {
|
|
10
|
+
return /* @__PURE__ */ jsxs(
|
|
11
|
+
"main",
|
|
12
|
+
{
|
|
13
|
+
className: "np-portfolio np-portfolio-error",
|
|
14
|
+
style: {
|
|
15
|
+
maxWidth: 560,
|
|
16
|
+
margin: "6rem auto",
|
|
17
|
+
padding: "0 1.5rem",
|
|
18
|
+
textAlign: "center"
|
|
19
|
+
},
|
|
20
|
+
children: [
|
|
21
|
+
/* @__PURE__ */ jsx(
|
|
22
|
+
"p",
|
|
23
|
+
{
|
|
24
|
+
style: {
|
|
25
|
+
margin: 0,
|
|
26
|
+
fontSize: "0.75rem",
|
|
27
|
+
textTransform: "uppercase",
|
|
28
|
+
letterSpacing: "0.18em",
|
|
29
|
+
color: "var(--np-color-muted-foreground, #94a3b8)",
|
|
30
|
+
fontFamily: "var(--np-font-body)"
|
|
31
|
+
},
|
|
32
|
+
children: "Server error"
|
|
33
|
+
}
|
|
34
|
+
),
|
|
35
|
+
/* @__PURE__ */ jsx(
|
|
36
|
+
"h1",
|
|
37
|
+
{
|
|
38
|
+
style: {
|
|
39
|
+
margin: "1rem 0 0",
|
|
40
|
+
fontSize: "clamp(1.75rem, 4vw, 2.75rem)",
|
|
41
|
+
fontFamily: "var(--np-font-heading)",
|
|
42
|
+
fontWeight: 500,
|
|
43
|
+
letterSpacing: "-0.02em",
|
|
44
|
+
lineHeight: 1.1
|
|
45
|
+
},
|
|
46
|
+
children: "Something didn\u2019t load."
|
|
47
|
+
}
|
|
48
|
+
),
|
|
49
|
+
/* @__PURE__ */ jsx(
|
|
50
|
+
"p",
|
|
51
|
+
{
|
|
52
|
+
style: {
|
|
53
|
+
margin: "1.25rem auto 0",
|
|
54
|
+
maxWidth: 460,
|
|
55
|
+
color: "var(--np-color-muted-foreground, #94a3b8)",
|
|
56
|
+
fontSize: "0.9375rem",
|
|
57
|
+
lineHeight: 1.6
|
|
58
|
+
},
|
|
59
|
+
children: process.env.NODE_ENV === "production" ? "Refreshing usually clears this. If it doesn't, try again in a moment." : error.message
|
|
60
|
+
}
|
|
61
|
+
),
|
|
62
|
+
/* @__PURE__ */ jsxs(
|
|
63
|
+
"div",
|
|
64
|
+
{
|
|
65
|
+
style: {
|
|
66
|
+
marginTop: "2rem",
|
|
67
|
+
display: "flex",
|
|
68
|
+
gap: "0.75rem",
|
|
69
|
+
justifyContent: "center",
|
|
70
|
+
flexWrap: "wrap"
|
|
71
|
+
},
|
|
72
|
+
children: [
|
|
73
|
+
/* @__PURE__ */ jsx(
|
|
74
|
+
"button",
|
|
75
|
+
{
|
|
76
|
+
type: "button",
|
|
77
|
+
onClick: reset,
|
|
78
|
+
style: {
|
|
79
|
+
padding: "0.625rem 1.5rem",
|
|
80
|
+
fontFamily: "inherit",
|
|
81
|
+
fontSize: "0.875rem",
|
|
82
|
+
fontWeight: 500,
|
|
83
|
+
background: "var(--np-color-primary, #0f172a)",
|
|
84
|
+
color: "var(--np-color-primary-foreground, #fff)",
|
|
85
|
+
border: "none",
|
|
86
|
+
borderRadius: "0.25rem",
|
|
87
|
+
cursor: "pointer"
|
|
88
|
+
},
|
|
89
|
+
children: "Try again"
|
|
90
|
+
}
|
|
91
|
+
),
|
|
92
|
+
/* @__PURE__ */ jsx(
|
|
93
|
+
"a",
|
|
94
|
+
{
|
|
95
|
+
href: "/",
|
|
96
|
+
style: {
|
|
97
|
+
padding: "0.625rem 1.5rem",
|
|
98
|
+
fontFamily: "inherit",
|
|
99
|
+
fontSize: "0.875rem",
|
|
100
|
+
fontWeight: 500,
|
|
101
|
+
background: "transparent",
|
|
102
|
+
color: "var(--np-color-foreground)",
|
|
103
|
+
border: "1px solid var(--np-color-border, #e2e8f0)",
|
|
104
|
+
borderRadius: "0.25rem",
|
|
105
|
+
textDecoration: "none",
|
|
106
|
+
display: "inline-flex",
|
|
107
|
+
alignItems: "center"
|
|
108
|
+
},
|
|
109
|
+
children: "Back home"
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
export {
|
|
120
|
+
PortfolioError as default
|
|
121
|
+
};
|
|
122
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/error.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Portfolio theme's public-site error boundary fallback.\n *\n * Same delegation pattern as `./members-error.tsx`: Next requires\n * `(site)/error.tsx` to be a client component, so theme error UI\n * ships as a separate client subpath that the host's error.tsx\n * lazy-imports based on the active theme.\n *\n * Imported as `@nexpress/theme-portfolio/components/error` by\n * `apps/web/src/app/(site)/error.tsx`'s registry.\n *\n * Tone matches the portfolio aesthetic — minimal, restrained.\n * No \"Back to sign in\" CTA here (that's the member-side concern);\n * just a \"Try again\" button + a link back to home.\n */\n\ninterface PortfolioErrorProps {\n error: Error & { digest?: string };\n reset: () => void;\n}\n\nexport default function PortfolioError({\n error,\n reset,\n}: PortfolioErrorProps) {\n // Renders `<main>` because the host's (site)/error.tsx no\n // longer relies on the layout for the `<main>` landmark (v0.2\n // shell-wrap refactor moved that into pages). Theme error\n // subpaths render *in place of* DefaultError, so we mirror\n // its `<main>` — one per page either way.\n return (\n <main\n className=\"np-portfolio np-portfolio-error\"\n style={{\n maxWidth: 560,\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, #94a3b8)\",\n fontFamily: \"var(--np-font-body)\",\n }}\n >\n Server error\n </p>\n <h1\n style={{\n margin: \"1rem 0 0\",\n fontSize: \"clamp(1.75rem, 4vw, 2.75rem)\",\n fontFamily: \"var(--np-font-heading)\",\n fontWeight: 500,\n letterSpacing: \"-0.02em\",\n lineHeight: 1.1,\n }}\n >\n Something didn’t load.\n </h1>\n <p\n style={{\n margin: \"1.25rem auto 0\",\n maxWidth: 460,\n color: \"var(--np-color-muted-foreground, #94a3b8)\",\n fontSize: \"0.9375rem\",\n lineHeight: 1.6,\n }}\n >\n {process.env.NODE_ENV === \"production\"\n ? \"Refreshing usually clears this. If it doesn't, try again in a moment.\"\n : error.message}\n </p>\n <div\n style={{\n marginTop: \"2rem\",\n display: \"flex\",\n gap: \"0.75rem\",\n justifyContent: \"center\",\n flexWrap: \"wrap\",\n }}\n >\n <button\n type=\"button\"\n onClick={reset}\n style={{\n padding: \"0.625rem 1.5rem\",\n fontFamily: \"inherit\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n background: \"var(--np-color-primary, #0f172a)\",\n color: \"var(--np-color-primary-foreground, #fff)\",\n border: \"none\",\n borderRadius: \"0.25rem\",\n cursor: \"pointer\",\n }}\n >\n Try again\n </button>\n <a\n href=\"/\"\n style={{\n padding: \"0.625rem 1.5rem\",\n fontFamily: \"inherit\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n background: \"transparent\",\n color: \"var(--np-color-foreground)\",\n border: \"1px solid var(--np-color-border, #e2e8f0)\",\n borderRadius: \"0.25rem\",\n textDecoration: \"none\",\n display: \"inline-flex\",\n alignItems: \"center\",\n }}\n >\n Back home\n </a>\n </div>\n </main>\n );\n}\n"],"mappings":";;;;AA0CM,cAqCA,YArCA;AAnBS,SAAR,eAAgC;AAAA,EACrC;AAAA,EACA;AACF,GAAwB;AAMtB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,OAAO;AAAA,QACL,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,MACb;AAAA,MAEA;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,QAAQ;AAAA,cACR,UAAU;AAAA,cACV,eAAe;AAAA,cACf,eAAe;AAAA,cACf,OAAO;AAAA,cACP,YAAY;AAAA,YACd;AAAA,YACD;AAAA;AAAA,QAED;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,QAAQ;AAAA,cACR,UAAU;AAAA,cACV,YAAY;AAAA,cACZ,YAAY;AAAA,cACZ,eAAe;AAAA,cACf,YAAY;AAAA,YACd;AAAA,YACD;AAAA;AAAA,QAED;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,QAAQ;AAAA,cACR,UAAU;AAAA,cACV,OAAO;AAAA,cACP,UAAU;AAAA,cACV,YAAY;AAAA,YACd;AAAA,YAEC,kBAAQ,IAAI,aAAa,eACtB,0EACA,MAAM;AAAA;AAAA,QACZ;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,WAAW;AAAA,cACX,SAAS;AAAA,cACT,KAAK;AAAA,cACL,gBAAgB;AAAA,cAChB,UAAU;AAAA,YACZ;AAAA,YAEA;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAS;AAAA,kBACT,OAAO;AAAA,oBACL,SAAS;AAAA,oBACT,YAAY;AAAA,oBACZ,UAAU;AAAA,oBACV,YAAY;AAAA,oBACZ,YAAY;AAAA,oBACZ,OAAO;AAAA,oBACP,QAAQ;AAAA,oBACR,cAAc;AAAA,oBACd,QAAQ;AAAA,kBACV;AAAA,kBACD;AAAA;AAAA,cAED;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,OAAO;AAAA,oBACL,SAAS;AAAA,oBACT,YAAY;AAAA,oBACZ,UAAU;AAAA,oBACV,YAAY;AAAA,oBACZ,YAAY;AAAA,oBACZ,OAAO;AAAA,oBACP,QAAQ;AAAA,oBACR,cAAc;AAAA,oBACd,gBAAgB;AAAA,oBAChB,SAAS;AAAA,oBACT,YAAY;AAAA,kBACd;AAAA,kBACD;AAAA;AAAA,cAED;AAAA;AAAA;AAAA,QACF;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Portfolio theme's member-tree error boundary fallback.
|
|
5
|
+
*
|
|
6
|
+
* Same delegation pattern as magazine's: Next requires
|
|
7
|
+
* `(member)/error.tsx` to be a client component, so theme error
|
|
8
|
+
* UI ships as a separate client subpath that the host's
|
|
9
|
+
* error.tsx lazy-imports based on the active theme. The theme's
|
|
10
|
+
* `impl.members.error` slot stays as a forward-compat type
|
|
11
|
+
* marker; the actual rendering goes through this client entry.
|
|
12
|
+
*
|
|
13
|
+
* Imported as `@nexpress/theme-portfolio/components/members-error`
|
|
14
|
+
* by `apps/web/src/app/(member)/error.tsx`.
|
|
15
|
+
*
|
|
16
|
+
* Tone matches the portfolio aesthetic — minimal, restrained,
|
|
17
|
+
* with the same "Back to sign in" CTA as other member error
|
|
18
|
+
* surfaces (the common cause of an error inside `/members/*` is
|
|
19
|
+
* stale session state).
|
|
20
|
+
*/
|
|
21
|
+
interface PortfolioMembersErrorProps {
|
|
22
|
+
error: Error & {
|
|
23
|
+
digest?: string;
|
|
24
|
+
};
|
|
25
|
+
reset: () => void;
|
|
26
|
+
}
|
|
27
|
+
declare function PortfolioMembersError({ error, reset, }: PortfolioMembersErrorProps): react_jsx_runtime.JSX.Element;
|
|
28
|
+
|
|
29
|
+
export { PortfolioMembersError as default };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/components/members-error.tsx
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
function PortfolioMembersError({
|
|
7
|
+
error,
|
|
8
|
+
reset
|
|
9
|
+
}) {
|
|
10
|
+
return /* @__PURE__ */ jsxs(
|
|
11
|
+
"main",
|
|
12
|
+
{
|
|
13
|
+
className: "np-portfolio np-portfolio-members-error",
|
|
14
|
+
style: {
|
|
15
|
+
maxWidth: 480,
|
|
16
|
+
margin: "6rem auto",
|
|
17
|
+
padding: "0 1.5rem",
|
|
18
|
+
textAlign: "center"
|
|
19
|
+
},
|
|
20
|
+
children: [
|
|
21
|
+
/* @__PURE__ */ jsx(
|
|
22
|
+
"p",
|
|
23
|
+
{
|
|
24
|
+
style: {
|
|
25
|
+
margin: 0,
|
|
26
|
+
fontSize: "0.75rem",
|
|
27
|
+
textTransform: "uppercase",
|
|
28
|
+
letterSpacing: "0.18em",
|
|
29
|
+
color: "var(--np-color-muted-foreground, #94a3b8)",
|
|
30
|
+
fontFamily: "var(--np-font-body)"
|
|
31
|
+
},
|
|
32
|
+
children: "Account"
|
|
33
|
+
}
|
|
34
|
+
),
|
|
35
|
+
/* @__PURE__ */ jsx(
|
|
36
|
+
"h1",
|
|
37
|
+
{
|
|
38
|
+
style: {
|
|
39
|
+
margin: "1rem 0 0",
|
|
40
|
+
fontSize: "clamp(1.75rem, 4vw, 2.5rem)",
|
|
41
|
+
fontFamily: "var(--np-font-heading)",
|
|
42
|
+
fontWeight: 500,
|
|
43
|
+
letterSpacing: "-0.02em",
|
|
44
|
+
lineHeight: 1.1
|
|
45
|
+
},
|
|
46
|
+
children: "Something interrupted your session."
|
|
47
|
+
}
|
|
48
|
+
),
|
|
49
|
+
/* @__PURE__ */ jsx(
|
|
50
|
+
"p",
|
|
51
|
+
{
|
|
52
|
+
style: {
|
|
53
|
+
margin: "1.25rem 0 0",
|
|
54
|
+
color: "var(--np-color-muted-foreground, #94a3b8)",
|
|
55
|
+
fontSize: "0.9375rem",
|
|
56
|
+
lineHeight: 1.6
|
|
57
|
+
},
|
|
58
|
+
children: process.env.NODE_ENV === "production" ? "Try again, or sign back in to start fresh." : error.message
|
|
59
|
+
}
|
|
60
|
+
),
|
|
61
|
+
/* @__PURE__ */ jsxs(
|
|
62
|
+
"div",
|
|
63
|
+
{
|
|
64
|
+
style: {
|
|
65
|
+
marginTop: "2rem",
|
|
66
|
+
display: "flex",
|
|
67
|
+
gap: "0.75rem",
|
|
68
|
+
justifyContent: "center",
|
|
69
|
+
flexWrap: "wrap"
|
|
70
|
+
},
|
|
71
|
+
children: [
|
|
72
|
+
/* @__PURE__ */ jsx(
|
|
73
|
+
"button",
|
|
74
|
+
{
|
|
75
|
+
type: "button",
|
|
76
|
+
onClick: reset,
|
|
77
|
+
style: {
|
|
78
|
+
padding: "0.625rem 1.5rem",
|
|
79
|
+
fontFamily: "inherit",
|
|
80
|
+
fontSize: "0.875rem",
|
|
81
|
+
fontWeight: 500,
|
|
82
|
+
background: "var(--np-color-primary, #0f172a)",
|
|
83
|
+
color: "var(--np-color-primary-foreground, #fff)",
|
|
84
|
+
border: "none",
|
|
85
|
+
borderRadius: "0.25rem",
|
|
86
|
+
cursor: "pointer"
|
|
87
|
+
},
|
|
88
|
+
children: "Try again"
|
|
89
|
+
}
|
|
90
|
+
),
|
|
91
|
+
/* @__PURE__ */ jsx(
|
|
92
|
+
"a",
|
|
93
|
+
{
|
|
94
|
+
href: "/members/login",
|
|
95
|
+
style: {
|
|
96
|
+
padding: "0.625rem 1.5rem",
|
|
97
|
+
fontFamily: "inherit",
|
|
98
|
+
fontSize: "0.875rem",
|
|
99
|
+
fontWeight: 500,
|
|
100
|
+
background: "transparent",
|
|
101
|
+
color: "var(--np-color-foreground)",
|
|
102
|
+
border: "1px solid var(--np-color-border, #e2e8f0)",
|
|
103
|
+
borderRadius: "0.25rem",
|
|
104
|
+
textDecoration: "none",
|
|
105
|
+
display: "inline-flex",
|
|
106
|
+
alignItems: "center"
|
|
107
|
+
},
|
|
108
|
+
children: "Back to sign in"
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
export {
|
|
119
|
+
PortfolioMembersError as default
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=members-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/members-error.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Portfolio theme's member-tree error boundary fallback.\n *\n * Same delegation pattern as magazine's: Next requires\n * `(member)/error.tsx` to be a client component, so theme error\n * UI ships as a separate client subpath that the host's\n * error.tsx lazy-imports based on the active theme. The theme's\n * `impl.members.error` slot stays as a forward-compat type\n * marker; the actual rendering goes through this client entry.\n *\n * Imported as `@nexpress/theme-portfolio/components/members-error`\n * by `apps/web/src/app/(member)/error.tsx`.\n *\n * Tone matches the portfolio aesthetic — minimal, restrained,\n * with the same \"Back to sign in\" CTA as other member error\n * surfaces (the common cause of an error inside `/members/*` is\n * stale session state).\n */\n\ninterface PortfolioMembersErrorProps {\n error: Error & { digest?: string };\n reset: () => void;\n}\n\nexport default function PortfolioMembersError({\n error,\n reset,\n}: PortfolioMembersErrorProps) {\n // Renders `<main>` because the host's (member)/error.tsx no\n // longer relies on the layout for the `<main>` landmark (v0.2\n // shell-wrap refactor moved that into pages). Theme\n // members-error subpaths render in place of DefaultMemberError,\n // so we mirror its `<main>` — one per page either way.\n return (\n <main\n className=\"np-portfolio np-portfolio-members-error\"\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, #94a3b8)\",\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 lineHeight: 1.1,\n }}\n >\n Something interrupted your session.\n </h1>\n <p\n style={{\n margin: \"1.25rem 0 0\",\n color: \"var(--np-color-muted-foreground, #94a3b8)\",\n fontSize: \"0.9375rem\",\n lineHeight: 1.6,\n }}\n >\n {process.env.NODE_ENV === \"production\"\n ? \"Try again, or sign back in to start fresh.\"\n : error.message}\n </p>\n <div\n style={{\n marginTop: \"2rem\",\n display: \"flex\",\n gap: \"0.75rem\",\n justifyContent: \"center\",\n flexWrap: \"wrap\",\n }}\n >\n <button\n type=\"button\"\n onClick={reset}\n style={{\n padding: \"0.625rem 1.5rem\",\n fontFamily: \"inherit\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n background: \"var(--np-color-primary, #0f172a)\",\n color: \"var(--np-color-primary-foreground, #fff)\",\n border: \"none\",\n borderRadius: \"0.25rem\",\n cursor: \"pointer\",\n }}\n >\n Try again\n </button>\n <a\n href=\"/members/login\"\n style={{\n padding: \"0.625rem 1.5rem\",\n fontFamily: \"inherit\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n background: \"transparent\",\n color: \"var(--np-color-foreground)\",\n border: \"1px solid var(--np-color-border, #e2e8f0)\",\n borderRadius: \"0.25rem\",\n textDecoration: \"none\",\n display: \"inline-flex\",\n alignItems: \"center\",\n }}\n >\n Back to sign in\n </a>\n </div>\n </main>\n );\n}\n"],"mappings":";;;;AA6CM,cAoCA,YApCA;AAnBS,SAAR,sBAAuC;AAAA,EAC5C;AAAA,EACA;AACF,GAA+B;AAM7B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,OAAO;AAAA,QACL,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,MACb;AAAA,MAEA;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,QAAQ;AAAA,cACR,UAAU;AAAA,cACV,eAAe;AAAA,cACf,eAAe;AAAA,cACf,OAAO;AAAA,cACP,YAAY;AAAA,YACd;AAAA,YACD;AAAA;AAAA,QAED;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,QAAQ;AAAA,cACR,UAAU;AAAA,cACV,YAAY;AAAA,cACZ,YAAY;AAAA,cACZ,eAAe;AAAA,cACf,YAAY;AAAA,YACd;AAAA,YACD;AAAA;AAAA,QAED;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,QAAQ;AAAA,cACR,OAAO;AAAA,cACP,UAAU;AAAA,cACV,YAAY;AAAA,YACd;AAAA,YAEC,kBAAQ,IAAI,aAAa,eACtB,+CACA,MAAM;AAAA;AAAA,QACZ;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,WAAW;AAAA,cACX,SAAS;AAAA,cACT,KAAK;AAAA,cACL,gBAAgB;AAAA,cAChB,UAAU;AAAA,YACZ;AAAA,YAEA;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAS;AAAA,kBACT,OAAO;AAAA,oBACL,SAAS;AAAA,oBACT,YAAY;AAAA,oBACZ,UAAU;AAAA,oBACV,YAAY;AAAA,oBACZ,YAAY;AAAA,oBACZ,OAAO;AAAA,oBACP,QAAQ;AAAA,oBACR,cAAc;AAAA,oBACd,QAAQ;AAAA,kBACV;AAAA,kBACD;AAAA;AAAA,cAED;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,OAAO;AAAA,oBACL,SAAS;AAAA,oBACT,YAAY;AAAA,oBACZ,UAAU;AAAA,oBACV,YAAY;AAAA,oBACZ,YAAY;AAAA,oBACZ,OAAO;AAAA,oBACP,QAAQ;AAAA,oBACR,cAAc;AAAA,oBACd,gBAAgB;AAAA,oBAChB,SAAS;AAAA,oBACT,YAAY;AAAA,kBACd;AAAA,kBACD;AAAA;AAAA,cAED;AAAA;AAAA;AAAA,QACF;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { NpNavItem } from '@nexpress/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Portfolio theme mobile drawer. Inherits the dark surface; the
|
|
6
|
+
* inline nav hides at <720px (CSS) and a "Menu" button opens
|
|
7
|
+
* a full-screen panel with large links centered.
|
|
8
|
+
*/
|
|
9
|
+
interface PortfolioMobileNavProps {
|
|
10
|
+
items: NpNavItem[];
|
|
11
|
+
}
|
|
12
|
+
declare function PortfolioMobileNav({ items }: PortfolioMobileNavProps): react_jsx_runtime.JSX.Element | null;
|
|
13
|
+
|
|
14
|
+
export { PortfolioMobileNav, type PortfolioMobileNavProps };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/components/mobile-nav.tsx
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
function PortfolioMobileNav({ items }) {
|
|
8
|
+
const [open, setOpen] = useState(false);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!open) return;
|
|
11
|
+
const onKey = (e) => {
|
|
12
|
+
if (e.key === "Escape") setOpen(false);
|
|
13
|
+
};
|
|
14
|
+
document.addEventListener("keydown", onKey);
|
|
15
|
+
const previousOverflow = document.body.style.overflow;
|
|
16
|
+
document.body.style.overflow = "hidden";
|
|
17
|
+
return () => {
|
|
18
|
+
document.removeEventListener("keydown", onKey);
|
|
19
|
+
document.body.style.overflow = previousOverflow;
|
|
20
|
+
};
|
|
21
|
+
}, [open]);
|
|
22
|
+
if (items.length === 0) return null;
|
|
23
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24
|
+
/* @__PURE__ */ jsx(
|
|
25
|
+
"button",
|
|
26
|
+
{
|
|
27
|
+
type: "button",
|
|
28
|
+
className: "np-portfolio-nav-toggle",
|
|
29
|
+
"aria-label": open ? "Close menu" : "Open menu",
|
|
30
|
+
"aria-expanded": open,
|
|
31
|
+
"aria-controls": "np-portfolio-nav-drawer",
|
|
32
|
+
onClick: () => setOpen((prev) => !prev),
|
|
33
|
+
children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: open ? "Close" : "Menu" })
|
|
34
|
+
}
|
|
35
|
+
),
|
|
36
|
+
/* @__PURE__ */ jsx(
|
|
37
|
+
"aside",
|
|
38
|
+
{
|
|
39
|
+
id: "np-portfolio-nav-drawer",
|
|
40
|
+
className: "np-portfolio-nav-drawer",
|
|
41
|
+
"data-open": open ? "true" : "false",
|
|
42
|
+
"aria-hidden": open ? "false" : "true",
|
|
43
|
+
onClick: (e) => {
|
|
44
|
+
if (e.target === e.currentTarget) setOpen(false);
|
|
45
|
+
},
|
|
46
|
+
children: /* @__PURE__ */ jsx("ul", { className: "np-portfolio-nav-drawer-list", children: items.map((item, index) => /* @__PURE__ */ jsxs("li", { children: [
|
|
47
|
+
/* @__PURE__ */ jsx("a", { href: item.url, onClick: () => setOpen(false), children: item.label }),
|
|
48
|
+
item.children && item.children.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "np-portfolio-mobile-subnav", children: item.children.map((child, childIndex) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", { href: child.url, onClick: () => setOpen(false), children: child.label }) }, `portfolio-mobile-${index.toString()}-${childIndex.toString()}`)) }) : null
|
|
49
|
+
] }, `portfolio-mobile-${index.toString()}`)) })
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
] });
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
PortfolioMobileNav
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=mobile-nav.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/mobile-nav.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { NpNavItem } from \"@nexpress/core\";\n\n/**\n * Portfolio theme mobile drawer. Inherits the dark surface; the\n * inline nav hides at <720px (CSS) and a \"Menu\" button opens\n * a full-screen panel with large links centered.\n */\nexport interface PortfolioMobileNavProps {\n items: NpNavItem[];\n}\n\nexport function PortfolioMobileNav({ items }: PortfolioMobileNavProps) {\n const [open, setOpen] = useState(false);\n\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(\"keydown\", onKey);\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.removeEventListener(\"keydown\", onKey);\n document.body.style.overflow = previousOverflow;\n };\n }, [open]);\n\n if (items.length === 0) return null;\n\n return (\n <>\n <button\n type=\"button\"\n className=\"np-portfolio-nav-toggle\"\n aria-label={open ? \"Close menu\" : \"Open menu\"}\n aria-expanded={open}\n aria-controls=\"np-portfolio-nav-drawer\"\n onClick={() => setOpen((prev) => !prev)}\n >\n <span aria-hidden=\"true\">{open ? \"Close\" : \"Menu\"}</span>\n </button>\n <aside\n id=\"np-portfolio-nav-drawer\"\n className=\"np-portfolio-nav-drawer\"\n data-open={open ? \"true\" : \"false\"}\n aria-hidden={open ? \"false\" : \"true\"}\n onClick={(e) => {\n // Click on the backdrop closes; clicks on inner content stop here.\n if (e.target === e.currentTarget) setOpen(false);\n }}\n >\n <ul className=\"np-portfolio-nav-drawer-list\">\n {items.map((item, index) => (\n <li key={`portfolio-mobile-${index.toString()}`}>\n <a href={item.url} onClick={() => setOpen(false)}>\n {item.label}\n </a>\n {item.children && item.children.length > 0 ? (\n <ul className=\"np-portfolio-mobile-subnav\">\n {item.children.map((child, childIndex) => (\n <li key={`portfolio-mobile-${index.toString()}-${childIndex.toString()}`}>\n <a href={child.url} onClick={() => setOpen(false)}>\n {child.label}\n </a>\n </li>\n ))}\n </ul>\n ) : null}\n </li>\n ))}\n </ul>\n </aside>\n </>\n );\n}\n"],"mappings":";;;;AAEA,SAAS,WAAW,gBAAgB;AAgChC,mBASI,KAcI,YAvBR;AApBG,SAAS,mBAAmB,EAAE,MAAM,GAA4B;AACrE,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AAEtC,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,CAAC,MAAqB;AAClC,UAAI,EAAE,QAAQ,SAAU,SAAQ,KAAK;AAAA,IACvC;AACA,aAAS,iBAAiB,WAAW,KAAK;AAC1C,UAAM,mBAAmB,SAAS,KAAK,MAAM;AAC7C,aAAS,KAAK,MAAM,WAAW;AAC/B,WAAO,MAAM;AACX,eAAS,oBAAoB,WAAW,KAAK;AAC7C,eAAS,KAAK,MAAM,WAAW;AAAA,IACjC;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,QACV,cAAY,OAAO,eAAe;AAAA,QAClC,iBAAe;AAAA,QACf,iBAAc;AAAA,QACd,SAAS,MAAM,QAAQ,CAAC,SAAS,CAAC,IAAI;AAAA,QAEtC,8BAAC,UAAK,eAAY,QAAQ,iBAAO,UAAU,QAAO;AAAA;AAAA,IACpD;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,IAAG;AAAA,QACH,WAAU;AAAA,QACV,aAAW,OAAO,SAAS;AAAA,QAC3B,eAAa,OAAO,UAAU;AAAA,QAC9B,SAAS,CAAC,MAAM;AAEd,cAAI,EAAE,WAAW,EAAE,cAAe,SAAQ,KAAK;AAAA,QACjD;AAAA,QAEA,8BAAC,QAAG,WAAU,gCACX,gBAAM,IAAI,CAAC,MAAM,UAChB,qBAAC,QACC;AAAA,8BAAC,OAAE,MAAM,KAAK,KAAK,SAAS,MAAM,QAAQ,KAAK,GAC5C,eAAK,OACR;AAAA,UACC,KAAK,YAAY,KAAK,SAAS,SAAS,IACvC,oBAAC,QAAG,WAAU,8BACX,eAAK,SAAS,IAAI,CAAC,OAAO,eACzB,oBAAC,QACC,8BAAC,OAAE,MAAM,MAAM,KAAK,SAAS,MAAM,QAAQ,KAAK,GAC7C,gBAAM,OACT,KAHO,oBAAoB,MAAM,SAAS,CAAC,IAAI,WAAW,SAAS,CAAC,EAItE,CACD,GACH,IACE;AAAA,aAdG,oBAAoB,MAAM,SAAS,CAAC,EAe7C,CACD,GACH;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import * as _nexpress_theme from '@nexpress/theme';
|
|
2
|
+
export { PortfolioMobileNav } from './components/mobile-nav.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
5
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Portfolio "project" card. Visual-first: a large image (1:1
|
|
10
|
+
* by default) with an overlaid title that fades in on hover.
|
|
11
|
+
* Defensive on the doc shape so collections of any kind can
|
|
12
|
+
* be routed through this card.
|
|
13
|
+
*
|
|
14
|
+
* Phase F.9.1-B — `settings.showProjectTags` toggles the
|
|
15
|
+
* category/tag chip below the title. Operators who want a
|
|
16
|
+
* cleaner card grid flip it off.
|
|
17
|
+
*/
|
|
18
|
+
interface PortfolioProjectDoc {
|
|
19
|
+
id?: string;
|
|
20
|
+
slug?: string;
|
|
21
|
+
title?: string;
|
|
22
|
+
category?: string;
|
|
23
|
+
cover?: {
|
|
24
|
+
url?: string;
|
|
25
|
+
alt?: string;
|
|
26
|
+
} | string | null;
|
|
27
|
+
}
|
|
28
|
+
interface PortfolioProjectCardProps {
|
|
29
|
+
doc: PortfolioProjectDoc;
|
|
30
|
+
}
|
|
31
|
+
declare function PortfolioProjectCard({ doc, }: PortfolioProjectCardProps): Promise<React.ReactElement>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Studio footer — rows on a thin top rule:
|
|
35
|
+
* 1. Optional bio (`settings.aboutCopy`) — short studio
|
|
36
|
+
* description rendered above the contact line. Operators
|
|
37
|
+
* who want a fuller about page do that through the page
|
|
38
|
+
* builder; this is the ambient bio surfaced on every page.
|
|
39
|
+
* 2. Contact line (NP_SOCIAL_EMAIL → mailto, or generic blurb)
|
|
40
|
+
* 3. Social mini-strip (NP_SOCIAL_GITHUB / TWITTER / LINKEDIN /
|
|
41
|
+
* MASTODON, all optional, hidden when none are configured)
|
|
42
|
+
* 4. Colophon — year + framework credit, toggled by
|
|
43
|
+
* `settings.showFooterCredit`. `settings.copyrightYear`
|
|
44
|
+
* overrides the auto-detected year (some studios pin to
|
|
45
|
+
* "2024" for an "established" feel).
|
|
46
|
+
*
|
|
47
|
+
* Stays minimal so the visual focus stays on the work above.
|
|
48
|
+
*/
|
|
49
|
+
declare function PortfolioFooter(): Promise<react_jsx_runtime.JSX.Element>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Slim sticky top bar. Studio name on the inline-start, primary
|
|
53
|
+
* nav on the inline-end. The inline list hides below ~720px and
|
|
54
|
+
* the mobile drawer takes over.
|
|
55
|
+
*
|
|
56
|
+
* Phase F.9.1-A — `settings.studioName` controls the brand
|
|
57
|
+
* label. Default ("Studio") falls through if operator hasn't
|
|
58
|
+
* customized.
|
|
59
|
+
*/
|
|
60
|
+
declare function PortfolioHeader(): Promise<react_jsx_runtime.JSX.Element>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Portfolio member-tree 404.
|
|
64
|
+
*
|
|
65
|
+
* Mirrors `PortfolioNotFound`'s minimal voice but tuned for the
|
|
66
|
+
* member context — CTA points at `/members/login` rather than
|
|
67
|
+
* the homepage, and the copy acknowledges stale auth links (the
|
|
68
|
+
* dominant cause of 404s inside `/members/*`).
|
|
69
|
+
*
|
|
70
|
+
* Server component; rendered by `(member)/not-found.tsx` when
|
|
71
|
+
* the active theme is portfolio and `impl.members.notFound` is
|
|
72
|
+
* declared.
|
|
73
|
+
*
|
|
74
|
+
* Renders a `<div>`, not `<main>`, because the framework's
|
|
75
|
+
* `<ShellWrap surface="member">` already emits the page's
|
|
76
|
+
* `<main className="np-member-main">` landmark.
|
|
77
|
+
*/
|
|
78
|
+
declare function PortfolioMembersNotFound(): React.ReactElement;
|
|
79
|
+
|
|
80
|
+
declare function PortfolioMembersShell({ children, }: {
|
|
81
|
+
children: ReactNode;
|
|
82
|
+
}): Promise<react_jsx_runtime.JSX.Element>;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Phase F.9-C — portfolio 404.
|
|
86
|
+
*
|
|
87
|
+
* Dark, sparse — matches the theme's surface palette. A single
|
|
88
|
+
* line of copy + return-home link, centered.
|
|
89
|
+
*/
|
|
90
|
+
declare function PortfolioNotFound(): React.ReactElement;
|
|
91
|
+
|
|
92
|
+
declare function PortfolioShell({ children }: {
|
|
93
|
+
children: ReactNode;
|
|
94
|
+
}): Promise<react_jsx_runtime.JSX.Element>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Theme-owned CSS for `@nexpress/theme-portfolio`. Reads from the
|
|
98
|
+
* theme token system (background / foreground / primary / card /
|
|
99
|
+
* muted) so admin token overrides reflow the whole shell. The
|
|
100
|
+
* dark surface ships via `impl.tokens` in `index.ts`; that's the
|
|
101
|
+
* single point of truth, this CSS just consumes it. Scoped under
|
|
102
|
+
* `.np-portfolio-*` so swapping themes never leaves residue.
|
|
103
|
+
*
|
|
104
|
+
* Decorative dividers stay as `rgba(255, 255, 255, …)` since they're
|
|
105
|
+
* tied to the dark assumption — flipping to a light palette is an
|
|
106
|
+
* intentional fork that needs a fresh divider color anyway.
|
|
107
|
+
*/
|
|
108
|
+
declare const portfolioCss: string;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Phase F.9-C — operator-tunable portfolio settings.
|
|
112
|
+
*
|
|
113
|
+
* Stresses F.3's auto-form on **deep settings**: many fields,
|
|
114
|
+
* mixed types, range-constrained numbers, color picker (hex
|
|
115
|
+
* regex heuristic), nested array of objects with required
|
|
116
|
+
* sub-fields. Combined with magazine's enum/array shape and
|
|
117
|
+
* docs' text-heavy shape, this round-trips F.3's full
|
|
118
|
+
* widget surface.
|
|
119
|
+
*/
|
|
120
|
+
declare const portfolioSettingsSchema: z.ZodObject<{
|
|
121
|
+
gridColumns: z.ZodDefault<z.ZodNumber>;
|
|
122
|
+
cardAspect: z.ZodDefault<z.ZodEnum<{
|
|
123
|
+
square: "square";
|
|
124
|
+
portrait: "portrait";
|
|
125
|
+
landscape: "landscape";
|
|
126
|
+
golden: "golden";
|
|
127
|
+
}>>;
|
|
128
|
+
hoverStyle: z.ZodDefault<z.ZodEnum<{
|
|
129
|
+
fade: "fade";
|
|
130
|
+
scale: "scale";
|
|
131
|
+
slide: "slide";
|
|
132
|
+
lift: "lift";
|
|
133
|
+
}>>;
|
|
134
|
+
galleryGutter: z.ZodDefault<z.ZodNumber>;
|
|
135
|
+
showProjectMeta: z.ZodDefault<z.ZodBoolean>;
|
|
136
|
+
showProjectTags: z.ZodDefault<z.ZodBoolean>;
|
|
137
|
+
accentColor: z.ZodOptional<z.ZodString>;
|
|
138
|
+
studioName: z.ZodDefault<z.ZodString>;
|
|
139
|
+
aboutCopy: z.ZodDefault<z.ZodString>;
|
|
140
|
+
showFooterCredit: z.ZodDefault<z.ZodBoolean>;
|
|
141
|
+
copyrightYear: z.ZodOptional<z.ZodNumber>;
|
|
142
|
+
clientLogos: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
143
|
+
name: z.ZodString;
|
|
144
|
+
logoUrl: z.ZodString;
|
|
145
|
+
link: z.ZodOptional<z.ZodString>;
|
|
146
|
+
}, z.core.$strip>>>;
|
|
147
|
+
}, z.core.$strip>;
|
|
148
|
+
type PortfolioSettings = z.infer<typeof portfolioSettingsSchema>;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* `@nexpress/theme-portfolio` — image-led dark theme.
|
|
152
|
+
*
|
|
153
|
+
* Designed for designers / photographers / studios. Pages get a
|
|
154
|
+
* centered text column or a gallery grid; "posts" are treated as
|
|
155
|
+
* project case studies with a hero image, role / year / client
|
|
156
|
+
* meta strip, and the standard block body underneath. The index
|
|
157
|
+
* template renders the project archive as a 2- / 3-column grid
|
|
158
|
+
* of square cards with hover-fade captions.
|
|
159
|
+
*
|
|
160
|
+
* Flips the surface palette: dark `--np-color-background` is
|
|
161
|
+
* driven entirely from the theme's CSS (no admin override
|
|
162
|
+
* required). Sites that want a light variant fork or override
|
|
163
|
+
* tokens via the admin.
|
|
164
|
+
*/
|
|
165
|
+
declare const portfolioTheme: _nexpress_theme.NpTheme;
|
|
166
|
+
|
|
167
|
+
export { PortfolioFooter, PortfolioHeader, PortfolioMembersNotFound, PortfolioMembersShell, PortfolioNotFound, PortfolioProjectCard, type PortfolioProjectDoc, type PortfolioSettings, PortfolioShell, portfolioCss, portfolioSettingsSchema, portfolioTheme };
|