react-cheminfo 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.
Files changed (38) hide show
  1. package/README.md +3 -3
  2. package/lib/citation/core/formats.js +2 -3
  3. package/lib/citation/core/formats.js.map +1 -1
  4. package/lib/citation/core/segments.d.ts +8 -0
  5. package/lib/citation/core/segments.d.ts.map +1 -1
  6. package/lib/citation/core/segments.js +12 -3
  7. package/lib/citation/core/segments.js.map +1 -1
  8. package/lib/ecosystem/core/sites.d.ts +1 -1
  9. package/lib/ecosystem/core/sites.d.ts.map +1 -1
  10. package/lib/ecosystem/core/sites.js +27 -0
  11. package/lib/ecosystem/core/sites.js.map +1 -1
  12. package/lib/ecosystem/ui/EcosystemLinks.d.ts +33 -0
  13. package/lib/ecosystem/ui/EcosystemLinks.d.ts.map +1 -0
  14. package/lib/ecosystem/ui/EcosystemLinks.js +55 -0
  15. package/lib/ecosystem/ui/EcosystemLinks.js.map +1 -0
  16. package/lib/ecosystem/ui/EcosystemMenu.d.ts +3 -1
  17. package/lib/ecosystem/ui/EcosystemMenu.d.ts.map +1 -1
  18. package/lib/ecosystem/ui/EcosystemMenu.js +7 -84
  19. package/lib/ecosystem/ui/EcosystemMenu.js.map +1 -1
  20. package/lib/ecosystem/ui/SiteTile.d.ts +34 -0
  21. package/lib/ecosystem/ui/SiteTile.d.ts.map +1 -0
  22. package/lib/ecosystem/ui/SiteTile.js +78 -0
  23. package/lib/ecosystem/ui/SiteTile.js.map +1 -0
  24. package/lib/ecosystem/ui/index.d.ts +4 -0
  25. package/lib/ecosystem/ui/index.d.ts.map +1 -1
  26. package/lib/ecosystem/ui/index.js +2 -0
  27. package/lib/ecosystem/ui/index.js.map +1 -1
  28. package/lib/ecosystem/ui/marks.js +7 -1
  29. package/lib/ecosystem/ui/marks.js.map +1 -1
  30. package/package.json +4 -4
  31. package/src/citation/core/formats.ts +2 -3
  32. package/src/citation/core/segments.ts +11 -3
  33. package/src/ecosystem/core/sites.ts +31 -1
  34. package/src/ecosystem/ui/EcosystemLinks.tsx +114 -0
  35. package/src/ecosystem/ui/EcosystemMenu.tsx +8 -135
  36. package/src/ecosystem/ui/SiteTile.tsx +148 -0
  37. package/src/ecosystem/ui/index.ts +4 -0
  38. package/src/ecosystem/ui/marks.tsx +44 -1
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  // Every mark is drawn in the same 32×32 box, on a plate of the site's own
3
- // colour, so ten marks from ten sites still read as one row. Each keeps the
3
+ // colour, so every mark of the family still reads as one row. Each keeps the
4
4
  // geometry of that site's own logo where it has one, and carries the site's
5
5
  // answering colour on exactly one element — which is what stops it collapsing
6
6
  // into a flat shape at 16 px.
