@softwareinfocus/next-og-image 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 +96 -0
- package/dist/chunk-ZZJXXDAB.js +12 -0
- package/dist/chunk-ZZJXXDAB.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/presets/sif/index.d.ts +31 -0
- package/dist/presets/sif/index.js +488 -0
- package/dist/presets/sif/index.js.map +1 -0
- package/dist/types-CaggCk6z.d.ts +18 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Software InFocus
|
|
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,96 @@
|
|
|
1
|
+
# @softwareinfocus/next-og-image
|
|
2
|
+
|
|
3
|
+
Reusable Open Graph image helpers for Next.js App Router metadata image routes, with an optional Software InFocus preset template.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- `next/og` image response wrapper with standard `1200x630` PNG defaults
|
|
8
|
+
- Helpers for static and dynamic `opengraph-image.tsx` routes
|
|
9
|
+
- Typed OG payload contract (`OgImageData`)
|
|
10
|
+
- SIF preset renderer + fallback data builders
|
|
11
|
+
- Works with Next.js 15+ App Router route metadata image files
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @softwareinfocus/next-og-image
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Peer dependencies:
|
|
20
|
+
|
|
21
|
+
- `next@15`
|
|
22
|
+
- `react@19`
|
|
23
|
+
|
|
24
|
+
## Basic usage (static route)
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import {
|
|
28
|
+
OG_IMAGE_CONTENT_TYPE_PNG,
|
|
29
|
+
OG_IMAGE_SIZE_1200x630,
|
|
30
|
+
createStaticOgImageHandler,
|
|
31
|
+
} from '@softwareinfocus/next-og-image'
|
|
32
|
+
import { createSifFallbacks, createSifOgRenderer } from '@softwareinfocus/next-og-image/presets/sif'
|
|
33
|
+
|
|
34
|
+
export const alt = 'Site social preview image'
|
|
35
|
+
export const size = OG_IMAGE_SIZE_1200x630
|
|
36
|
+
export const contentType = OG_IMAGE_CONTENT_TYPE_PNG
|
|
37
|
+
export const runtime = 'edge'
|
|
38
|
+
export const revalidate = 3600
|
|
39
|
+
|
|
40
|
+
const render = createSifOgRenderer({
|
|
41
|
+
brandName: 'Software InFocus',
|
|
42
|
+
domainLabel: 'softwareinfocus.com',
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const fallbacks = createSifFallbacks({ brandName: 'Software InFocus' })
|
|
46
|
+
|
|
47
|
+
export default createStaticOgImageHandler({
|
|
48
|
+
fallbackData: fallbacks.global,
|
|
49
|
+
render,
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Basic usage (dynamic route)
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { createDynamicOgImageHandler } from '@softwareinfocus/next-og-image'
|
|
57
|
+
import { createSifFallbacks, createSifOgRenderer } from '@softwareinfocus/next-og-image/presets/sif'
|
|
58
|
+
|
|
59
|
+
const render = createSifOgRenderer()
|
|
60
|
+
const fallbacks = createSifFallbacks()
|
|
61
|
+
|
|
62
|
+
export default createDynamicOgImageHandler<{ slug: string }, 'blog'>({
|
|
63
|
+
resolveData: async ({ slug }) => {
|
|
64
|
+
const post = await getPostBySlug(slug)
|
|
65
|
+
if (!post) return null
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
type: 'blog',
|
|
69
|
+
title: post.title,
|
|
70
|
+
subtitle: post.description,
|
|
71
|
+
chips: post.tags,
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
fallbackData: fallbacks.section.blog,
|
|
75
|
+
render,
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Notes
|
|
80
|
+
|
|
81
|
+
- Keep `export const runtime = 'edge'` in your route file for predictable `next/og` behavior.
|
|
82
|
+
- Keep `export const revalidate = 3600` (or your preferred cache interval) in each route.
|
|
83
|
+
- `twitter-image.tsx` can re-export from `./opengraph-image` in most projects.
|
|
84
|
+
- Fonts are optional. Provide `loadFonts` in `createSifOgRenderer()` if you want custom typography.
|
|
85
|
+
|
|
86
|
+
## Development
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pnpm install
|
|
90
|
+
pnpm build
|
|
91
|
+
pnpm test
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// src/core/constants.ts
|
|
2
|
+
var OG_IMAGE_SIZE_1200x630 = {
|
|
3
|
+
width: 1200,
|
|
4
|
+
height: 630
|
|
5
|
+
};
|
|
6
|
+
var OG_IMAGE_CONTENT_TYPE_PNG = "image/png";
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
OG_IMAGE_SIZE_1200x630,
|
|
10
|
+
OG_IMAGE_CONTENT_TYPE_PNG
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=chunk-ZZJXXDAB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/constants.ts"],"sourcesContent":["export const OG_IMAGE_SIZE_1200x630 = {\n width: 1200,\n height: 630,\n} as const\n\nexport const OG_IMAGE_CONTENT_TYPE_PNG = 'image/png' as const\n"],"mappings":";AAAO,IAAM,yBAAyB;AAAA,EACpC,OAAO;AAAA,EACP,QAAQ;AACV;AAEO,IAAM,4BAA4B;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ImageResponse } from 'next/og';
|
|
2
|
+
import { O as OgImageData, M as MetadataImageRouteProps } from './types-CaggCk6z.js';
|
|
3
|
+
export { a as OgFontDefinition } from './types-CaggCk6z.js';
|
|
4
|
+
|
|
5
|
+
declare const OG_IMAGE_SIZE_1200x630: {
|
|
6
|
+
readonly width: 1200;
|
|
7
|
+
readonly height: 630;
|
|
8
|
+
};
|
|
9
|
+
declare const OG_IMAGE_CONTENT_TYPE_PNG: "image/png";
|
|
10
|
+
|
|
11
|
+
declare function createStaticOgImageHandler<TType extends string>(options: {
|
|
12
|
+
resolveData?: () => Promise<OgImageData<TType> | null> | OgImageData<TType> | null;
|
|
13
|
+
fallbackData: OgImageData<TType> | (() => OgImageData<TType>);
|
|
14
|
+
render: (data: OgImageData<TType>) => Promise<ImageResponse> | ImageResponse;
|
|
15
|
+
}): () => Promise<ImageResponse>;
|
|
16
|
+
declare function createDynamicOgImageHandler<TParams extends Record<string, string>, TType extends string>(options: {
|
|
17
|
+
resolveData: (params: TParams) => Promise<OgImageData<TType> | null> | OgImageData<TType> | null;
|
|
18
|
+
fallbackData: OgImageData<TType> | ((params: TParams) => OgImageData<TType>);
|
|
19
|
+
render: (data: OgImageData<TType>) => Promise<ImageResponse> | ImageResponse;
|
|
20
|
+
}): (props: MetadataImageRouteProps<TParams>) => Promise<ImageResponse>;
|
|
21
|
+
|
|
22
|
+
export { MetadataImageRouteProps, OG_IMAGE_CONTENT_TYPE_PNG, OG_IMAGE_SIZE_1200x630, OgImageData, createDynamicOgImageHandler, createStaticOgImageHandler };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OG_IMAGE_CONTENT_TYPE_PNG,
|
|
3
|
+
OG_IMAGE_SIZE_1200x630
|
|
4
|
+
} from "./chunk-ZZJXXDAB.js";
|
|
5
|
+
|
|
6
|
+
// src/core/route-handlers.ts
|
|
7
|
+
function resolveStaticFallback(fallbackData) {
|
|
8
|
+
return typeof fallbackData === "function" ? fallbackData() : fallbackData;
|
|
9
|
+
}
|
|
10
|
+
function resolveDynamicFallback(fallbackData, params) {
|
|
11
|
+
return typeof fallbackData === "function" ? fallbackData(params) : fallbackData;
|
|
12
|
+
}
|
|
13
|
+
function createStaticOgImageHandler(options) {
|
|
14
|
+
return async function StaticOgImageHandler() {
|
|
15
|
+
const resolved = options.resolveData ? await options.resolveData() : null;
|
|
16
|
+
return options.render(resolved ?? resolveStaticFallback(options.fallbackData));
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function createDynamicOgImageHandler(options) {
|
|
20
|
+
return async function DynamicOgImageHandler(props) {
|
|
21
|
+
const params = await props.params;
|
|
22
|
+
const resolved = await options.resolveData(params);
|
|
23
|
+
return options.render(resolved ?? resolveDynamicFallback(options.fallbackData, params));
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
OG_IMAGE_CONTENT_TYPE_PNG,
|
|
28
|
+
OG_IMAGE_SIZE_1200x630,
|
|
29
|
+
createDynamicOgImageHandler,
|
|
30
|
+
createStaticOgImageHandler
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/route-handlers.ts"],"sourcesContent":["import type { ImageResponse } from 'next/og'\nimport type { MetadataImageRouteProps, OgImageData } from './types'\n\nfunction resolveStaticFallback<TType extends string>(\n fallbackData: OgImageData<TType> | (() => OgImageData<TType>),\n): OgImageData<TType> {\n return typeof fallbackData === 'function'\n ? (fallbackData as () => OgImageData<TType>)()\n : fallbackData\n}\n\nfunction resolveDynamicFallback<TParams extends Record<string, string>, TType extends string>(\n fallbackData: OgImageData<TType> | ((params: TParams) => OgImageData<TType>),\n params: TParams,\n): OgImageData<TType> {\n return typeof fallbackData === 'function'\n ? (fallbackData as (params: TParams) => OgImageData<TType>)(params)\n : fallbackData\n}\n\nexport function createStaticOgImageHandler<TType extends string>(options: {\n resolveData?: () => Promise<OgImageData<TType> | null> | OgImageData<TType> | null\n fallbackData: OgImageData<TType> | (() => OgImageData<TType>)\n render: (data: OgImageData<TType>) => Promise<ImageResponse> | ImageResponse\n}) {\n return async function StaticOgImageHandler() {\n const resolved = options.resolveData ? await options.resolveData() : null\n return options.render(resolved ?? resolveStaticFallback(options.fallbackData))\n }\n}\n\nexport function createDynamicOgImageHandler<\n TParams extends Record<string, string>,\n TType extends string,\n>(options: {\n resolveData: (params: TParams) => Promise<OgImageData<TType> | null> | OgImageData<TType> | null\n fallbackData: OgImageData<TType> | ((params: TParams) => OgImageData<TType>)\n render: (data: OgImageData<TType>) => Promise<ImageResponse> | ImageResponse\n}) {\n return async function DynamicOgImageHandler(props: MetadataImageRouteProps<TParams>) {\n const params = await props.params\n const resolved = await options.resolveData(params)\n return options.render(resolved ?? resolveDynamicFallback(options.fallbackData, params))\n }\n}\n"],"mappings":";;;;;;AAGA,SAAS,sBACP,cACoB;AACpB,SAAO,OAAO,iBAAiB,aAC1B,aAA0C,IAC3C;AACN;AAEA,SAAS,uBACP,cACA,QACoB;AACpB,SAAO,OAAO,iBAAiB,aAC1B,aAAyD,MAAM,IAChE;AACN;AAEO,SAAS,2BAAiD,SAI9D;AACD,SAAO,eAAe,uBAAuB;AAC3C,UAAM,WAAW,QAAQ,cAAc,MAAM,QAAQ,YAAY,IAAI;AACrE,WAAO,QAAQ,OAAO,YAAY,sBAAsB,QAAQ,YAAY,CAAC;AAAA,EAC/E;AACF;AAEO,SAAS,4BAGd,SAIC;AACD,SAAO,eAAe,sBAAsB,OAAyC;AACnF,UAAM,SAAS,MAAM,MAAM;AAC3B,UAAM,WAAW,MAAM,QAAQ,YAAY,MAAM;AACjD,WAAO,QAAQ,OAAO,YAAY,uBAAuB,QAAQ,cAAc,MAAM,CAAC;AAAA,EACxF;AACF;","names":[]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ImageResponse } from 'next/og';
|
|
2
|
+
import { O as OgImageData, a as OgFontDefinition } from '../../types-CaggCk6z.js';
|
|
3
|
+
|
|
4
|
+
type SifOgType = 'brand' | 'blog' | 'service' | 'portfolio';
|
|
5
|
+
type SifOgData = OgImageData<SifOgType>;
|
|
6
|
+
type SifOgTheme = {
|
|
7
|
+
label: string;
|
|
8
|
+
accent: string;
|
|
9
|
+
accentSoft: string;
|
|
10
|
+
glow: string;
|
|
11
|
+
chipBorder: string;
|
|
12
|
+
chipBg: string;
|
|
13
|
+
};
|
|
14
|
+
type SifPresetConfig = {
|
|
15
|
+
brandName?: string;
|
|
16
|
+
domainLabel?: string;
|
|
17
|
+
themes?: Partial<Record<SifOgType, SifOgTheme>>;
|
|
18
|
+
loadFonts?: () => Promise<OgFontDefinition[]>;
|
|
19
|
+
};
|
|
20
|
+
type SifOgRenderer = (data: SifOgData) => Promise<ImageResponse>;
|
|
21
|
+
|
|
22
|
+
declare function createSifFallbacks(config?: {
|
|
23
|
+
brandName?: string;
|
|
24
|
+
}): {
|
|
25
|
+
global: SifOgData;
|
|
26
|
+
section: Record<SifOgType, SifOgData>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
declare function createSifOgRenderer(config?: SifPresetConfig): SifOgRenderer;
|
|
30
|
+
|
|
31
|
+
export { type SifOgData, type SifOgRenderer, type SifOgTheme, type SifOgType, type SifPresetConfig, createSifFallbacks, createSifOgRenderer };
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OG_IMAGE_SIZE_1200x630
|
|
3
|
+
} from "../../chunk-ZZJXXDAB.js";
|
|
4
|
+
|
|
5
|
+
// src/core/image-response.tsx
|
|
6
|
+
import { ImageResponse } from "next/og";
|
|
7
|
+
function createPngImageResponse(element, options = {}) {
|
|
8
|
+
const size = options.size ?? OG_IMAGE_SIZE_1200x630;
|
|
9
|
+
const fonts = options.fonts?.length ? options.fonts : void 0;
|
|
10
|
+
return new ImageResponse(element, {
|
|
11
|
+
width: size.width,
|
|
12
|
+
height: size.height,
|
|
13
|
+
...fonts ? { fonts } : {}
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/core/normalize.ts
|
|
18
|
+
function clampText(text, maxLength) {
|
|
19
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
20
|
+
if (normalized.length <= maxLength) return normalized;
|
|
21
|
+
const clipped = normalized.slice(0, Math.max(1, maxLength - 1));
|
|
22
|
+
const lastSpace = clipped.lastIndexOf(" ");
|
|
23
|
+
const safe = lastSpace > Math.floor(maxLength * 0.6) ? clipped.slice(0, lastSpace) : clipped;
|
|
24
|
+
return `${safe.trimEnd()}\u2026`;
|
|
25
|
+
}
|
|
26
|
+
function normalizeChips(chips, max = 3) {
|
|
27
|
+
if (!chips?.length) return [];
|
|
28
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29
|
+
const result = [];
|
|
30
|
+
for (const chip of chips) {
|
|
31
|
+
const normalized = chip.replace(/\s+/g, " ").trim();
|
|
32
|
+
if (!normalized) continue;
|
|
33
|
+
const key = normalized.toLowerCase();
|
|
34
|
+
if (seen.has(key)) continue;
|
|
35
|
+
seen.add(key);
|
|
36
|
+
result.push(clampText(normalized, 24));
|
|
37
|
+
if (result.length >= max) break;
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
function normalizeOgImageData(data, options = {}) {
|
|
42
|
+
const title = clampText(data.title, options.maxTitleLength ?? 140);
|
|
43
|
+
const brand = (data.brand ?? options.defaultBrand ?? "").trim();
|
|
44
|
+
const subtitleSeed = data.subtitle ?? options.defaultSubtitle ?? brand;
|
|
45
|
+
const subtitle = clampText(subtitleSeed || title, options.maxSubtitleLength ?? 160);
|
|
46
|
+
const chips = normalizeChips(data.chips, options.maxChips ?? 3);
|
|
47
|
+
return {
|
|
48
|
+
type: data.type,
|
|
49
|
+
title,
|
|
50
|
+
subtitle,
|
|
51
|
+
chips: chips.length > 0 ? chips : brand ? [brand] : [],
|
|
52
|
+
brand
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/presets/sif/theme.ts
|
|
57
|
+
var SIF_BRAND_COLORS = {
|
|
58
|
+
primary: "#EE6C4D",
|
|
59
|
+
primaryBright: "#FF8A67",
|
|
60
|
+
ink: "#292A38",
|
|
61
|
+
slate: "#C7D3DD",
|
|
62
|
+
paper: "#E8EEF2",
|
|
63
|
+
night: "#121726"
|
|
64
|
+
};
|
|
65
|
+
var SIF_DEFAULT_THEMES = {
|
|
66
|
+
blog: {
|
|
67
|
+
label: "Blog",
|
|
68
|
+
accent: SIF_BRAND_COLORS.primary,
|
|
69
|
+
accentSoft: "rgba(238, 108, 77, 0.16)",
|
|
70
|
+
glow: "rgba(238, 108, 77, 0.28)",
|
|
71
|
+
chipBorder: "rgba(238, 108, 77, 0.28)",
|
|
72
|
+
chipBg: "rgba(238, 108, 77, 0.1)"
|
|
73
|
+
},
|
|
74
|
+
service: {
|
|
75
|
+
label: "Service",
|
|
76
|
+
accent: "#F27C62",
|
|
77
|
+
accentSoft: "rgba(242, 124, 98, 0.16)",
|
|
78
|
+
glow: "rgba(242, 124, 98, 0.27)",
|
|
79
|
+
chipBorder: "rgba(242, 124, 98, 0.26)",
|
|
80
|
+
chipBg: "rgba(242, 124, 98, 0.09)"
|
|
81
|
+
},
|
|
82
|
+
portfolio: {
|
|
83
|
+
label: "Portfolio",
|
|
84
|
+
accent: SIF_BRAND_COLORS.primaryBright,
|
|
85
|
+
accentSoft: "rgba(255, 138, 103, 0.16)",
|
|
86
|
+
glow: "rgba(255, 138, 103, 0.25)",
|
|
87
|
+
chipBorder: "rgba(255, 138, 103, 0.24)",
|
|
88
|
+
chipBg: "rgba(255, 138, 103, 0.08)"
|
|
89
|
+
},
|
|
90
|
+
brand: {
|
|
91
|
+
label: "Software InFocus",
|
|
92
|
+
accent: SIF_BRAND_COLORS.primary,
|
|
93
|
+
accentSoft: "rgba(238, 108, 77, 0.18)",
|
|
94
|
+
glow: "rgba(238, 108, 77, 0.28)",
|
|
95
|
+
chipBorder: "rgba(238, 108, 77, 0.28)",
|
|
96
|
+
chipBg: "rgba(238, 108, 77, 0.1)"
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
var SIF_LOGO_MARK_VIEWBOX = "0 0 256 128";
|
|
100
|
+
var SIF_LOGO_MARK_PATHS = [
|
|
101
|
+
"M64 24 L24 64 L64 104",
|
|
102
|
+
"M144 16 L112 112",
|
|
103
|
+
"M192 24 L232 64 L192 104"
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
// src/presets/sif/template.tsx
|
|
107
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
108
|
+
function SifLogoMark({
|
|
109
|
+
width = 132,
|
|
110
|
+
height = 66,
|
|
111
|
+
color = "#EE6C4D",
|
|
112
|
+
opacity = 1
|
|
113
|
+
}) {
|
|
114
|
+
return /* @__PURE__ */ jsx(
|
|
115
|
+
"svg",
|
|
116
|
+
{
|
|
117
|
+
width,
|
|
118
|
+
height,
|
|
119
|
+
viewBox: SIF_LOGO_MARK_VIEWBOX,
|
|
120
|
+
fill: "none",
|
|
121
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
122
|
+
style: { opacity },
|
|
123
|
+
children: SIF_LOGO_MARK_PATHS.map((path) => /* @__PURE__ */ jsx(
|
|
124
|
+
"path",
|
|
125
|
+
{
|
|
126
|
+
d: path,
|
|
127
|
+
stroke: color,
|
|
128
|
+
strokeWidth: "16",
|
|
129
|
+
strokeLinecap: "square",
|
|
130
|
+
strokeLinejoin: "miter"
|
|
131
|
+
},
|
|
132
|
+
path
|
|
133
|
+
))
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
function SifOgTemplate({ data, theme, domainLabel }) {
|
|
138
|
+
const chipsToRender = data.chips.slice(0, 3);
|
|
139
|
+
const titleFontSize = data.title.length < 36 ? 58 : data.title.length < 60 ? 50 : 42;
|
|
140
|
+
return /* @__PURE__ */ jsxs(
|
|
141
|
+
"div",
|
|
142
|
+
{
|
|
143
|
+
style: {
|
|
144
|
+
width: OG_IMAGE_SIZE_1200x630.width,
|
|
145
|
+
height: OG_IMAGE_SIZE_1200x630.height,
|
|
146
|
+
display: "flex",
|
|
147
|
+
position: "relative",
|
|
148
|
+
overflow: "hidden",
|
|
149
|
+
background: `linear-gradient(145deg, ${SIF_BRAND_COLORS.night} 0%, #0d1424 38%, #171c2b 68%, ${SIF_BRAND_COLORS.ink} 100%)`,
|
|
150
|
+
color: SIF_BRAND_COLORS.paper
|
|
151
|
+
},
|
|
152
|
+
children: [
|
|
153
|
+
/* @__PURE__ */ jsx(
|
|
154
|
+
"div",
|
|
155
|
+
{
|
|
156
|
+
style: {
|
|
157
|
+
position: "absolute",
|
|
158
|
+
inset: 0,
|
|
159
|
+
backgroundImage: "linear-gradient(rgba(199,211,221,0.025) 1px, transparent 1px), linear-gradient(90deg, rgba(199,211,221,0.025) 1px, transparent 1px)",
|
|
160
|
+
backgroundSize: "36px 36px",
|
|
161
|
+
opacity: 0.35
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
),
|
|
165
|
+
/* @__PURE__ */ jsx(
|
|
166
|
+
"div",
|
|
167
|
+
{
|
|
168
|
+
style: {
|
|
169
|
+
position: "absolute",
|
|
170
|
+
top: -140,
|
|
171
|
+
right: -80,
|
|
172
|
+
width: 480,
|
|
173
|
+
height: 480,
|
|
174
|
+
borderRadius: 480,
|
|
175
|
+
background: `radial-gradient(circle, ${theme.glow} 0%, rgba(0,0,0,0) 70%)`
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
),
|
|
179
|
+
/* @__PURE__ */ jsx(
|
|
180
|
+
"div",
|
|
181
|
+
{
|
|
182
|
+
style: {
|
|
183
|
+
position: "absolute",
|
|
184
|
+
bottom: -120,
|
|
185
|
+
left: -80,
|
|
186
|
+
width: 320,
|
|
187
|
+
height: 320,
|
|
188
|
+
borderRadius: 320,
|
|
189
|
+
background: "radial-gradient(circle, rgba(238,108,77,0.14) 0%, rgba(0,0,0,0) 72%)"
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
),
|
|
193
|
+
/* @__PURE__ */ jsx(
|
|
194
|
+
"div",
|
|
195
|
+
{
|
|
196
|
+
style: {
|
|
197
|
+
position: "absolute",
|
|
198
|
+
right: -30,
|
|
199
|
+
top: 183,
|
|
200
|
+
display: "flex"
|
|
201
|
+
},
|
|
202
|
+
children: /* @__PURE__ */ jsx(SifLogoMark, { width: 528, height: 264, color: theme.accent, opacity: 0.055 })
|
|
203
|
+
}
|
|
204
|
+
),
|
|
205
|
+
/* @__PURE__ */ jsxs(
|
|
206
|
+
"div",
|
|
207
|
+
{
|
|
208
|
+
style: {
|
|
209
|
+
position: "relative",
|
|
210
|
+
zIndex: 2,
|
|
211
|
+
display: "flex",
|
|
212
|
+
flexDirection: "column",
|
|
213
|
+
width: "100%",
|
|
214
|
+
height: "100%",
|
|
215
|
+
padding: "52px 60px"
|
|
216
|
+
},
|
|
217
|
+
children: [
|
|
218
|
+
/* @__PURE__ */ jsxs(
|
|
219
|
+
"div",
|
|
220
|
+
{
|
|
221
|
+
style: {
|
|
222
|
+
display: "flex",
|
|
223
|
+
alignItems: "center",
|
|
224
|
+
justifyContent: "space-between"
|
|
225
|
+
},
|
|
226
|
+
children: [
|
|
227
|
+
/* @__PURE__ */ jsxs(
|
|
228
|
+
"div",
|
|
229
|
+
{
|
|
230
|
+
style: {
|
|
231
|
+
display: "flex",
|
|
232
|
+
alignItems: "center",
|
|
233
|
+
gap: 12
|
|
234
|
+
},
|
|
235
|
+
children: [
|
|
236
|
+
/* @__PURE__ */ jsx(SifLogoMark, { width: 52, height: 26, color: theme.accent, opacity: 0.95 }),
|
|
237
|
+
/* @__PURE__ */ jsx(
|
|
238
|
+
"span",
|
|
239
|
+
{
|
|
240
|
+
style: {
|
|
241
|
+
display: "flex",
|
|
242
|
+
fontSize: 20,
|
|
243
|
+
fontWeight: 600,
|
|
244
|
+
color: SIF_BRAND_COLORS.paper,
|
|
245
|
+
letterSpacing: 0.2
|
|
246
|
+
},
|
|
247
|
+
children: data.brand
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
]
|
|
251
|
+
}
|
|
252
|
+
),
|
|
253
|
+
/* @__PURE__ */ jsxs(
|
|
254
|
+
"div",
|
|
255
|
+
{
|
|
256
|
+
style: {
|
|
257
|
+
display: "flex",
|
|
258
|
+
alignItems: "center",
|
|
259
|
+
gap: 10,
|
|
260
|
+
borderRadius: 999,
|
|
261
|
+
border: `1px solid ${theme.chipBorder}`,
|
|
262
|
+
background: "rgba(18,23,38,0.55)",
|
|
263
|
+
height: 44,
|
|
264
|
+
padding: "0 20px"
|
|
265
|
+
},
|
|
266
|
+
children: [
|
|
267
|
+
/* @__PURE__ */ jsx(
|
|
268
|
+
"div",
|
|
269
|
+
{
|
|
270
|
+
style: {
|
|
271
|
+
width: 8,
|
|
272
|
+
height: 8,
|
|
273
|
+
borderRadius: 8,
|
|
274
|
+
background: theme.accent,
|
|
275
|
+
boxShadow: `0 0 14px ${theme.glow}`
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
),
|
|
279
|
+
/* @__PURE__ */ jsx(
|
|
280
|
+
"span",
|
|
281
|
+
{
|
|
282
|
+
style: {
|
|
283
|
+
fontSize: 17,
|
|
284
|
+
fontWeight: 500,
|
|
285
|
+
color: SIF_BRAND_COLORS.paper,
|
|
286
|
+
letterSpacing: 0.2,
|
|
287
|
+
display: "flex"
|
|
288
|
+
},
|
|
289
|
+
children: theme.label
|
|
290
|
+
}
|
|
291
|
+
)
|
|
292
|
+
]
|
|
293
|
+
}
|
|
294
|
+
)
|
|
295
|
+
]
|
|
296
|
+
}
|
|
297
|
+
),
|
|
298
|
+
/* @__PURE__ */ jsxs(
|
|
299
|
+
"div",
|
|
300
|
+
{
|
|
301
|
+
style: {
|
|
302
|
+
display: "flex",
|
|
303
|
+
flexDirection: "column",
|
|
304
|
+
flex: 1,
|
|
305
|
+
justifyContent: "center",
|
|
306
|
+
paddingBottom: 8
|
|
307
|
+
},
|
|
308
|
+
children: [
|
|
309
|
+
/* @__PURE__ */ jsx(
|
|
310
|
+
"div",
|
|
311
|
+
{
|
|
312
|
+
style: {
|
|
313
|
+
width: 52,
|
|
314
|
+
height: 3,
|
|
315
|
+
borderRadius: 999,
|
|
316
|
+
background: theme.accent,
|
|
317
|
+
boxShadow: `0 0 20px ${theme.glow}`,
|
|
318
|
+
marginBottom: 24
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
),
|
|
322
|
+
/* @__PURE__ */ jsx(
|
|
323
|
+
"div",
|
|
324
|
+
{
|
|
325
|
+
style: {
|
|
326
|
+
fontSize: titleFontSize,
|
|
327
|
+
fontWeight: 700,
|
|
328
|
+
lineHeight: 1.18,
|
|
329
|
+
color: SIF_BRAND_COLORS.paper,
|
|
330
|
+
maxWidth: 760,
|
|
331
|
+
letterSpacing: -0.5,
|
|
332
|
+
display: "flex",
|
|
333
|
+
flexWrap: "wrap"
|
|
334
|
+
},
|
|
335
|
+
children: data.title
|
|
336
|
+
}
|
|
337
|
+
),
|
|
338
|
+
/* @__PURE__ */ jsx(
|
|
339
|
+
"div",
|
|
340
|
+
{
|
|
341
|
+
style: {
|
|
342
|
+
fontSize: 21,
|
|
343
|
+
lineHeight: 1.55,
|
|
344
|
+
color: "rgba(232,238,242,0.62)",
|
|
345
|
+
maxWidth: 680,
|
|
346
|
+
marginTop: 18,
|
|
347
|
+
display: "flex",
|
|
348
|
+
flexWrap: "wrap"
|
|
349
|
+
},
|
|
350
|
+
children: data.subtitle
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
]
|
|
354
|
+
}
|
|
355
|
+
),
|
|
356
|
+
/* @__PURE__ */ jsxs(
|
|
357
|
+
"div",
|
|
358
|
+
{
|
|
359
|
+
style: {
|
|
360
|
+
display: "flex",
|
|
361
|
+
alignItems: "center",
|
|
362
|
+
justifyContent: "space-between"
|
|
363
|
+
},
|
|
364
|
+
children: [
|
|
365
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 10 }, children: chipsToRender.map((chip) => /* @__PURE__ */ jsx(
|
|
366
|
+
"div",
|
|
367
|
+
{
|
|
368
|
+
style: {
|
|
369
|
+
display: "flex",
|
|
370
|
+
alignItems: "center",
|
|
371
|
+
height: 38,
|
|
372
|
+
borderRadius: 999,
|
|
373
|
+
padding: "0 16px",
|
|
374
|
+
fontSize: 15,
|
|
375
|
+
border: `1px solid ${theme.chipBorder}`,
|
|
376
|
+
background: theme.chipBg,
|
|
377
|
+
color: SIF_BRAND_COLORS.paper
|
|
378
|
+
},
|
|
379
|
+
children: chip
|
|
380
|
+
},
|
|
381
|
+
chip
|
|
382
|
+
)) }),
|
|
383
|
+
/* @__PURE__ */ jsx(
|
|
384
|
+
"span",
|
|
385
|
+
{
|
|
386
|
+
style: {
|
|
387
|
+
fontSize: 15,
|
|
388
|
+
color: "rgba(232,238,242,0.38)",
|
|
389
|
+
letterSpacing: 0.4,
|
|
390
|
+
display: "flex"
|
|
391
|
+
},
|
|
392
|
+
children: domainLabel
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
]
|
|
396
|
+
}
|
|
397
|
+
)
|
|
398
|
+
]
|
|
399
|
+
}
|
|
400
|
+
)
|
|
401
|
+
]
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/presets/sif/fallbacks.ts
|
|
407
|
+
function createSifFallbacks(config = {}) {
|
|
408
|
+
const brandName = config.brandName?.trim() || "Software InFocus";
|
|
409
|
+
const global = {
|
|
410
|
+
type: "brand",
|
|
411
|
+
title: brandName,
|
|
412
|
+
subtitle: "Custom websites, software, and growth systems for Delaware businesses",
|
|
413
|
+
chips: ["Web Development", "Design", "SEO"],
|
|
414
|
+
brand: brandName
|
|
415
|
+
};
|
|
416
|
+
const section = {
|
|
417
|
+
brand: global,
|
|
418
|
+
blog: {
|
|
419
|
+
type: "blog",
|
|
420
|
+
title: `${brandName} Blog`,
|
|
421
|
+
subtitle: "Practical insights on websites, software, SEO, and growth",
|
|
422
|
+
chips: ["Articles", "Guides", "Case Notes"],
|
|
423
|
+
brand: brandName
|
|
424
|
+
},
|
|
425
|
+
portfolio: {
|
|
426
|
+
type: "portfolio",
|
|
427
|
+
title: `${brandName} Portfolio`,
|
|
428
|
+
subtitle: "Case studies and project outcomes across web, SEO, and software delivery",
|
|
429
|
+
chips: ["Case Studies", "Web Builds", "Results"],
|
|
430
|
+
brand: brandName
|
|
431
|
+
},
|
|
432
|
+
service: {
|
|
433
|
+
type: "service",
|
|
434
|
+
title: `${brandName} Services`,
|
|
435
|
+
subtitle: "Design, development, SEO, hosting, and long-term support",
|
|
436
|
+
chips: ["Delaware", "Custom", "Lead-Focused"],
|
|
437
|
+
brand: brandName
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
return { global, section };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/presets/sif/index.tsx
|
|
444
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
445
|
+
function mergeSifThemes(overrides) {
|
|
446
|
+
return {
|
|
447
|
+
brand: overrides?.brand ?? SIF_DEFAULT_THEMES.brand,
|
|
448
|
+
blog: overrides?.blog ?? SIF_DEFAULT_THEMES.blog,
|
|
449
|
+
service: overrides?.service ?? SIF_DEFAULT_THEMES.service,
|
|
450
|
+
portfolio: overrides?.portfolio ?? SIF_DEFAULT_THEMES.portfolio
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function normalizeSifOgData(input, brandName) {
|
|
454
|
+
const normalized = normalizeOgImageData(input, {
|
|
455
|
+
defaultBrand: brandName,
|
|
456
|
+
defaultSubtitle: brandName,
|
|
457
|
+
maxTitleLength: 140,
|
|
458
|
+
maxSubtitleLength: 160,
|
|
459
|
+
maxChips: 3
|
|
460
|
+
});
|
|
461
|
+
return {
|
|
462
|
+
type: normalized.type,
|
|
463
|
+
title: normalized.title,
|
|
464
|
+
subtitle: normalized.subtitle,
|
|
465
|
+
chips: normalized.chips,
|
|
466
|
+
brand: normalized.brand || brandName
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function createSifOgRenderer(config = {}) {
|
|
470
|
+
const brandName = config.brandName?.trim() || "Software InFocus";
|
|
471
|
+
const domainLabel = config.domainLabel?.trim() || "softwareinfocus.com";
|
|
472
|
+
const themes = mergeSifThemes(config.themes);
|
|
473
|
+
const loadFonts = config.loadFonts;
|
|
474
|
+
return async (input) => {
|
|
475
|
+
const data = normalizeSifOgData(input, brandName);
|
|
476
|
+
const theme = themes[data.type];
|
|
477
|
+
const fonts = loadFonts ? await loadFonts() : [];
|
|
478
|
+
return createPngImageResponse(
|
|
479
|
+
/* @__PURE__ */ jsx2(SifOgTemplate, { data, theme, domainLabel }),
|
|
480
|
+
{ fonts }
|
|
481
|
+
);
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
export {
|
|
485
|
+
createSifFallbacks,
|
|
486
|
+
createSifOgRenderer
|
|
487
|
+
};
|
|
488
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/core/image-response.tsx","../../../src/core/normalize.ts","../../../src/presets/sif/theme.ts","../../../src/presets/sif/template.tsx","../../../src/presets/sif/fallbacks.ts","../../../src/presets/sif/index.tsx"],"sourcesContent":["import { ImageResponse } from 'next/og'\nimport type { ReactElement } from 'react'\nimport { OG_IMAGE_SIZE_1200x630 } from './constants'\nimport type { OgFontDefinition, OgImageSize } from './types'\n\nexport function createPngImageResponse(\n element: ReactElement,\n options: {\n fonts?: OgFontDefinition[]\n size?: OgImageSize\n } = {},\n) {\n const size = options.size ?? OG_IMAGE_SIZE_1200x630\n const fonts = options.fonts?.length ? options.fonts : undefined\n\n return new ImageResponse(element, {\n width: size.width,\n height: size.height,\n ...(fonts ? { fonts } : {}),\n })\n}\n","import type { OgImageData } from './types'\n\nexport function clampText(text: string, maxLength: number): string {\n const normalized = text.replace(/\\s+/g, ' ').trim()\n if (normalized.length <= maxLength) return normalized\n\n const clipped = normalized.slice(0, Math.max(1, maxLength - 1))\n const lastSpace = clipped.lastIndexOf(' ')\n const safe = lastSpace > Math.floor(maxLength * 0.6) ? clipped.slice(0, lastSpace) : clipped\n\n return `${safe.trimEnd()}…`\n}\n\nexport function normalizeChips(chips: string[] | undefined, max = 3): string[] {\n if (!chips?.length) return []\n\n const seen = new Set<string>()\n const result: string[] = []\n\n for (const chip of chips) {\n const normalized = chip.replace(/\\s+/g, ' ').trim()\n if (!normalized) continue\n\n const key = normalized.toLowerCase()\n if (seen.has(key)) continue\n seen.add(key)\n\n result.push(clampText(normalized, 24))\n if (result.length >= max) break\n }\n\n return result\n}\n\nexport function normalizeOgImageData<TType extends string>(\n data: OgImageData<TType>,\n options: {\n defaultBrand?: string\n defaultSubtitle?: string\n maxTitleLength?: number\n maxSubtitleLength?: number\n maxChips?: number\n } = {},\n): Required<Pick<OgImageData<TType>, 'type' | 'title' | 'subtitle' | 'chips' | 'brand'>> {\n const title = clampText(data.title, options.maxTitleLength ?? 140)\n const brand = (data.brand ?? options.defaultBrand ?? '').trim()\n const subtitleSeed = data.subtitle ?? options.defaultSubtitle ?? brand\n const subtitle = clampText(subtitleSeed || title, options.maxSubtitleLength ?? 160)\n const chips = normalizeChips(data.chips, options.maxChips ?? 3)\n\n return {\n type: data.type,\n title,\n subtitle,\n chips: chips.length > 0 ? chips : (brand ? [brand] : []),\n brand,\n }\n}\n","import type { SifOgTheme, SifOgType } from './types'\n\nexport const SIF_BRAND_COLORS = {\n primary: '#EE6C4D',\n primaryBright: '#FF8A67',\n ink: '#292A38',\n slate: '#C7D3DD',\n paper: '#E8EEF2',\n night: '#121726',\n} as const\n\nexport const SIF_DEFAULT_THEMES: Record<SifOgType, SifOgTheme> = {\n blog: {\n label: 'Blog',\n accent: SIF_BRAND_COLORS.primary,\n accentSoft: 'rgba(238, 108, 77, 0.16)',\n glow: 'rgba(238, 108, 77, 0.28)',\n chipBorder: 'rgba(238, 108, 77, 0.28)',\n chipBg: 'rgba(238, 108, 77, 0.1)',\n },\n service: {\n label: 'Service',\n accent: '#F27C62',\n accentSoft: 'rgba(242, 124, 98, 0.16)',\n glow: 'rgba(242, 124, 98, 0.27)',\n chipBorder: 'rgba(242, 124, 98, 0.26)',\n chipBg: 'rgba(242, 124, 98, 0.09)',\n },\n portfolio: {\n label: 'Portfolio',\n accent: SIF_BRAND_COLORS.primaryBright,\n accentSoft: 'rgba(255, 138, 103, 0.16)',\n glow: 'rgba(255, 138, 103, 0.25)',\n chipBorder: 'rgba(255, 138, 103, 0.24)',\n chipBg: 'rgba(255, 138, 103, 0.08)',\n },\n brand: {\n label: 'Software InFocus',\n accent: SIF_BRAND_COLORS.primary,\n accentSoft: 'rgba(238, 108, 77, 0.18)',\n glow: 'rgba(238, 108, 77, 0.28)',\n chipBorder: 'rgba(238, 108, 77, 0.28)',\n chipBg: 'rgba(238, 108, 77, 0.1)',\n },\n}\n\nexport const SIF_LOGO_MARK_VIEWBOX = '0 0 256 128'\n\nexport const SIF_LOGO_MARK_PATHS = [\n 'M64 24 L24 64 L64 104',\n 'M144 16 L112 112',\n 'M192 24 L232 64 L192 104',\n] as const\n","import { OG_IMAGE_SIZE_1200x630 } from '../../core/constants'\nimport type { SifNormalizedOgData, SifOgTheme } from './types'\nimport { SIF_BRAND_COLORS, SIF_LOGO_MARK_PATHS, SIF_LOGO_MARK_VIEWBOX } from './theme'\n\ntype SifLogoMarkProps = {\n width?: number\n height?: number\n color?: string\n opacity?: number\n}\n\nfunction SifLogoMark({\n width = 132,\n height = 66,\n color = '#EE6C4D',\n opacity = 1,\n}: SifLogoMarkProps) {\n return (\n <svg\n width={width}\n height={height}\n viewBox={SIF_LOGO_MARK_VIEWBOX}\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n style={{ opacity }}\n >\n {SIF_LOGO_MARK_PATHS.map((path) => (\n <path\n key={path}\n d={path}\n stroke={color}\n strokeWidth=\"16\"\n strokeLinecap=\"square\"\n strokeLinejoin=\"miter\"\n />\n ))}\n </svg>\n )\n}\n\ntype SifOgTemplateProps = {\n data: SifNormalizedOgData\n theme: SifOgTheme\n domainLabel: string\n}\n\nexport function SifOgTemplate({ data, theme, domainLabel }: SifOgTemplateProps) {\n const chipsToRender = data.chips.slice(0, 3)\n const titleFontSize = data.title.length < 36 ? 58 : data.title.length < 60 ? 50 : 42\n\n return (\n <div\n style={{\n width: OG_IMAGE_SIZE_1200x630.width,\n height: OG_IMAGE_SIZE_1200x630.height,\n display: 'flex',\n position: 'relative',\n overflow: 'hidden',\n background: `linear-gradient(145deg, ${SIF_BRAND_COLORS.night} 0%, #0d1424 38%, #171c2b 68%, ${SIF_BRAND_COLORS.ink} 100%)`,\n color: SIF_BRAND_COLORS.paper,\n }}\n >\n <div\n style={{\n position: 'absolute',\n inset: 0,\n backgroundImage:\n 'linear-gradient(rgba(199,211,221,0.025) 1px, transparent 1px), linear-gradient(90deg, rgba(199,211,221,0.025) 1px, transparent 1px)',\n backgroundSize: '36px 36px',\n opacity: 0.35,\n }}\n />\n\n <div\n style={{\n position: 'absolute',\n top: -140,\n right: -80,\n width: 480,\n height: 480,\n borderRadius: 480,\n background: `radial-gradient(circle, ${theme.glow} 0%, rgba(0,0,0,0) 70%)`,\n }}\n />\n\n <div\n style={{\n position: 'absolute',\n bottom: -120,\n left: -80,\n width: 320,\n height: 320,\n borderRadius: 320,\n background: 'radial-gradient(circle, rgba(238,108,77,0.14) 0%, rgba(0,0,0,0) 72%)',\n }}\n />\n\n <div\n style={{\n position: 'absolute',\n right: -30,\n top: 183,\n display: 'flex',\n }}\n >\n <SifLogoMark width={528} height={264} color={theme.accent} opacity={0.055} />\n </div>\n\n <div\n style={{\n position: 'relative',\n zIndex: 2,\n display: 'flex',\n flexDirection: 'column',\n width: '100%',\n height: '100%',\n padding: '52px 60px',\n }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 12,\n }}\n >\n <SifLogoMark width={52} height={26} color={theme.accent} opacity={0.95} />\n <span\n style={{\n display: 'flex',\n fontSize: 20,\n fontWeight: 600,\n color: SIF_BRAND_COLORS.paper,\n letterSpacing: 0.2,\n }}\n >\n {data.brand}\n </span>\n </div>\n\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n borderRadius: 999,\n border: `1px solid ${theme.chipBorder}`,\n background: 'rgba(18,23,38,0.55)',\n height: 44,\n padding: '0 20px',\n }}\n >\n <div\n style={{\n width: 8,\n height: 8,\n borderRadius: 8,\n background: theme.accent,\n boxShadow: `0 0 14px ${theme.glow}`,\n }}\n />\n <span\n style={{\n fontSize: 17,\n fontWeight: 500,\n color: SIF_BRAND_COLORS.paper,\n letterSpacing: 0.2,\n display: 'flex',\n }}\n >\n {theme.label}\n </span>\n </div>\n </div>\n\n <div\n style={{\n display: 'flex',\n flexDirection: 'column',\n flex: 1,\n justifyContent: 'center',\n paddingBottom: 8,\n }}\n >\n <div\n style={{\n width: 52,\n height: 3,\n borderRadius: 999,\n background: theme.accent,\n boxShadow: `0 0 20px ${theme.glow}`,\n marginBottom: 24,\n }}\n />\n\n <div\n style={{\n fontSize: titleFontSize,\n fontWeight: 700,\n lineHeight: 1.18,\n color: SIF_BRAND_COLORS.paper,\n maxWidth: 760,\n letterSpacing: -0.5,\n display: 'flex',\n flexWrap: 'wrap',\n }}\n >\n {data.title}\n </div>\n\n <div\n style={{\n fontSize: 21,\n lineHeight: 1.55,\n color: 'rgba(232,238,242,0.62)',\n maxWidth: 680,\n marginTop: 18,\n display: 'flex',\n flexWrap: 'wrap',\n }}\n >\n {data.subtitle}\n </div>\n </div>\n\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <div style={{ display: 'flex', gap: 10 }}>\n {chipsToRender.map((chip) => (\n <div\n key={chip}\n style={{\n display: 'flex',\n alignItems: 'center',\n height: 38,\n borderRadius: 999,\n padding: '0 16px',\n fontSize: 15,\n border: `1px solid ${theme.chipBorder}`,\n background: theme.chipBg,\n color: SIF_BRAND_COLORS.paper,\n }}\n >\n {chip}\n </div>\n ))}\n </div>\n\n <span\n style={{\n fontSize: 15,\n color: 'rgba(232,238,242,0.38)',\n letterSpacing: 0.4,\n display: 'flex',\n }}\n >\n {domainLabel}\n </span>\n </div>\n </div>\n </div>\n )\n}\n","import type { SifOgData } from './types'\n\nexport function createSifFallbacks(config: { brandName?: string } = {}) {\n const brandName = config.brandName?.trim() || 'Software InFocus'\n\n const global: SifOgData = {\n type: 'brand',\n title: brandName,\n subtitle: 'Custom websites, software, and growth systems for Delaware businesses',\n chips: ['Web Development', 'Design', 'SEO'],\n brand: brandName,\n }\n\n const section: Record<SifOgData['type'], SifOgData> = {\n brand: global,\n blog: {\n type: 'blog',\n title: `${brandName} Blog`,\n subtitle: 'Practical insights on websites, software, SEO, and growth',\n chips: ['Articles', 'Guides', 'Case Notes'],\n brand: brandName,\n },\n portfolio: {\n type: 'portfolio',\n title: `${brandName} Portfolio`,\n subtitle: 'Case studies and project outcomes across web, SEO, and software delivery',\n chips: ['Case Studies', 'Web Builds', 'Results'],\n brand: brandName,\n },\n service: {\n type: 'service',\n title: `${brandName} Services`,\n subtitle: 'Design, development, SEO, hosting, and long-term support',\n chips: ['Delaware', 'Custom', 'Lead-Focused'],\n brand: brandName,\n },\n }\n\n return { global, section }\n}\n","import { createPngImageResponse } from '../../core/image-response'\nimport { normalizeOgImageData } from '../../core/normalize'\nimport { SifOgTemplate } from './template'\nimport { createSifFallbacks } from './fallbacks'\nimport { SIF_DEFAULT_THEMES } from './theme'\nimport type {\n SifNormalizedOgData,\n SifOgData,\n SifOgRenderer,\n SifOgTheme,\n SifPresetConfig,\n SifOgType,\n} from './types'\n\nfunction mergeSifThemes(\n overrides: Partial<Record<SifOgType, SifOgTheme>> | undefined,\n): Record<SifOgType, SifOgTheme> {\n return {\n brand: overrides?.brand ?? SIF_DEFAULT_THEMES.brand,\n blog: overrides?.blog ?? SIF_DEFAULT_THEMES.blog,\n service: overrides?.service ?? SIF_DEFAULT_THEMES.service,\n portfolio: overrides?.portfolio ?? SIF_DEFAULT_THEMES.portfolio,\n }\n}\n\nfunction normalizeSifOgData(input: SifOgData, brandName: string): SifNormalizedOgData {\n const normalized = normalizeOgImageData(input, {\n defaultBrand: brandName,\n defaultSubtitle: brandName,\n maxTitleLength: 140,\n maxSubtitleLength: 160,\n maxChips: 3,\n })\n\n return {\n type: normalized.type,\n title: normalized.title,\n subtitle: normalized.subtitle,\n chips: normalized.chips,\n brand: normalized.brand || brandName,\n }\n}\n\nexport function createSifOgRenderer(config: SifPresetConfig = {}): SifOgRenderer {\n const brandName = config.brandName?.trim() || 'Software InFocus'\n const domainLabel = config.domainLabel?.trim() || 'softwareinfocus.com'\n const themes = mergeSifThemes(config.themes)\n const loadFonts = config.loadFonts\n\n return async (input: SifOgData) => {\n const data = normalizeSifOgData(input, brandName)\n const theme = themes[data.type]\n const fonts = loadFonts ? await loadFonts() : []\n\n return createPngImageResponse(\n <SifOgTemplate data={data} theme={theme} domainLabel={domainLabel} />,\n { fonts },\n )\n }\n}\n\nexport { createSifFallbacks }\nexport type {\n SifOgData,\n SifOgRenderer,\n SifOgTheme,\n SifOgType,\n SifPresetConfig,\n} from './types'\n"],"mappings":"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","names":["jsx"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type OgImageData<TType extends string = string> = {
|
|
2
|
+
type: TType;
|
|
3
|
+
title: string;
|
|
4
|
+
subtitle?: string;
|
|
5
|
+
chips?: string[];
|
|
6
|
+
brand?: string;
|
|
7
|
+
};
|
|
8
|
+
type OgFontDefinition = {
|
|
9
|
+
name: string;
|
|
10
|
+
data: ArrayBuffer;
|
|
11
|
+
style?: 'normal' | 'italic';
|
|
12
|
+
weight: 400 | 500 | 600 | 700;
|
|
13
|
+
};
|
|
14
|
+
type MetadataImageRouteProps<TParams extends Record<string, string>> = {
|
|
15
|
+
params: Promise<TParams> | TParams;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type { MetadataImageRouteProps as M, OgImageData as O, OgFontDefinition as a };
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@softwareinfocus/next-og-image",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Reusable Open Graph image toolkit for Next.js App Router with an optional Software InFocus preset",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./presets/sif": {
|
|
19
|
+
"types": "./dist/presets/sif/index.d.ts",
|
|
20
|
+
"import": "./dist/presets/sif/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"next": ">=15 <16",
|
|
26
|
+
"react": ">=19 <20"
|
|
27
|
+
},
|
|
28
|
+
"peerDependenciesMeta": {
|
|
29
|
+
"react": {
|
|
30
|
+
"optional": false
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@changesets/cli": "^2.29.7",
|
|
35
|
+
"@eslint/js": "^9.20.0",
|
|
36
|
+
"@types/node": "^22.13.5",
|
|
37
|
+
"@types/react": "^19.0.10",
|
|
38
|
+
"eslint": "^9.20.1",
|
|
39
|
+
"globals": "^15.15.0",
|
|
40
|
+
"next": "^15.3.3",
|
|
41
|
+
"react": "^19.0.0",
|
|
42
|
+
"react-dom": "^19.0.0",
|
|
43
|
+
"tsup": "^8.3.5",
|
|
44
|
+
"typescript": "^5.7.3",
|
|
45
|
+
"typescript-eslint": "^8.24.1",
|
|
46
|
+
"vitest": "^3.0.7"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"clean": "rm -rf dist",
|
|
51
|
+
"lint": "eslint .",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
54
|
+
"release:check": "changeset status --verbose"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public",
|
|
58
|
+
"provenance": true
|
|
59
|
+
}
|
|
60
|
+
}
|