@venqoo/brand 0.1.0 → 0.2.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/README.md CHANGED
@@ -31,12 +31,15 @@ import { Credit, ImprintCredit } from '@venqoo/brand/credit'
31
31
  import '@venqoo/brand/credit.css'
32
32
 
33
33
  // footer
34
- <Credit /> // Made with ♥ by venqoo in Würzburg
35
- <Credit variant="pill" theme="dark" /> // outline pill on a dark footer
34
+ <Credit site="baeckerei-muster.de" /> // Made with ♥ by venqoo in Würzburg
35
+ <Credit site="baeckerei-muster.de" variant="pill" theme="dark" />
36
36
 
37
37
  // imprint / legal notice
38
- <ImprintCredit /> // heading, sentence, address
38
+ <ImprintCredit site="baeckerei-muster.de" /> // heading, sentence, address
39
+ <ImprintCredit site="baeckerei-muster.de" lang="de" /> // the same, in German
40
+ <ImprintCredit site="baeckerei-muster.de" subject="app" /> // "This app was designed…"
39
41
  <ImprintCredit
42
+ site="baeckerei-muster.de"
40
43
  variant="card"
41
44
  roles={[
42
45
  { term: 'App', detail: 'iOS & Android' },
@@ -45,8 +48,10 @@ import '@venqoo/brand/credit.css'
45
48
  />
46
49
  ```
47
50
 
48
- Every variant is a single link to `https://www.venqoo.com` that opens in a new
49
- tab. Copy is English only.
51
+ Every variant links to `https://www.venqoo.com` and opens in a new tab. Pass
52
+ `site` on every credit: it tags the link so venqoo can see where a visitor came
53
+ from (see [Attribution and search engines](#attribution-and-search-engines)).
54
+ Footer copy is English only; the imprint credit also comes in German.
50
55
 
51
56
  ### `<Credit>` — footer credit
52
57
 
@@ -74,19 +79,56 @@ concept / design / development / operations; pass your own, or `[]` to hide it.
74
79
  `children` replaces the descriptive sentence on `section`, and `headingLevel`
75
80
  (2–4) matches the imprint's other headings.
76
81
 
82
+ `lang` is `'en'` (default) or `'de'`. It sets the heading, sentence, eyebrow,
83
+ country and default roles, and the root's `lang` attribute. Custom `roles` and
84
+ `children` are yours to translate. The default lists are exported as
85
+ `imprintRoles.en` / `imprintRoles.de` (`defaultRoles` is the English one), so a
86
+ project can extend them.
87
+
88
+ `subject` is `'website'` (default) or `'app'` and sets the first sentence's
89
+ subject: "This website was designed and developed by venqoo, a software agency
90
+ in Würzburg, Germany." becomes "This app was…" on an app's legal screen. A
91
+ second sentence says what venqoo GmbH does; `children` replaces both.
92
+
77
93
  ### Shared props
78
94
 
79
- | Prop | Type | Default |
80
- | ----------- | ----------------------------- | ------------------------ |
81
- | `theme` | `'light' \| 'dark' \| 'auto'` | `'light'` |
82
- | `href` | `string` | `https://www.venqoo.com` |
83
- | `newTab` | `boolean` | `true` |
84
- | `className` | `string` | — |
95
+ | Prop | Type | Default |
96
+ | ----------- | -------------------------------------- | --------------------------------------------- |
97
+ | `site` | `string` | — (set it: the client site's host) |
98
+ | `rel` | `'nofollow' \| 'follow' \| 'sponsored'` | `'nofollow'` on `Credit`, `'follow'` on `ImprintCredit` |
99
+ | `theme` | `'light' \| 'dark' \| 'auto'` | `'light'` |
100
+ | `href` | `string` | `https://www.venqoo.com` |
101
+ | `newTab` | `boolean` | `true` |
102
+ | `className` | `string` | — |
85
103
 
86
104
  `theme` switches the colour tokens: use `dark` for a dark footer, `auto` on
87
105
  sites with a colour-scheme toggle (it follows `prefers-color-scheme`). The
88
106
  component cannot see what it sits on, so `light` is the predictable default.
89
107
 
108
+ ### Attribution and search engines
109
+
110
+ The package runs no script and loads nothing; the link is the whole tracker.
111
+ With `site="baeckerei-muster.de"`, a footer credit links to
112
+
113
+ ```
114
+ https://www.venqoo.com?utm_source=baeckerei-muster.de&utm_medium=credit&utm_campaign=footer&utm_content=heart
115
+ ```
116
+
117
+ `utm_source` is the site (normalised to its bare host), `utm_campaign` the
118
+ placement (`footer` or `imprint`) and `utm_content` the variant, so analytics
119
+ on venqoo.com show which site, placement and design a visitor clicked. Without
120
+ `site` the link is untagged; the Referer header then only names the origin, and
121
+ not at all on sites with a strict `Referrer-Policy`. Existing query parameters
122
+ and fragments on a custom `href` are kept. `creditHref()` builds the same URL
123
+ for links you render yourself.
124
+
125
+ `rel` says how search engines should read the link. Google asks for links that
126
+ sit in the template of many sites to be qualified, so the footer credit is
127
+ `nofollow` by default; the imprint credit is one editorial sentence naming the
128
+ maker and stays `follow`. Both can be overridden per project, and `sponsored` is
129
+ for credits that are part of a deal, such as a discount for keeping them. Links
130
+ that open in a new tab always carry `noopener`, never `noreferrer`.
131
+
90
132
  ### Styling the credit
91
133
 
92
134
  All credit styles live under `.vq-credit` and are driven by CSS custom
@@ -9,17 +9,33 @@ export declare const footerCopy: {
9
9
  readonly pill: 'Made by';
10
10
  readonly pillSignal: 'Made with love by';
11
11
  };
12
+ /** Languages the imprint credit is written in. */
13
+ export type ImprintLang = 'en' | 'de';
14
+ /** What the imprint credit says venqoo made: a website (default) or an app. */
15
+ export type ImprintSubject = 'website' | 'app';
12
16
  export interface ImprintRole {
13
17
  /** What venqoo was responsible for, e.g. "Development". */
14
18
  term: string;
15
19
  /** The specifics, e.g. "Front end & back end". */
16
20
  detail: string;
17
21
  }
18
- export declare const imprintCopy: {
19
- readonly heading: 'Concept, design & development';
20
- readonly sentenceBefore: 'This website was designed and developed by';
21
- readonly sentenceAfter: ', a software company in Würzburg, Germany.';
22
- readonly eyebrow: 'Built by';
23
- };
24
- /** Default role list for the card; override per project with the `roles` prop. */
22
+ interface ImprintCopy {
23
+ heading: string;
24
+ /** "This website" / "This app" — the sentence's subject. */
25
+ subject: Record<ImprintSubject, string>;
26
+ /** Between the subject and the linked name "venqoo". */
27
+ sentenceBefore: string;
28
+ /** After the linked name, including the full stop. */
29
+ sentenceAfter: string;
30
+ /** Second sentence: what venqoo GmbH does. */
31
+ about: string;
32
+ eyebrow: string;
33
+ country: string;
34
+ roles: readonly ImprintRole[];
35
+ }
36
+ export declare const imprintCopy: Record<ImprintLang, ImprintCopy>;
37
+ /** Default role list for the card, per language; override per project with the `roles` prop. */
38
+ export declare const imprintRoles: Record<ImprintLang, readonly ImprintRole[]>;
39
+ /** The English default role list. */
25
40
  export declare const defaultRoles: readonly ImprintRole[];
41
+ export {};
@@ -11,15 +11,41 @@ export const footerCopy = {
11
11
  pillSignal: 'Made with love by',
12
12
  };
13
13
  export const imprintCopy = {
14
- heading: 'Concept, design & development',
15
- sentenceBefore: 'This website was designed and developed by',
16
- sentenceAfter: ', a software company in Würzburg, Germany.',
17
- eyebrow: 'Built by',
14
+ en: {
15
+ heading: 'Concept, design & development',
16
+ subject: { website: 'This website', app: 'This app' },
17
+ sentenceBefore: 'was designed and developed by',
18
+ sentenceAfter: ', a software agency in Würzburg, Germany.',
19
+ about: 'venqoo GmbH builds apps, websites and custom software and advises companies on AI strategy and AI integration.',
20
+ eyebrow: 'Built by',
21
+ country: 'Germany',
22
+ roles: [
23
+ { term: 'Concept', detail: 'UX & structure' },
24
+ { term: 'Design', detail: 'Visual concept' },
25
+ { term: 'Development', detail: 'Front end & back end' },
26
+ { term: 'Operations', detail: 'Hosting & maintenance' },
27
+ ],
28
+ },
29
+ de: {
30
+ heading: 'Konzept, Design & Entwicklung',
31
+ subject: { website: 'Diese Website', app: 'Diese App' },
32
+ sentenceBefore: 'wurde von',
33
+ sentenceAfter: ', einer Software-Agentur aus Würzburg, gestaltet und entwickelt.',
34
+ about: 'Die venqoo GmbH entwickelt Apps, Websites und individuelle Software und berät Unternehmen zu KI-Strategie und KI-Integration.',
35
+ eyebrow: 'Umgesetzt von',
36
+ country: 'Deutschland',
37
+ roles: [
38
+ { term: 'Konzept', detail: 'UX & Struktur' },
39
+ { term: 'Design', detail: 'Visuelles Konzept' },
40
+ { term: 'Entwicklung', detail: 'Frontend & Backend' },
41
+ { term: 'Betrieb', detail: 'Hosting & Wartung' },
42
+ ],
43
+ },
18
44
  };
19
- /** Default role list for the card; override per project with the `roles` prop. */
20
- export const defaultRoles = [
21
- { term: 'Concept', detail: 'UX & structure' },
22
- { term: 'Design', detail: 'Visual concept' },
23
- { term: 'Development', detail: 'Front end & back end' },
24
- { term: 'Operations', detail: 'Hosting & maintenance' },
25
- ];
45
+ /** Default role list for the card, per language; override per project with the `roles` prop. */
46
+ export const imprintRoles = {
47
+ en: imprintCopy.en.roles,
48
+ de: imprintCopy.de.roles,
49
+ };
50
+ /** The English default role list. */
51
+ export const defaultRoles = imprintRoles.en;
@@ -9,5 +9,9 @@ export type CreditVariant = 'heart' | 'mark' | 'pill' | 'pill-signal';
9
9
  export interface CreditProps extends CreditBaseProps {
10
10
  variant?: CreditVariant;
11
11
  }
12
- /** Footer credit: one link to venqoo in the style of your choice. */
13
- export declare function Credit({ variant, theme, className, href, newTab, }: CreditProps): import("react").JSX.Element;
12
+ /**
13
+ * Footer credit: one link to venqoo in the style of your choice. Pass `site` so
14
+ * visits can be attributed to the client site; the link is `nofollow` unless
15
+ * `rel` says otherwise.
16
+ */
17
+ export declare function Credit({ variant, theme, className, href, site, rel, newTab, }: CreditProps): import("react").JSX.Element;
@@ -4,9 +4,21 @@ import { Heart, UfoMark } from '../marks.js';
4
4
  import { VENQOO } from '../venqoo.js';
5
5
  import { footerCopy as t } from './copy.js';
6
6
  import { linkAttrs } from './link.js';
7
- /** Footer credit: one link to venqoo in the style of your choice. */
8
- export function Credit({ variant = 'heart', theme = 'light', className, href, newTab, }) {
9
- const a = linkAttrs({ href, newTab });
7
+ /**
8
+ * Footer credit: one link to venqoo in the style of your choice. Pass `site` so
9
+ * visits can be attributed to the client site; the link is `nofollow` unless
10
+ * `rel` says otherwise.
11
+ */
12
+ export function Credit({ variant = 'heart', theme = 'light', className, href, site, rel, newTab, }) {
13
+ const a = linkAttrs({
14
+ href,
15
+ site,
16
+ rel,
17
+ newTab,
18
+ placement: 'footer',
19
+ variant,
20
+ defaultRel: 'nofollow',
21
+ });
10
22
  const name = _jsx("span", { className: "vq-credit__name", children: VENQOO.name });
11
23
  let content;
12
24
  switch (variant) {
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
- import { type ImprintRole } from './copy.js';
2
+ import { type ImprintLang, type ImprintRole, type ImprintSubject } from './copy.js';
3
3
  import type { CreditBaseProps } from './types.js';
4
4
  /**
5
5
  * - `section` — heading, sentence and address; inherits the imprint's type (default)
@@ -10,13 +10,21 @@ export interface ImprintCreditProps extends CreditBaseProps {
10
10
  variant?: ImprintCreditVariant;
11
11
  /** Heading element for `section`; match the imprint's other section headings. */
12
12
  headingLevel?: 2 | 3 | 4;
13
+ /** Language of the copy and the address: English (default) or German. */
14
+ lang?: ImprintLang;
15
+ /** What the sentence on `section` says venqoo made: a `website` (default) or an `app`. */
16
+ subject?: ImprintSubject;
13
17
  /**
14
18
  * What venqoo was responsible for in this project, shown on `card`.
15
- * Defaults to concept, design, development and operations; pass `[]` to hide the list.
19
+ * Defaults to concept, design, development and operations in `lang`; pass `[]` to hide the list.
16
20
  */
17
21
  roles?: readonly ImprintRole[];
18
22
  /** Replaces the descriptive sentence on `section`. */
19
23
  children?: ReactNode;
20
24
  }
21
- /** Impressum / legal-notice credit naming venqoo GmbH as the maker of the site. */
22
- export declare function ImprintCredit({ variant, theme, headingLevel, roles, className, href, newTab, children, }: ImprintCreditProps): import("react").JSX.Element;
25
+ /**
26
+ * Impressum / legal-notice credit naming venqoo GmbH as the maker of the site or
27
+ * app. Pass `site` so visits can be attributed to the client site. Its links are
28
+ * followed unless `rel` says otherwise: one editorial sentence, not a template.
29
+ */
30
+ export declare function ImprintCredit({ variant, theme, headingLevel, lang, subject, roles, className, href, site, rel, newTab, children, }: ImprintCreditProps): import("react").JSX.Element;
@@ -2,16 +2,30 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import { cx } from '../cx.js';
3
3
  import { UfoMark } from '../marks.js';
4
4
  import { VENQOO } from '../venqoo.js';
5
- import { defaultRoles, imprintCopy as t } from './copy.js';
5
+ import { imprintCopy } from './copy.js';
6
6
  import { linkAttrs } from './link.js';
7
- /** Impressum / legal-notice credit naming venqoo GmbH as the maker of the site. */
8
- export function ImprintCredit({ variant = 'section', theme = 'light', headingLevel = 2, roles = defaultRoles, className, href, newTab, children, }) {
9
- const a = linkAttrs({ href, newTab });
7
+ /**
8
+ * Impressum / legal-notice credit naming venqoo GmbH as the maker of the site or
9
+ * app. Pass `site` so visits can be attributed to the client site. Its links are
10
+ * followed unless `rel` says otherwise: one editorial sentence, not a template.
11
+ */
12
+ export function ImprintCredit({ variant = 'section', theme = 'light', headingLevel = 2, lang = 'en', subject = 'website', roles, className, href, site, rel, newTab, children, }) {
13
+ const t = imprintCopy[lang];
14
+ const roleList = roles ?? t.roles;
15
+ const a = linkAttrs({
16
+ href,
17
+ site,
18
+ rel,
19
+ newTab,
20
+ placement: 'imprint',
21
+ variant,
22
+ defaultRel: 'follow',
23
+ });
10
24
  const rootClass = cx('vq-credit', `vq-credit--${variant}`, className);
11
25
  const Heading = `h${headingLevel}`;
12
26
  const siteLink = (_jsx("a", { className: "vq-credit__textlink", ...a, children: VENQOO.displayUrl }));
13
27
  if (variant === 'card') {
14
- return (_jsxs("section", { className: rootClass, "data-theme": theme, lang: "en", children: [_jsx("span", { className: "vq-credit__eyebrow", children: t.eyebrow }), _jsxs("a", { className: "vq-credit__link vq-credit__logo", ...a, children: [_jsx(UfoMark, { className: "vq-credit__mark" }), _jsx("span", { className: "vq-credit__wordmark", children: VENQOO.name })] }), roles.length > 0 && (_jsx("dl", { className: "vq-credit__roles", children: roles.map((role) => (_jsxs("div", { className: "vq-credit__role-item", children: [_jsx("dt", { children: role.term }), _jsx("dd", { children: role.detail })] }, role.term))) })), _jsxs("address", { className: "vq-credit__address", children: [_jsxs("span", { children: [VENQOO.legalName, " \u00B7 ", VENQOO.street, " \u00B7 ", VENQOO.postalCode, " ", VENQOO.city, " \u00B7", ' ', VENQOO.country] }), siteLink] })] }));
28
+ return (_jsxs("section", { className: rootClass, "data-theme": theme, lang: lang, children: [_jsx("span", { className: "vq-credit__eyebrow", children: t.eyebrow }), _jsxs("a", { className: "vq-credit__link vq-credit__logo", ...a, children: [_jsx(UfoMark, { className: "vq-credit__mark" }), _jsx("span", { className: "vq-credit__wordmark", children: VENQOO.name })] }), roleList.length > 0 && (_jsx("dl", { className: "vq-credit__roles", children: roleList.map((role) => (_jsxs("div", { className: "vq-credit__role-item", children: [_jsx("dt", { children: role.term }), _jsx("dd", { children: role.detail })] }, role.term))) })), _jsxs("address", { className: "vq-credit__address", children: [_jsxs("span", { children: [VENQOO.legalName, " \u00B7 ", VENQOO.street, " \u00B7 ", VENQOO.postalCode, " ", VENQOO.city, " \u00B7 ", t.country] }), siteLink] })] }));
15
29
  }
16
- return (_jsxs("section", { className: rootClass, "data-theme": theme, lang: "en", children: [_jsx(Heading, { className: "vq-credit__heading", children: t.heading }), _jsx("p", { className: "vq-credit__body", children: children ?? (_jsxs(_Fragment, { children: [t.sentenceBefore, ' ', _jsx("a", { className: "vq-credit__textlink", ...a, children: VENQOO.legalName }), t.sentenceAfter] })) }), _jsxs("address", { className: "vq-credit__address", children: [_jsx("span", { className: "vq-credit__address-name", children: VENQOO.legalName }), _jsx("span", { children: VENQOO.street }), _jsxs("span", { children: [VENQOO.postalCode, " ", VENQOO.city] }), _jsx("span", { children: VENQOO.country }), _jsx("a", { className: "vq-credit__textlink vq-credit__textlink--quiet vq-credit__address-gap", href: `mailto:${VENQOO.email}`, children: VENQOO.email }), siteLink] })] }));
30
+ return (_jsxs("section", { className: rootClass, "data-theme": theme, lang: lang, children: [_jsx(Heading, { className: "vq-credit__heading", children: t.heading }), _jsx("p", { className: "vq-credit__body", children: children ?? (_jsxs(_Fragment, { children: [t.subject[subject], " ", t.sentenceBefore, ' ', _jsx("a", { className: "vq-credit__textlink", ...a, children: VENQOO.name }), t.sentenceAfter, " ", t.about] })) }), _jsxs("address", { className: "vq-credit__address", children: [_jsx("span", { className: "vq-credit__address-name", children: VENQOO.legalName }), _jsx("span", { children: VENQOO.street }), _jsxs("span", { children: [VENQOO.postalCode, " ", VENQOO.city] }), _jsx("span", { children: t.country }), _jsx("a", { className: "vq-credit__textlink vq-credit__textlink--quiet vq-credit__address-gap", href: `mailto:${VENQOO.email}`, children: VENQOO.email }), siteLink] })] }));
17
31
  }
@@ -1,7 +1,9 @@
1
- export type { ImprintRole } from './copy.js';
2
- export { defaultRoles } from './copy.js';
1
+ export type { ImprintLang, ImprintRole, ImprintSubject } from './copy.js';
2
+ export { defaultRoles, imprintRoles } from './copy.js';
3
3
  export type { CreditProps, CreditVariant } from './credit.js';
4
4
  export { Credit } from './credit.js';
5
5
  export type { ImprintCreditProps, ImprintCreditVariant } from './imprint.js';
6
6
  export { ImprintCredit } from './imprint.js';
7
- export type { CreditBaseProps, CreditLinkProps, CreditTheme } from './types.js';
7
+ export type { CreditHrefOptions, CreditPlacement } from './link.js';
8
+ export { creditHref, normalizeSite } from './link.js';
9
+ export type { CreditBaseProps, CreditLinkProps, CreditRel, CreditTheme } from './types.js';
@@ -1,3 +1,4 @@
1
- export { defaultRoles } from './copy.js';
1
+ export { defaultRoles, imprintRoles } from './copy.js';
2
2
  export { Credit } from './credit.js';
3
3
  export { ImprintCredit } from './imprint.js';
4
+ export { creditHref, normalizeSite } from './link.js';
@@ -1,3 +1,28 @@
1
1
  import type { AnchorHTMLAttributes } from 'react';
2
- import type { CreditLinkProps } from './types.js';
3
- export declare function linkAttrs({ href, newTab, }: CreditLinkProps): Pick<AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'target' | 'rel'>;
2
+ import type { CreditLinkProps, CreditRel } from './types.js';
3
+ /** Where on the client site the credit sits; becomes `utm_campaign`. */
4
+ export type CreditPlacement = 'footer' | 'imprint';
5
+ export interface CreditHrefOptions {
6
+ /** Base URL; defaults to https://www.venqoo.com. */
7
+ href?: string;
8
+ /** The client site, e.g. "baeckerei-muster.de". Without it the base URL is returned as is. */
9
+ site?: string;
10
+ placement: CreditPlacement;
11
+ /** The component variant, e.g. "heart"; becomes `utm_content`. */
12
+ variant: string;
13
+ }
14
+ /** "https://www.Baeckerei-Muster.de/impressum" → "baeckerei-muster.de" */
15
+ export declare function normalizeSite(site: string): string;
16
+ /**
17
+ * The credit's URL with campaign parameters when a `site` is given, so a visit
18
+ * can be attributed to the client site, the placement and the variant. Existing
19
+ * query parameters and fragments on `href` are kept.
20
+ */
21
+ export declare function creditHref({ href, site, placement, variant, }: CreditHrefOptions): string;
22
+ export interface CreditLinkOptions extends CreditLinkProps {
23
+ placement: CreditPlacement;
24
+ variant: string;
25
+ /** Qualification used when the `rel` prop is not given. */
26
+ defaultRel: CreditRel;
27
+ }
28
+ export declare function linkAttrs({ href, site, rel, newTab, placement, variant, defaultRel, }: CreditLinkOptions): Pick<AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'target' | 'rel'>;
@@ -1,5 +1,42 @@
1
1
  import { DEFAULT_HREF } from './copy.js';
2
- export function linkAttrs({ href = DEFAULT_HREF, newTab = true, }) {
3
- // No `noreferrer`: the Referer header is how venqoo sees which client site sent a visitor.
4
- return newTab ? { href, target: '_blank', rel: 'noopener' } : { href };
2
+ /** "https://www.Baeckerei-Muster.de/impressum" → "baeckerei-muster.de" */
3
+ export function normalizeSite(site) {
4
+ return site
5
+ .trim()
6
+ .toLowerCase()
7
+ .replace(/^[a-z][a-z0-9+.-]*:\/\//, '')
8
+ .replace(/^www\./, '')
9
+ .replace(/[/?#].*$/, '');
10
+ }
11
+ /**
12
+ * The credit's URL with campaign parameters when a `site` is given, so a visit
13
+ * can be attributed to the client site, the placement and the variant. Existing
14
+ * query parameters and fragments on `href` are kept.
15
+ */
16
+ export function creditHref({ href = DEFAULT_HREF, site, placement, variant, }) {
17
+ const source = site ? normalizeSite(site) : '';
18
+ if (!source)
19
+ return href;
20
+ const params = new URLSearchParams({
21
+ utm_source: source,
22
+ utm_medium: 'credit',
23
+ utm_campaign: placement,
24
+ utm_content: variant,
25
+ });
26
+ const hashAt = href.indexOf('#');
27
+ const base = hashAt === -1 ? href : href.slice(0, hashAt);
28
+ const hash = hashAt === -1 ? '' : href.slice(hashAt);
29
+ const separator = base.includes('?') ? '&' : '?';
30
+ return `${base}${separator}${params}${hash}`;
31
+ }
32
+ export function linkAttrs({ href, site, rel, newTab = true, placement, variant, defaultRel, }) {
33
+ const qualifier = rel ?? defaultRel;
34
+ // No `noreferrer`: besides the campaign parameters, the Referer header is how
35
+ // venqoo sees which client site sent a visitor.
36
+ const rels = [...(newTab ? ['noopener'] : []), ...(qualifier === 'follow' ? [] : [qualifier])];
37
+ return {
38
+ href: creditHref({ href, site, placement, variant }),
39
+ ...(newTab ? { target: '_blank' } : {}),
40
+ ...(rels.length > 0 ? { rel: rels.join(' ') } : {}),
41
+ };
5
42
  }
@@ -4,9 +4,30 @@
4
4
  * colour-scheme toggle — the component cannot see what it sits on.
5
5
  */
6
6
  export type CreditTheme = 'light' | 'dark' | 'auto';
7
+ /**
8
+ * How the link is qualified for search engines. `nofollow` and `sponsored` tell
9
+ * crawlers not to treat the link as an endorsement of venqoo; `follow` leaves it
10
+ * unqualified. `noopener` is added on top whenever the link opens in a new tab.
11
+ *
12
+ * The footer credit defaults to `nofollow`: it is the same link in the template
13
+ * of many sites, which Google asks to be qualified. The imprint credit defaults
14
+ * to `follow`: a single sentence naming the maker of the site. Use `sponsored`
15
+ * when the credit is part of a deal, e.g. a discount for keeping it.
16
+ */
17
+ export type CreditRel = 'follow' | 'nofollow' | 'sponsored';
7
18
  export interface CreditLinkProps {
8
19
  /** Where the credit links to. Defaults to https://www.venqoo.com. */
9
20
  href?: string;
21
+ /**
22
+ * The client site the credit sits on, e.g. `"baeckerei-muster.de"`. When set,
23
+ * the link carries campaign parameters (`utm_source` = site, `utm_medium` =
24
+ * credit, `utm_campaign` = footer or imprint, `utm_content` = variant), so
25
+ * venqoo's analytics can tell which site, placement and design a visitor came
26
+ * from. Nothing runs or is tracked on the client site itself.
27
+ */
28
+ site?: string;
29
+ /** Search-engine qualification of the link; see {@link CreditRel} for the defaults. */
30
+ rel?: CreditRel;
10
31
  /** Open the link in a new tab (default) so the visitor stays on the client site. */
11
32
  newTab?: boolean;
12
33
  }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type { ColorName, ColorScheme } from './colors.js';
2
2
  export { colors } from './colors.js';
3
- export type { CreditBaseProps, CreditLinkProps, CreditProps, CreditTheme, CreditVariant, ImprintCreditProps, ImprintCreditVariant, ImprintRole, } from './credit/index.js';
4
- export { Credit, defaultRoles, ImprintCredit } from './credit/index.js';
3
+ export type { CreditBaseProps, CreditHrefOptions, CreditLinkProps, CreditPlacement, CreditProps, CreditRel, CreditTheme, CreditVariant, ImprintCreditProps, ImprintCreditVariant, ImprintLang, ImprintRole, ImprintSubject, } from './credit/index.js';
4
+ export { Credit, creditHref, defaultRoles, ImprintCredit, imprintRoles, normalizeSite, } from './credit/index.js';
5
5
  export { fonts } from './fonts.js';
6
6
  export { Heart, Logo, UfoMark } from './marks.js';
7
7
  export { VENQOO } from './venqoo.js';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { colors } from './colors.js';
2
- export { Credit, defaultRoles, ImprintCredit } from './credit/index.js';
2
+ export { Credit, creditHref, defaultRoles, ImprintCredit, imprintRoles, normalizeSite, } from './credit/index.js';
3
3
  export { fonts } from './fonts.js';
4
4
  export { Heart, Logo, UfoMark } from './marks.js';
5
5
  export { VENQOO } from './venqoo.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@venqoo/brand",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "venqoo brand kit: palette, type contract, marks and SVG assets, plus \"Made by venqoo\" credit components for sites and apps built by venqoo.",
5
5
  "license": "MIT",
6
6
  "author": "venqoo GmbH (https://www.venqoo.com)",