@@ -39,5 +39,11 @@ const GLYPHS = {
39
39
  regexp: (alt) => (_jsx("text", { x: "50%", y: "53%", dominantBaseline: "central", textAnchor: "middle", fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: "18", fontWeight: "700", fill: alt, children: ".*" })),
40
40
  // Three residues of a chain, one of them the ligand you came for.
41
41
  pdb: (alt) => (_jsxs(_Fragment, { children: [_jsx("g", { stroke: "#ffffff", strokeWidth: "1.9", strokeLinecap: "round", fill: "none", opacity: "0.85", children: _jsx("path", { d: "M9.2 12.4 22 15.4 15.6 23Z" }) }), _jsx("circle", { cx: "9.2", cy: "12.4", r: "3.1", fill: "#ffffff" }), _jsx("circle", { cx: "22", cy: "15.4", r: "3.1", fill: "#ffffff" }), _jsx("circle", { cx: "15.6", cy: "23", r: "3.1", fill: alt })] })),
42
+ // The site's own favicon: a spectrum, and the one signal being assigned.
43
+ elucidation: (alt) => (_jsxs(_Fragment, { children: [_jsx("path", { d: "M5 24h4l2-11 2 11h4l2-15 2 15h3l1.5-7 1.5 7", fill: "none", stroke: "#ffffff", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("circle", { cx: "19", cy: "9", r: "3", fill: alt })] })),
44
+ // The site's own favicon: the double harpoon, one direction per colour.
45
+ equilibrium: (alt) => (_jsxs("g", { fill: "none", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M6.5 12.5h17l-4-4", stroke: "#ffffff" }), _jsx("path", { d: "M25.5 19.5h-17l4 4", stroke: alt })] })),
46
+ // A chain of monomers, one of them the comonomer written into it.
47
+ polycarp: (alt) => (_jsxs(_Fragment, { children: [_jsx("path", { d: "M6.5 20 12.5 12 19.5 20 25.5 12", fill: "none", stroke: "#ffffff", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("circle", { cx: "6.5", cy: "20", r: "3", fill: "#ffffff" }), _jsx("circle", { cx: "12.5", cy: "12", r: "3", fill: "#ffffff" }), _jsx("circle", { cx: "19.5", cy: "20", r: "3", fill: alt }), _jsx("circle", { cx: "25.5", cy: "12", r: "3", fill: "#ffffff" })] })),
42
48
  };
43
49
  //# sourceMappingURL=marks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"marks.js","sourceRoot":"","sources":["../../../src/ecosystem/ui/marks.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"marks.js","sourceRoot":"","sources":["../../../src/ecosystem/ui/marks.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-cheminfo",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "description": "Assembled React components shared by the cheminfo sites: a Cite button and an ecosystem menu",
6
6
  "author": "Luc Patiny",
@@ -64,9 +64,9 @@
64
64
  },
65
65
  "peerDependencies": {
66
66
  "@blueprintjs/core": "^6.18.0",
67
- "react": ">=19",
68
- "react-dom": ">=19",
69
- "react-science": "^21.0.0 || ^22.0.0 || ^23.0.0"
67
+ "react": "^18.3.1 || >=19",
68
+ "react-dom": "^18.3.1 || >=19",
69
+ "react-science": "^20.6.2 || ^21.0.0 || ^22.0.0 || ^23.0.0"
70
70
  },
71
71
  "repository": {
72
72
  "type": "git",
@@ -2,7 +2,7 @@ import type { Reference } from './reference.ts';
2
2
  import { doiUrl } from './reference.ts';
3
3
  import { renderHtml, renderMarkdown, renderText } from './render.ts';
4
4
  import type { CitationStyleId } from './segments.ts';
5
- import { citationSegments } from './segments.ts';
5
+ import { citationSegments, pageRange } from './segments.ts';
6
6
 
7
7
  /** The formats the reference can be copied in. */
8
8
  export type CitationFormatId =
@@ -113,7 +113,6 @@ export function formatCitation(
113
113
 
114
114
  function formatBibTeX(reference: Reference): string {
115
115
  const { title, journal, year, volume, issue, publisher, doi } = reference;
116
- const { firstPage, lastPage } = reference;
117
116
  const fields: Array<[string, string]> = [
118
117
  ['author', authorList(reference, ' and ')],
119
118
  ['title', title],
@@ -121,7 +120,7 @@ function formatBibTeX(reference: Reference): string {
121
120
  ['year', String(year)],
122
121
  ['volume', volume],
123
122
  ['number', issue],
124
- ['pages', `${firstPage}--${lastPage}`],
123
+ ['pages', pageRange(reference, '--')],
125
124
  ['publisher', publisher],
126
125
  ['doi', doi],
127
126
  ['url', doiUrl(reference)],
@@ -164,7 +164,15 @@ function initialsFirst(reference: Reference, lastSeparator: string): string {
164
164
  return `${names.slice(0, -1).join(', ')}${lastSeparator}${last}`;
165
165
  }
166
166
 
167
- function pageRange(reference: Reference): string {
168
- // En dash: what every style asks for in a page range.
169
- return `${reference.firstPage}–${reference.lastPage}`;
167
+ /**
168
+ * The pages of a work, as its styles write them.
169
+ * @param reference - The work being written.
170
+ * @param dash - What joins the two ends, an en dash in every style but BibTeX.
171
+ * @returns One number for a journal that paginates by article number, the
172
+ * range otherwise.
173
+ */
174
+ export function pageRange(reference: Reference, dash = '–'): string {
175
+ const { firstPage, lastPage } = reference;
176
+ if (firstPage === lastPage) return firstPage;
177
+ return `${firstPage}${dash}${lastPage}`;
170
178
  }
@@ -9,7 +9,10 @@ export type SiteId =
9
9
  | 'tex'
10
10
  | 'lcao'
11
11
  | 'regexp'
12
- | 'pdb';
12
+ | 'pdb'
13
+ | 'elucidation'
14
+ | 'equilibrium'
15
+ | 'polycarp';
13
16
 
14
17
  export interface SiteName {
15
18
  /** First half of the name, set in the site's leading colour. */
@@ -152,6 +155,33 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
152
155
  brandAlt: '#b45309',
153
156
  mark: { plate: '#2563eb', accent: '#fbbf24' },
154
157
  },
158
+ {
159
+ id: 'elucidation',
160
+ name: { lead: 'elucidation', alt: 'cheminfo', dot: true },
161
+ host: 'elucidation.cheminfo.org',
162
+ tagline: 'A structure from a 1H NMR spectrum and a formula.',
163
+ brand: '#7e22ce',
164
+ brandAlt: '#b45309',
165
+ mark: { plate: '#7e22ce', accent: '#f59e0b' },
166
+ },
167
+ {
168
+ id: 'equilibrium',
169
+ name: { lead: 'Equi', alt: 'Librium' },
170
+ host: 'equilibrium.cheminfo.org',
171
+ tagline: 'Chemical equilibria: pH, speciation and titration curves.',
172
+ brand: '#0b5754',
173
+ brandAlt: '#a56600',
174
+ mark: { plate: '#0b5754', accent: '#f2a71b' },
175
+ },
176
+ {
177
+ id: 'polycarp',
178
+ name: { lead: 'Poly', alt: 'Carp' },
179
+ host: 'polycarp.cheminfo.org',
180
+ tagline: 'Predict the microstructure of a radical copolymerisation.',
181
+ brand: '#0d1d37',
182
+ brandAlt: '#0c9ba3',
183
+ mark: { plate: '#0d1d37', accent: '#0c9ba3' },
184
+ },
155
185
  ];
156
186
 
157
187
  /**
@@ -0,0 +1,114 @@
1
+ import type { CSSProperties, ReactElement } from 'react';
2
+
3
+ import type { SiteId } from '../core/sites.ts';
4
+ import { ECOSYSTEM_SITES, siteUrl } from '../core/sites.ts';
5
+
6
+ import { SiteTile } from './SiteTile.tsx';
7
+
8
+ // The menu of `EcosystemMenu` lives inside a popover, so its links only enter
9
+ // the document once somebody clicks. A crawler never clicks, so these are the
10
+ // links that carry the family: always rendered, in the page, followed — and
11
+ // each one carrying the name and the one line that says what it opens, as real
12
+ // text rather than a `title` a crawler ignores.
13
+ const SECTION_STYLE: CSSProperties = {
14
+ display: 'flex',
15
+ flexDirection: 'column',
16
+ gap: 8,
17
+ };
18
+ const HEADING_STYLE: CSSProperties = {
19
+ margin: 0,
20
+ color: '#5b6875',
21
+ fontSize: '0.75rem',
22
+ fontWeight: 600,
23
+ };
24
+ const GRID_STYLE: CSSProperties = {
25
+ display: 'grid',
26
+ gridTemplateColumns: 'repeat(auto-fit, minmax(15rem, 1fr))',
27
+ gap: 2,
28
+ };
29
+ const ROW_STYLE: CSSProperties = {
30
+ display: 'flex',
31
+ flexWrap: 'wrap',
32
+ alignItems: 'baseline',
33
+ gap: '0.35rem 0.9rem',
34
+ };
35
+ const LINK_STYLE: CSSProperties = {
36
+ fontSize: '0.8125rem',
37
+ fontWeight: 600,
38
+ textDecoration: 'none',
39
+ whiteSpace: 'nowrap',
40
+ };
41
+ const CURRENT_STYLE: CSSProperties = {
42
+ ...LINK_STYLE,
43
+ color: '#8a96a3',
44
+ cursor: 'default',
45
+ };
46
+
47
+ export interface EcosystemLinksProps {
48
+ /**
49
+ * The site the visitor is already on, which is written but never linked.
50
+ * @default undefined
51
+ */
52
+ currentSiteId?: SiteId;
53
+ /**
54
+ * What introduces the section.
55
+ * @default 'Our other tools'
56
+ */
57
+ heading?: string;
58
+ /**
59
+ * How much of each site is written. `grid` gives every site its mark, its
60
+ * name and the line saying what it does — which is what a crawler reads to
61
+ * know what it is following, so it is the default. `row` writes the names
62
+ * only, for a footer with no room.
63
+ * @default 'grid'
64
+ */
65
+ layout?: 'grid' | 'row';
66
+ }
67
+
68
+ /**
69
+ * Every site of the family as a plain link, for the footer of a site. It is
70
+ * what lets a crawler — and a visitor with no patience for menus — walk from
71
+ * one of our tools to the next, so it is rendered on every page rather than
72
+ * behind a button.
73
+ * @param props - The site it sits on, what introduces the section, and how much
74
+ * of each site is written.
75
+ * @returns The section of links.
76
+ */
77
+ export function EcosystemLinks(props: EcosystemLinksProps): ReactElement {
78
+ const { currentSiteId, heading = 'Our other tools', layout = 'grid' } = props;
79
+
80
+ return (
81
+ <nav className="ecosystem-links" style={SECTION_STYLE} aria-label={heading}>
82
+ <h2 style={HEADING_STYLE}>{heading}</h2>
83
+ {layout === 'grid' ? (
84
+ <div style={GRID_STYLE}>
85
+ {ECOSYSTEM_SITES.map((site) => (
86
+ <SiteTile
87
+ key={site.id}
88
+ site={site}
89
+ isCurrent={site.id === currentSiteId}
90
+ />
91
+ ))}
92
+ </div>
93
+ ) : (
94
+ <div style={ROW_STYLE}>
95
+ {ECOSYSTEM_SITES.map((site) =>
96
+ site.id === currentSiteId ? (
97
+ <span key={site.id} style={CURRENT_STYLE}>
98
+ {site.host}
99
+ </span>
100
+ ) : (
101
+ <a
102
+ key={site.id}
103
+ style={{ ...LINK_STYLE, color: site.brand }}
104
+ href={siteUrl(site)}
105
+ >
106
+ {site.host}
107
+ </a>
108
+ ),
109
+ )}
110
+ </div>
111
+ )}
112
+ </nav>
113
+ );
114
+ }
@@ -1,10 +1,10 @@
1
1
  import type { CSSProperties, ReactElement } from 'react';
2
2
  import { useState } from 'react';
3
3
 
4
- import type { EcosystemSite, SiteId } from '../core/sites.ts';
5
- import { ECOSYSTEM_SITES, siteUrl } from '../core/sites.ts';
4
+ import type { SiteId } from '../core/sites.ts';
5
+ import { ECOSYSTEM_SITES } from '../core/sites.ts';
6
6
 
7
- import { SiteMark } from './marks.tsx';
7
+ import { SiteTile } from './SiteTile.tsx';
8
8
 
9
9
  const PANEL_STYLE: CSSProperties = {
10
10
  display: 'flex',
@@ -23,42 +23,7 @@ const GRID_STYLE: CSSProperties = {
23
23
  gridTemplateColumns: 'repeat(auto-fit, minmax(15rem, 1fr))',
24
24
  gap: 2,
25
25
  };
26
- const TILE_STYLE: CSSProperties = {
27
- display: 'flex',
28
- alignItems: 'center',
29
- padding: '7px 8px',
30
- border: '1px solid transparent',
31
- borderRadius: 10,
32
- color: '#16202c',
33
- gap: 10,
34
- textDecoration: 'none',
35
- transition: 'background 120ms, border-color 120ms, transform 120ms',
36
- };
37
- const MARK_HOLDER_STYLE: CSSProperties = {
38
- display: 'flex',
39
- transition: 'transform 160ms',
40
- };
41
- const TEXT_STYLE: CSSProperties = { minWidth: 0 };
42
- const NAME_STYLE: CSSProperties = {
43
- display: 'flex',
44
- alignItems: 'baseline',
45
- fontSize: '0.9375rem',
46
- fontWeight: 700,
47
- letterSpacing: '-0.01em',
48
- gap: 6,
49
- whiteSpace: 'nowrap',
50
- };
51
- const HERE_STYLE: CSSProperties = {
52
- fontSize: '0.625rem',
53
- fontWeight: 700,
54
- letterSpacing: '0.04em',
55
- textTransform: 'uppercase',
56
- };
57
- const TAGLINE_STYLE: CSSProperties = {
58
- color: '#5b6875',
59
- fontSize: '0.75rem',
60
- lineHeight: 1.35,
61
- };
26
+
62
27
  export interface EcosystemMenuProps {
63
28
  /**
64
29
  * The site the visitor is already on, which is shown but never linked.
@@ -69,7 +34,9 @@ export interface EcosystemMenuProps {
69
34
 
70
35
  /**
71
36
  * What the ecosystem button opens: every site of the family, each behind its
72
- * own little logo and the two colours it owns.
37
+ * own little logo and the two colours it owns. It lives in a popover, so a
38
+ * crawler never reaches it — `EcosystemLinks` is what carries the family
39
+ * through the page itself.
73
40
  * @param props - The site the visitor is already on.
74
41
  * @returns The grid of sites.
75
42
  */
@@ -88,104 +55,10 @@ export function EcosystemMenu(props: EcosystemMenuProps): ReactElement {
88
55
  isCurrent={site.id === currentSiteId}
89
56
  isHovered={hovered === site.id}
90
57
  onHover={setHovered}
58
+ newTab
91
59
  />
92
60
  ))}
93
61
  </div>
94
62
  </div>
95
63
  );
96
64
  }
97
-
98
- interface SiteTileProps {
99
- site: EcosystemSite;
100
- isCurrent: boolean;
101
- isHovered: boolean;
102
- onHover: (id: SiteId | null) => void;
103
- }
104
-
105
- /**
106
- * One site: its mark, its name in its own colours, and what it does.
107
- * @param props - The site, and whether it is the current or the hovered one.
108
- * @returns The tile.
109
- */
110
- function SiteTile(props: SiteTileProps): ReactElement {
111
- const { site, isCurrent, isHovered, onHover } = props;
112
- const lit = isHovered && !isCurrent;
113
-
114
- const body = (
115
- <>
116
- <div
117
- style={{
118
- ...MARK_HOLDER_STYLE,
119
- transform: lit ? 'scale(1.08) rotate(-6deg)' : 'none',
120
- }}
121
- >
122
- <SiteMark site={site} />
123
- </div>
124
- <div style={TEXT_STYLE}>
125
- <div style={NAME_STYLE}>
126
- <span>
127
- <span style={{ color: site.brand }}>{site.name.lead}</span>
128
- {site.name.dot ? <span style={{ color: '#8a96a3' }}>.</span> : null}
129
- <span style={{ color: site.brandAlt }}>{site.name.alt}</span>
130
- </span>
131
- {isCurrent ? (
132
- <span style={{ ...HERE_STYLE, color: site.brandAlt }}>
133
- you are here
134
- </span>
135
- ) : null}
136
- </div>
137
- <div style={TAGLINE_STYLE}>{site.tagline}</div>
138
- </div>
139
- </>
140
- );
141
-
142
- const style: CSSProperties = {
143
- ...TILE_STYLE,
144
- background: tint(site, isCurrent, lit),
145
- borderColor: borderOf(site, isCurrent, lit),
146
- transform: lit ? 'translateY(-1px)' : 'none',
147
- cursor: isCurrent ? 'default' : 'pointer',
148
- };
149
-
150
- if (isCurrent) return <div style={style}>{body}</div>;
151
-
152
- return (
153
- <a
154
- style={style}
155
- href={siteUrl(site)}
156
- title={site.host}
157
- target="_blank"
158
- rel="noreferrer"
159
- onMouseEnter={() => {
160
- onHover(site.id);
161
- }}
162
- onMouseLeave={() => {
163
- onHover(null);
164
- }}
165
- onFocus={() => {
166
- onHover(site.id);
167
- }}
168
- onBlur={() => {
169
- onHover(null);
170
- }}
171
- >
172
- {body}
173
- </a>
174
- );
175
- }
176
-
177
- // A tile lights up in the colour of the site it opens, never in ours: the
178
- // pointer moving down the grid is what makes the ten pairs of colours read.
179
- function tint(site: EcosystemSite, isCurrent: boolean, lit: boolean): string {
180
- if (isCurrent) return '#f5f7fa';
181
- return lit ? `color-mix(in oklab, ${site.brand} 9%, white)` : 'transparent';
182
- }
183
-
184
- function borderOf(
185
- site: EcosystemSite,
186
- isCurrent: boolean,
187
- lit: boolean,
188
- ): string {
189
- if (isCurrent) return '#dfe3e8';
190
- return lit ? `color-mix(in oklab, ${site.brand} 32%, white)` : 'transparent';
191
- }
@@ -0,0 +1,148 @@
1
+ import type { CSSProperties, ReactElement } from 'react';
2
+
3
+ import type { EcosystemSite, SiteId } from '../core/sites.ts';
4
+ import { siteUrl } from '../core/sites.ts';
5
+
6
+ import { SiteMark } from './marks.tsx';
7
+
8
+ const TILE_STYLE: CSSProperties = {
9
+ display: 'flex',
10
+ alignItems: 'center',
11
+ padding: '7px 8px',
12
+ border: '1px solid transparent',
13
+ borderRadius: 10,
14
+ color: '#16202c',
15
+ gap: 10,
16
+ textDecoration: 'none',
17
+ transition: 'background 120ms, border-color 120ms, transform 120ms',
18
+ };
19
+ const MARK_HOLDER_STYLE: CSSProperties = {
20
+ display: 'flex',
21
+ transition: 'transform 160ms',
22
+ };
23
+ const TEXT_STYLE: CSSProperties = { minWidth: 0 };
24
+ const NAME_STYLE: CSSProperties = {
25
+ display: 'flex',
26
+ alignItems: 'baseline',
27
+ fontSize: '0.9375rem',
28
+ fontWeight: 700,
29
+ letterSpacing: '-0.01em',
30
+ gap: 6,
31
+ whiteSpace: 'nowrap',
32
+ };
33
+ const HERE_STYLE: CSSProperties = {
34
+ fontSize: '0.625rem',
35
+ fontWeight: 700,
36
+ letterSpacing: '0.04em',
37
+ textTransform: 'uppercase',
38
+ };
39
+ const TAGLINE_STYLE: CSSProperties = {
40
+ color: '#5b6875',
41
+ fontSize: '0.75rem',
42
+ lineHeight: 1.35,
43
+ };
44
+
45
+ export interface SiteTileProps {
46
+ /** The site the tile opens. */
47
+ site: EcosystemSite;
48
+ /** Whether this is the site the visitor is already on. */
49
+ isCurrent: boolean;
50
+ /**
51
+ * Whether the pointer is on it, which lights it in its own colour.
52
+ * @default false
53
+ */
54
+ isHovered?: boolean;
55
+ /**
56
+ * Told which site the pointer moved onto, or null when it left.
57
+ * @default undefined
58
+ */
59
+ onHover?: (id: SiteId | null) => void;
60
+ /**
61
+ * Whether the link opens a tab of its own. A menu the visitor opened on
62
+ * purpose does; a footer does not.
63
+ * @default false
64
+ */
65
+ newTab?: boolean;
66
+ }
67
+
68
+ /**
69
+ * One site: its mark, its name in its own colours, and what it does. The name
70
+ * and the tagline are real text — that is what a crawler reads to know what it
71
+ * is following, so neither is ever a `title` attribute.
72
+ * @param props - The site, whether it is the current or the hovered one, and
73
+ * how the link opens.
74
+ * @returns The tile.
75
+ */
76
+ export function SiteTile(props: SiteTileProps): ReactElement {
77
+ const { site, isCurrent, isHovered = false, onHover, newTab = false } = props;
78
+ const lit = isHovered && !isCurrent;
79
+
80
+ const body = (
81
+ <>
82
+ <div
83
+ style={{
84
+ ...MARK_HOLDER_STYLE,
85
+ transform: lit ? 'scale(1.08) rotate(-6deg)' : 'none',
86
+ }}
87
+ >
88
+ <SiteMark site={site} />
89
+ </div>
90
+ <div style={TEXT_STYLE}>
91
+ <div style={NAME_STYLE}>
92
+ <span>
93
+ <span style={{ color: site.brand }}>{site.name.lead}</span>
94
+ {site.name.dot ? <span style={{ color: '#8a96a3' }}>.</span> : null}
95
+ <span style={{ color: site.brandAlt }}>{site.name.alt}</span>
96
+ </span>
97
+ {isCurrent ? (
98
+ <span style={{ ...HERE_STYLE, color: site.brandAlt }}>
99
+ you are here
100
+ </span>
101
+ ) : null}
102
+ </div>
103
+ <div style={TAGLINE_STYLE}>{site.tagline}</div>
104
+ </div>
105
+ </>
106
+ );
107
+
108
+ const style: CSSProperties = {
109
+ ...TILE_STYLE,
110
+ background: tint(site, isCurrent, lit),
111
+ borderColor: borderOf(site, isCurrent, lit),
112
+ transform: lit ? 'translateY(-1px)' : 'none',
113
+ cursor: isCurrent ? 'default' : 'pointer',
114
+ };
115
+
116
+ if (isCurrent) return <div style={style}>{body}</div>;
117
+
118
+ return (
119
+ <a
120
+ style={style}
121
+ href={siteUrl(site)}
122
+ target={newTab ? '_blank' : undefined}
123
+ rel={newTab ? 'noreferrer' : undefined}
124
+ onMouseEnter={() => onHover?.(site.id)}
125
+ onMouseLeave={() => onHover?.(null)}
126
+ onFocus={() => onHover?.(site.id)}
127
+ onBlur={() => onHover?.(null)}
128
+ >
129
+ {body}
130
+ </a>
131
+ );
132
+ }
133
+
134
+ // A tile lights up in the colour of the site it opens, never in ours: the
135
+ // pointer moving down the grid is what makes the pairs of colours read.
136
+ function tint(site: EcosystemSite, isCurrent: boolean, lit: boolean): string {
137
+ if (isCurrent) return '#f5f7fa';
138
+ return lit ? `color-mix(in oklab, ${site.brand} 9%, white)` : 'transparent';
139
+ }
140
+
141
+ function borderOf(
142
+ site: EcosystemSite,
143
+ isCurrent: boolean,
144
+ lit: boolean,
145
+ ): string {
146
+ if (isCurrent) return '#dfe3e8';
147
+ return lit ? `color-mix(in oklab, ${site.brand} 32%, white)` : 'transparent';
148
+ }
@@ -1,6 +1,10 @@
1
1
  export type { EcosystemButtonProps } from './EcosystemButton.tsx';
2
2
  export { EcosystemButton } from './EcosystemButton.tsx';
3
+ export type { EcosystemLinksProps } from './EcosystemLinks.tsx';
4
+ export { EcosystemLinks } from './EcosystemLinks.tsx';
3
5
  export type { EcosystemMenuProps } from './EcosystemMenu.tsx';
4
6
  export { EcosystemMenu } from './EcosystemMenu.tsx';
5
7
  export type { SiteMarkProps } from './marks.tsx';
6
8
  export { SiteMark } from './marks.tsx';
9
+ export type { SiteTileProps } from './SiteTile.tsx';
10
+ export { SiteTile } from './SiteTile.tsx';
@@ -3,7 +3,7 @@ import type { CSSProperties, ReactElement, ReactNode } from 'react';
3
3
  import type { EcosystemSite, SiteId } from '../core/sites.ts';
4
4
 
5
5
  // Every mark is drawn in the same 32×32 box, on a plate of the site's own
6
- // colour, so ten marks from ten sites still read as one row. Each keeps the
6
+ // colour, so every mark of the family still reads as one row. Each keeps the
7
7
  // geometry of that site's own logo where it has one, and carries the site's
8
8
  // answering colour on exactly one element — which is what stops it collapsing
9
9
  // into a flat shape at 16 px.
@@ -204,4 +204,47 @@ const GLYPHS: Record<SiteId, (alt: string) => ReactNode> = {
204
204
  <circle cx="15.6" cy="23" r="3.1" fill={alt} />
205
205
  </>
206
206
  ),
207
+ // The site's own favicon: a spectrum, and the one signal being assigned.
208
+ elucidation: (alt) => (
209
+ <>
210
+ <path
211
+ d="M5 24h4l2-11 2 11h4l2-15 2 15h3l1.5-7 1.5 7"
212
+ fill="none"
213
+ stroke="#ffffff"
214
+ strokeWidth="2"
215
+ strokeLinecap="round"
216
+ strokeLinejoin="round"
217
+ />
218
+ <circle cx="19" cy="9" r="3" fill={alt} />
219
+ </>
220
+ ),
221
+ // The site's own favicon: the double harpoon, one direction per colour.
222
+ equilibrium: (alt) => (
223
+ <g
224
+ fill="none"
225
+ strokeWidth="2.5"
226
+ strokeLinecap="round"
227
+ strokeLinejoin="round"
228
+ >
229
+ <path d="M6.5 12.5h17l-4-4" stroke="#ffffff" />
230
+ <path d="M25.5 19.5h-17l4 4" stroke={alt} />
231
+ </g>
232
+ ),
233
+ // A chain of monomers, one of them the comonomer written into it.
234
+ polycarp: (alt) => (
235
+ <>
236
+ <path
237
+ d="M6.5 20 12.5 12 19.5 20 25.5 12"
238
+ fill="none"
239
+ stroke="#ffffff"
240
+ strokeWidth="2"
241
+ strokeLinecap="round"
242
+ strokeLinejoin="round"
243
+ />
244
+ <circle cx="6.5" cy="20" r="3" fill="#ffffff" />
245
+ <circle cx="12.5" cy="12" r="3" fill="#ffffff" />
246
+ <circle cx="19.5" cy="20" r="3" fill={alt} />
247
+ <circle cx="25.5" cy="12" r="3" fill="#ffffff" />
248
+ </>
249
+ ),
207
250
  };