@kolkrabbi/kol-foundry 0.1.0 → 0.3.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 +5 -1
- package/package.json +6 -5
- package/src/ColorLoader.jsx +155 -0
- package/src/GlyphMetricsSection.jsx +107 -0
- package/src/TextPressure.jsx +331 -0
- package/src/TypeSample.jsx +50 -0
- package/src/TypeSpecCard.jsx +42 -0
- package/src/TypefaceLibraryGrid.jsx +75 -0
- package/src/TypefaceLibraryGridWithVariables.jsx +236 -0
- package/src/TypefaceLibraryItem.jsx +190 -0
- package/src/TypefaceSpecimenPage.jsx +223 -0
- package/src/TypefaceVariablePreview.jsx +160 -0
- package/src/index.js +26 -9
- package/src/typefaceConfig.js +227 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { Button } from '@kolkrabbi/kol-component'
|
|
2
|
+
|
|
3
|
+
import TypefaceStyleSection from './TypefaceStyleSection.jsx'
|
|
4
|
+
import FontPreviewSection from './FontPreviewSection.jsx'
|
|
5
|
+
import VariableFontSection from './VariableFontSection.jsx'
|
|
6
|
+
import GlyphMetricsSection from './GlyphMetricsSection.jsx'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* TypefaceSpecimenPage — the full data-driven typeface specimen composition:
|
|
10
|
+
* hero → styles → preview → variable axes → glyph metrics, interleaved with
|
|
11
|
+
* editorial photos. Drives every section generically from one `typeface` config
|
|
12
|
+
* object (see the bundled `typefaceConfig` / `getTypefaceConfig`).
|
|
13
|
+
*
|
|
14
|
+
* SEVERED from the monorepo route (`TypefacePage`), which is why this is a pure
|
|
15
|
+
* composition, not a route:
|
|
16
|
+
* - No router: navigation is an injected `linkComponent` prop (receives `to`),
|
|
17
|
+
* defaulting to a plain `<a href>`.
|
|
18
|
+
* - No app theme control: the route's `applyTheme`/`getInitialTheme` effect and
|
|
19
|
+
* `toggleTheme` were dropped — theme is the consuming app's concern.
|
|
20
|
+
* - No app-shell hero: the route's `FullBleedHero` (consumer chrome) is now an
|
|
21
|
+
* injectable `HeroComponent` prop. A minimal built-in `DefaultHero` renders the
|
|
22
|
+
* background photo when none is injected; pass the real hero to restore it.
|
|
23
|
+
* - No SEO / data-fetch wrapper: data arrives via the `typeface` prop.
|
|
24
|
+
*
|
|
25
|
+
* Text casing: displayName, category, description and labels render verbatim.
|
|
26
|
+
*
|
|
27
|
+
* @param {Object} props
|
|
28
|
+
* @param {Object} props.typeface - Typeface config object (see typefaceConfig).
|
|
29
|
+
* @param {string} props.titleClassName - Optional hero title className (kept for API parity).
|
|
30
|
+
* @param {React.ElementType} props.linkComponent - Optional link wrapper (receives `to`); defaults to `<a href>`.
|
|
31
|
+
* @param {React.ElementType} props.HeroComponent - Optional hero slot; defaults to a minimal built-in hero.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** Minimal built-in hero used when no HeroComponent is injected — background
|
|
35
|
+
* photo with the specimen intro overlaid. Ignores FullBleedHero-only props. */
|
|
36
|
+
const DefaultHero = ({ image, srcSet, alt, children }) => (
|
|
37
|
+
<div className="relative w-full min-h-[70vh] flex items-center justify-center overflow-hidden rounded-[2px] bg-surface-secondary">
|
|
38
|
+
{image && (
|
|
39
|
+
<img
|
|
40
|
+
src={image}
|
|
41
|
+
srcSet={srcSet || undefined}
|
|
42
|
+
alt={alt}
|
|
43
|
+
className="absolute inset-0 w-full h-full object-cover"
|
|
44
|
+
loading="eager"
|
|
45
|
+
/>
|
|
46
|
+
)}
|
|
47
|
+
<div className="relative z-10">{children}</div>
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const TypefaceSpecimenPage = ({
|
|
52
|
+
typeface,
|
|
53
|
+
titleClassName = 'text-8xl',
|
|
54
|
+
linkComponent,
|
|
55
|
+
HeroComponent,
|
|
56
|
+
}) => {
|
|
57
|
+
const LinkEl = linkComponent || 'a'
|
|
58
|
+
const linkPropsFor = (dest) => (linkComponent ? { to: dest } : { href: dest })
|
|
59
|
+
const Hero = HeroComponent || DefaultHero
|
|
60
|
+
|
|
61
|
+
const {
|
|
62
|
+
displayName,
|
|
63
|
+
fontFamily,
|
|
64
|
+
fontUrl,
|
|
65
|
+
fontUrlRoman,
|
|
66
|
+
fontUrlItalic,
|
|
67
|
+
fontStyle,
|
|
68
|
+
badgeText,
|
|
69
|
+
category,
|
|
70
|
+
description,
|
|
71
|
+
styles,
|
|
72
|
+
photos = []
|
|
73
|
+
} = typeface
|
|
74
|
+
|
|
75
|
+
// Determine if variable font sections should be shown
|
|
76
|
+
const showVariableSection = styles.hasWeight || styles.hasWidth
|
|
77
|
+
|
|
78
|
+
// Get photo at index (all typefaces have CDN photos configured)
|
|
79
|
+
const getPhoto = (index) => photos[index]
|
|
80
|
+
|
|
81
|
+
// Generate srcSet from a photo URL by replacing the size
|
|
82
|
+
// Pattern: {path}-{size}.jpg → {path}-400.jpg 400w, {path}-800.jpg 800w, etc.
|
|
83
|
+
// Capped at 1600 as not all images have 2560 available
|
|
84
|
+
const getSrcSet = (photoUrl) => {
|
|
85
|
+
if (!photoUrl || !photoUrl.includes('-1200.jpg')) return null
|
|
86
|
+
const basePath = photoUrl.replace('-1200.jpg', '')
|
|
87
|
+
return `${basePath}-400.jpg 400w, ${basePath}-800.jpg 800w, ${basePath}-1200.jpg 1200w, ${basePath}-1600.jpg 1600w`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="min-h-screen mb-16 bg-surface-primary">
|
|
92
|
+
<main className="w-full">
|
|
93
|
+
{/* Hero (injectable slot) */}
|
|
94
|
+
<div className="mt-14 md:mt-16">
|
|
95
|
+
<Hero
|
|
96
|
+
image={getPhoto(0)}
|
|
97
|
+
srcSet={getSrcSet(photos[0])}
|
|
98
|
+
alt={`${displayName} showcase`}
|
|
99
|
+
imageOpacity={100}
|
|
100
|
+
>
|
|
101
|
+
<div className="flex flex-col items-center text-center gap-4 md:gap-6 px-4 md:px-6 py-6 md:py-8 rounded-[2px]" style={{ backgroundColor: 'color-mix(in srgb, var(--kol-surface-primary) 80%, transparent)', backdropFilter: 'blur(1px)' }}>
|
|
102
|
+
<span
|
|
103
|
+
className={`${(displayName === 'Málrómur' || displayName === 'Tröllatunga') ? 'text-[48px] sm:text-[64px] md:text-[88px] lg:text-[120px]' : 'text-[56px] sm:text-[80px] md:text-[110px] lg:text-[144px]'} block text-auto leading-none ${fontStyle === 'italic' ? 'italic' : ''}`.trim()}
|
|
104
|
+
style={{ fontFamily, fontStyle: fontStyle || 'normal', fontWeight: 400 }}
|
|
105
|
+
>
|
|
106
|
+
{displayName}
|
|
107
|
+
</span>
|
|
108
|
+
<span className="kol-mono-xs text-fg-64">{category}</span>
|
|
109
|
+
<p className="kol-mono-xs text-auto max-w-[480px] md:max-w-[600px]">{description}</p>
|
|
110
|
+
<LinkEl {...linkPropsFor('/foundry/licensing')}>
|
|
111
|
+
<Button variant="primary" size="sm">Download Font</Button>
|
|
112
|
+
</LinkEl>
|
|
113
|
+
</div>
|
|
114
|
+
</Hero>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div className="breakpoint-padding">
|
|
118
|
+
{/* Section 1: Styles */}
|
|
119
|
+
<TypefaceStyleSection typeface={typeface} />
|
|
120
|
+
|
|
121
|
+
{/* Image Section 2 */}
|
|
122
|
+
<section className="w-full overflow-hidden py-16">
|
|
123
|
+
<div className="max-w-[1400px] mx-auto aspect-[2/1]">
|
|
124
|
+
<div className="w-full h-full bg-surface-secondary rounded border border-fg-08">
|
|
125
|
+
<img
|
|
126
|
+
src={getPhoto(1)}
|
|
127
|
+
srcSet={getSrcSet(photos[1])}
|
|
128
|
+
sizes="(max-width: 1400px) 100vw, 1400px"
|
|
129
|
+
alt={`${displayName} showcase`}
|
|
130
|
+
className="w-full h-full object-cover rounded-[4px]"
|
|
131
|
+
loading="lazy"
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</section>
|
|
136
|
+
|
|
137
|
+
{/* Section 2: Font Preview */}
|
|
138
|
+
<FontPreviewSection
|
|
139
|
+
fontFamily={fontFamily}
|
|
140
|
+
badgeText={badgeText}
|
|
141
|
+
showDropdown={styles.hasItalic}
|
|
142
|
+
availableWeights={(styles.weights || []).map(w => w.label)}
|
|
143
|
+
initialWeight={(styles.weights || [])[0]?.label || 'Regular'}
|
|
144
|
+
/>
|
|
145
|
+
|
|
146
|
+
{/* Image Section 3 */}
|
|
147
|
+
<section className="w-full overflow-hidden py-16">
|
|
148
|
+
<div className="max-w-[1400px] mx-auto aspect-[2/1]">
|
|
149
|
+
<div className="w-full h-full bg-surface-secondary rounded border border-fg-08">
|
|
150
|
+
<img
|
|
151
|
+
src={getPhoto(2)}
|
|
152
|
+
srcSet={getSrcSet(photos[2])}
|
|
153
|
+
sizes="(max-width: 1400px) 100vw, 1400px"
|
|
154
|
+
alt={`${displayName} showcase`}
|
|
155
|
+
className="w-full h-full object-cover rounded-[4px]"
|
|
156
|
+
loading="lazy"
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</section>
|
|
161
|
+
|
|
162
|
+
{/* Section 3: Variable Font (only for variable fonts) */}
|
|
163
|
+
{showVariableSection && (
|
|
164
|
+
<VariableFontSection
|
|
165
|
+
fontFamily={fontFamily}
|
|
166
|
+
badgeText={badgeText}
|
|
167
|
+
showDropdown={styles.hasItalic}
|
|
168
|
+
/>
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
{/* Section 4: Glyph Metrics Grid */}
|
|
172
|
+
<GlyphMetricsSection
|
|
173
|
+
fontUrlRoman={fontUrlRoman || fontUrl}
|
|
174
|
+
fontUrlItalic={fontUrlItalic || fontUrl}
|
|
175
|
+
fontFamily={fontFamily}
|
|
176
|
+
fontStyle={fontStyle}
|
|
177
|
+
badgeText={badgeText}
|
|
178
|
+
showDropdown={styles.hasItalic}
|
|
179
|
+
hasWeight={styles.hasWeight}
|
|
180
|
+
hasWidth={styles.hasWidth}
|
|
181
|
+
weights={styles.weights || []}
|
|
182
|
+
widths={styles.widths || []}
|
|
183
|
+
/>
|
|
184
|
+
|
|
185
|
+
{/* Image Section 4 */}
|
|
186
|
+
<section className="w-full mt-12 py-16 overflow-hidden">
|
|
187
|
+
<div className="max-w-[1400px] mx-auto aspect-[2/1]">
|
|
188
|
+
<div className="w-full h-full bg-surface-secondary rounded border border-fg-08">
|
|
189
|
+
<img
|
|
190
|
+
src={getPhoto(3)}
|
|
191
|
+
srcSet={getSrcSet(photos[3])}
|
|
192
|
+
sizes="(max-width: 1400px) 100vw, 1400px"
|
|
193
|
+
alt={`${displayName} showcase`}
|
|
194
|
+
className="w-full h-full object-cover rounded-[4px]"
|
|
195
|
+
loading="lazy"
|
|
196
|
+
/>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
</section>
|
|
200
|
+
|
|
201
|
+
{/* Image Section 5 */}
|
|
202
|
+
<section className="w-full py-16 overflow-hidden">
|
|
203
|
+
<div className="max-w-[1400px] mx-auto aspect-[2/1]">
|
|
204
|
+
<div className="w-full h-full bg-surface-secondary rounded border border-fg-08">
|
|
205
|
+
<img
|
|
206
|
+
src={getPhoto(4)}
|
|
207
|
+
srcSet={getSrcSet(photos[4])}
|
|
208
|
+
sizes="(max-width: 1400px) 100vw, 1400px"
|
|
209
|
+
alt={`${displayName} showcase`}
|
|
210
|
+
className="w-full h-full object-cover rounded-[4px]"
|
|
211
|
+
loading="lazy"
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
</section>
|
|
216
|
+
|
|
217
|
+
</div>
|
|
218
|
+
</main>
|
|
219
|
+
</div>
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export default TypefaceSpecimenPage
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { Slider } from '@kolkrabbi/kol-component'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* TypefaceVariablePreview — interactive preview for a single typeface weight.
|
|
6
|
+
* List view: editable specimen text with Size / Leading / Spacing sliders. Card
|
|
7
|
+
* view: static metrics readout. Used in the "By Typeface" library filter to
|
|
8
|
+
* compare weights side by side.
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} props
|
|
11
|
+
* @param {Object} props.typeface - Typeface data ({ name, styles, classification, status, year }).
|
|
12
|
+
* @param {string} props.weight - Weight label (e.g. 'Medium', 'Bold').
|
|
13
|
+
* @param {number} props.weightValue - Numeric weight value (200–900).
|
|
14
|
+
* @param {'list'|'card'} props.variant - Display variant (default 'list').
|
|
15
|
+
*/
|
|
16
|
+
const TypefaceVariablePreview = ({
|
|
17
|
+
typeface,
|
|
18
|
+
weight = 'Medium',
|
|
19
|
+
weightValue = 400,
|
|
20
|
+
variant = 'list'
|
|
21
|
+
}) => {
|
|
22
|
+
// Font family mapping
|
|
23
|
+
const fontFamilyMap = {
|
|
24
|
+
'TG Málrómur': 'TGMalromur',
|
|
25
|
+
'TG Rót': 'TGRoot',
|
|
26
|
+
'TG Gullhamrar': 'TGGullhamrar',
|
|
27
|
+
'TG Tröllatunga': 'TGTrollatunga',
|
|
28
|
+
'TG Dylgjur': 'TGDylgjur'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const fontFamily = fontFamilyMap[typeface.name] || 'TGMalromur'
|
|
32
|
+
const isItalic = typeface.name === 'TG Málrómur' || typeface.name === 'TG Dylgjur'
|
|
33
|
+
|
|
34
|
+
// State for interactive controls
|
|
35
|
+
const [size, setSize] = useState(64)
|
|
36
|
+
const [leading, setLeading] = useState(10) // 0-50 range (maps to 90-140%)
|
|
37
|
+
const [spacing, setSpacing] = useState(0)
|
|
38
|
+
const [previewText, setPreviewText] = useState('Rennimjúkt eðal flauel, duft slæðist, silkislaufa')
|
|
39
|
+
|
|
40
|
+
// Card View - Static information display
|
|
41
|
+
if (variant === 'card') {
|
|
42
|
+
return (
|
|
43
|
+
<div className="bg-surface-primary border border-fg-08 rounded-sm p-6 h-full">
|
|
44
|
+
<div className="space-y-4">
|
|
45
|
+
{/* Header */}
|
|
46
|
+
<div>
|
|
47
|
+
<h3 className="kol-heading-sm mb-1">{typeface.name} — {weight}</h3>
|
|
48
|
+
<p className="kol-mono-xs text-fg-64">{typeface.styles}</p>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
{/* Metrics Grid */}
|
|
52
|
+
<div className="grid grid-cols-2 gap-3 pt-4">
|
|
53
|
+
<div>
|
|
54
|
+
<span className="kol-mono-xs text-fg-64">Classification</span>
|
|
55
|
+
<p className="kol-mono-sm text-auto">{typeface.classification}</p>
|
|
56
|
+
</div>
|
|
57
|
+
<div>
|
|
58
|
+
<span className="kol-mono-xs text-fg-64">Weight</span>
|
|
59
|
+
<p className="kol-mono-sm text-auto">{weight} ({weightValue})</p>
|
|
60
|
+
</div>
|
|
61
|
+
<div>
|
|
62
|
+
<span className="kol-mono-xs text-fg-64">Status</span>
|
|
63
|
+
<p className="kol-mono-sm text-auto">{typeface.status}</p>
|
|
64
|
+
</div>
|
|
65
|
+
<div>
|
|
66
|
+
<span className="kol-mono-xs text-fg-64">Year</span>
|
|
67
|
+
<p className="kol-mono-sm text-auto">{typeface.year}</p>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
{/* Typography Metrics - Placeholder for future implementation */}
|
|
72
|
+
<div className="pt-4 border-t border-fg-08">
|
|
73
|
+
<h4 className="kol-mono-xs text-fg-64 mb-2">Typography Metrics</h4>
|
|
74
|
+
<div className="grid grid-cols-2 gap-2 kol-mono-xs text-fg-64">
|
|
75
|
+
<div>Baseline: —</div>
|
|
76
|
+
<div>x-height: —</div>
|
|
77
|
+
<div>Cap height: —</div>
|
|
78
|
+
<div>Ascender: —</div>
|
|
79
|
+
<div>Descender: —</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// List View - Interactive preview with controls
|
|
88
|
+
return (
|
|
89
|
+
<div className="self-stretch min-h-40 p-6 rounded border border-fg-16 flex flex-col justify-start items-start gap-6 overflow-hidden bg-surface-primary">
|
|
90
|
+
{/* Header Row with Controls */}
|
|
91
|
+
<div className="self-stretch flex justify-between items-center">
|
|
92
|
+
{/* Left: Typeface Name & Info */}
|
|
93
|
+
<div className="w-64 flex justify-start items-start gap-6">
|
|
94
|
+
<div className="flex-1 flex flex-col justify-start items-start gap-3">
|
|
95
|
+
<div className="self-stretch flex flex-col justify-start items-start gap-2">
|
|
96
|
+
<div className="self-stretch kol-mono-sm uppercase">
|
|
97
|
+
{typeface.name} — {weight}
|
|
98
|
+
</div>
|
|
99
|
+
<div className="self-stretch kol-mono-xs text-fg-64">
|
|
100
|
+
{typeface.styles}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{/* Right: Three Sliders Horizontal */}
|
|
107
|
+
<div className="w-[960px] flex justify-start items-start gap-8">
|
|
108
|
+
<Slider
|
|
109
|
+
label="Size"
|
|
110
|
+
min={12}
|
|
111
|
+
max={200}
|
|
112
|
+
value={size}
|
|
113
|
+
onChange={setSize}
|
|
114
|
+
variant="minimal"
|
|
115
|
+
className="flex-1"
|
|
116
|
+
/>
|
|
117
|
+
<Slider
|
|
118
|
+
label="Leading"
|
|
119
|
+
min={0}
|
|
120
|
+
max={50}
|
|
121
|
+
value={leading}
|
|
122
|
+
onChange={setLeading}
|
|
123
|
+
variant="minimal"
|
|
124
|
+
className="flex-1"
|
|
125
|
+
formatValue={(val) => 90 + val}
|
|
126
|
+
/>
|
|
127
|
+
<Slider
|
|
128
|
+
label="Spacing"
|
|
129
|
+
min={-50}
|
|
130
|
+
max={50}
|
|
131
|
+
value={spacing}
|
|
132
|
+
onChange={setSpacing}
|
|
133
|
+
variant="minimal"
|
|
134
|
+
className="flex-1"
|
|
135
|
+
/>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{/* Preview Text Row */}
|
|
140
|
+
<div className="self-stretch rounded flex justify-start items-center">
|
|
141
|
+
<input
|
|
142
|
+
type="text"
|
|
143
|
+
value={previewText}
|
|
144
|
+
onChange={(e) => setPreviewText(e.target.value)}
|
|
145
|
+
className="flex-1 self-stretch justify-start bg-transparent text-auto font-black leading-[52px] outline-none border-none placeholder:text-auto"
|
|
146
|
+
style={{
|
|
147
|
+
fontFamily: fontFamily,
|
|
148
|
+
fontStyle: isItalic ? 'italic' : 'normal',
|
|
149
|
+
fontWeight: weightValue,
|
|
150
|
+
fontSize: `${size}px`,
|
|
151
|
+
lineHeight: `${90 + leading}%`,
|
|
152
|
+
letterSpacing: `${spacing}px`
|
|
153
|
+
}}
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export default TypefaceVariablePreview
|
package/src/index.js
CHANGED
|
@@ -1,21 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @kol
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* src/index.js barrel. The root barrel export lines are documented in the p9
|
|
6
|
-
* wiring notes for a central merge.
|
|
2
|
+
* @kolkrabbi/kol-foundry — the type-specimen apparatus. Every export renders,
|
|
3
|
+
* inspects, or manipulates a live font (see COMPONENTS.md for the membership
|
|
4
|
+
* test). Data is consumer-injected; shared primitives stay in @kolkrabbi/kol-component.
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
|
-
//
|
|
7
|
+
// section scaffold
|
|
10
8
|
export { default as SpecimenSectionHeader } from './SpecimenSectionHeader.jsx'
|
|
11
9
|
|
|
12
|
-
//
|
|
10
|
+
// specimen tools — one typeface, inspected
|
|
13
11
|
export { default as TypefaceHero } from './TypefaceHero.jsx'
|
|
14
|
-
export { default as VariableFontSection } from './VariableFontSection.jsx'
|
|
15
|
-
export { default as GlyphMetricsGrid } from './GlyphMetricsGrid.jsx'
|
|
16
12
|
export { default as TypefaceStyleSection } from './TypefaceStyleSection.jsx'
|
|
17
13
|
export { default as FontPreviewSection } from './FontPreviewSection.jsx'
|
|
14
|
+
export { default as VariableFontSection } from './VariableFontSection.jsx'
|
|
15
|
+
export { default as GlyphMetricsGrid } from './GlyphMetricsGrid.jsx'
|
|
16
|
+
export { default as GlyphMetricsSection } from './GlyphMetricsSection.jsx'
|
|
18
17
|
export { default as FoundryCharacterSets } from './FoundryCharacterSets.jsx'
|
|
19
18
|
|
|
19
|
+
// catalog — the specimen collection
|
|
20
|
+
export { default as TypefaceLibraryGrid } from './TypefaceLibraryGrid.jsx'
|
|
21
|
+
export { default as TypefaceLibraryGridWithVariables } from './TypefaceLibraryGridWithVariables.jsx'
|
|
22
|
+
export { default as TypefaceLibraryItem } from './TypefaceLibraryItem.jsx'
|
|
23
|
+
export { default as TypefaceVariablePreview } from './TypefaceVariablePreview.jsx'
|
|
24
|
+
|
|
25
|
+
// type-specimen kit — prop-driven specimen blocks (moved from @kolkrabbi/kol-component 2026-07-09)
|
|
26
|
+
export { default as TypeSample } from './TypeSample.jsx'
|
|
27
|
+
export { default as TypeSpecCard } from './TypeSpecCard.jsx'
|
|
28
|
+
|
|
29
|
+
// live-font effects — variable-font axes, animated
|
|
30
|
+
export { default as TextPressure } from './TextPressure.jsx'
|
|
31
|
+
export { default as ColorLoader } from './ColorLoader.jsx'
|
|
32
|
+
|
|
33
|
+
// reference composition (severed page — data via props, no router/SEO/data-fetch)
|
|
34
|
+
export { default as TypefaceSpecimenPage } from './TypefaceSpecimenPage.jsx'
|
|
35
|
+
|
|
20
36
|
// data
|
|
21
37
|
export { glyphSets, glyphCategories, SPECIMEN_SAMPLE_TEXT } from './glyphData.js'
|
|
38
|
+
export { typefaceConfig, getTypefaceConfig, getAllTypefaceIds, getAllTypefaces } from './typefaceConfig.js'
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typeface Configuration — bundled default fixture for the foundry specimen
|
|
3
|
+
* layer. Ported verbatim from the monorepo `apps/web` foundry data so the DS
|
|
4
|
+
* package carries a ready-to-render default set (Málrómur, Rót, Dylgjur,
|
|
5
|
+
* Gullhamrar, Tröllatunga). Consumers can ignore this and inject their own
|
|
6
|
+
* typeface objects via props — nothing here is load-bearing beyond a default.
|
|
7
|
+
*
|
|
8
|
+
* Font URLs are relative (`/fonts/…`); a consumer serves the actual files.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const cdnBase = 'https://f005.backblazeb2.com/file/kolkrabbi/website/asset-library/foundry'
|
|
12
|
+
|
|
13
|
+
export const typefaceConfig = {
|
|
14
|
+
malromur: {
|
|
15
|
+
id: 'malromur',
|
|
16
|
+
name: 'Málrómur',
|
|
17
|
+
displayName: 'Málrómur',
|
|
18
|
+
fontFamily: 'TGMalromur',
|
|
19
|
+
fontUrl: '/fonts/TGMalromurItalicVF.ttf',
|
|
20
|
+
fontUrlRoman: '/fonts/TGMalromurRomanVF.ttf',
|
|
21
|
+
fontUrlItalic: '/fonts/TGMalromurItalicVF.ttf',
|
|
22
|
+
fontStyle: 'italic',
|
|
23
|
+
category: 'Variable Font',
|
|
24
|
+
description: 'A contemporary italic variable font for editorial design',
|
|
25
|
+
badgeText: 'Málrómur',
|
|
26
|
+
specimenLink: '/foundry/specimen/malromur',
|
|
27
|
+
|
|
28
|
+
photos: [
|
|
29
|
+
`${cdnBase}/foundry-typefaces/01-malromur/typefaces-malromur/01-typefaces-hero/typefaces-hero-1200.jpg`,
|
|
30
|
+
`${cdnBase}/foundry-typefaces/01-malromur/typefaces-malromur/02-typefaces-image/typefaces-image-1200.jpg`,
|
|
31
|
+
`${cdnBase}/foundry-typefaces/01-malromur/typefaces-malromur/03-typefaces-image/typefaces-image-1200.jpg`,
|
|
32
|
+
`${cdnBase}/foundry-typefaces/01-malromur/typefaces-malromur/04-typefaces-image/typefaces-image-1200.jpg`,
|
|
33
|
+
`${cdnBase}/foundry-typefaces/01-malromur/typefaces-malromur/05-typefaces-image/typefaces-image-1200.jpg`
|
|
34
|
+
],
|
|
35
|
+
|
|
36
|
+
// Style section config
|
|
37
|
+
styles: {
|
|
38
|
+
hasWeight: true,
|
|
39
|
+
hasWidth: false,
|
|
40
|
+
hasItalic: true,
|
|
41
|
+
defaultStyle: 'italic',
|
|
42
|
+
weights: [
|
|
43
|
+
{ label: 'Thin', weight: 100 },
|
|
44
|
+
{ label: 'Extralight', weight: 200 },
|
|
45
|
+
{ label: 'Light', weight: 300 },
|
|
46
|
+
{ label: 'Regular', weight: 400 },
|
|
47
|
+
{ label: 'Medium', weight: 500 },
|
|
48
|
+
{ label: 'Semibold', weight: 600 },
|
|
49
|
+
{ label: 'Bold', weight: 700 },
|
|
50
|
+
{ label: 'Extrabold', weight: 800 }
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
rot: {
|
|
56
|
+
id: 'rot',
|
|
57
|
+
name: 'Rót',
|
|
58
|
+
displayName: 'Rót',
|
|
59
|
+
fontFamily: 'TGRoot',
|
|
60
|
+
fontUrl: '/fonts/TGRotVF.ttf',
|
|
61
|
+
fontStyle: 'normal',
|
|
62
|
+
category: 'Variable Font',
|
|
63
|
+
description: 'A precise geometric sans serif with variable weight and width axes',
|
|
64
|
+
badgeText: 'Rót Aa',
|
|
65
|
+
specimenLink: '/foundry/specimen/rot',
|
|
66
|
+
|
|
67
|
+
photos: [
|
|
68
|
+
`${cdnBase}/foundry-typefaces/02-raetur/typefaces-raetur/01-typefaces-hero/typefaces-hero-1200.jpg`,
|
|
69
|
+
`${cdnBase}/foundry-typefaces/02-raetur/typefaces-raetur/02-typefaces-image/typefaces-image-1200.jpg`,
|
|
70
|
+
`${cdnBase}/foundry-typefaces/02-raetur/typefaces-raetur/03-typefaces-image/typefaces-image-1200.jpg`,
|
|
71
|
+
`${cdnBase}/foundry-typefaces/02-raetur/typefaces-raetur/04-typefaces-image/typefaces-image-1200.jpg`,
|
|
72
|
+
`${cdnBase}/foundry-typefaces/02-raetur/typefaces-raetur/05-typefaces-image/typefaces-image-1200.jpg`
|
|
73
|
+
],
|
|
74
|
+
|
|
75
|
+
styles: {
|
|
76
|
+
hasWeight: true,
|
|
77
|
+
hasWidth: true,
|
|
78
|
+
hasItalic: false,
|
|
79
|
+
defaultStyle: 'weight',
|
|
80
|
+
weights: [
|
|
81
|
+
{ label: 'Thin', weight: 100 },
|
|
82
|
+
{ label: 'Extralight', weight: 200 },
|
|
83
|
+
{ label: 'Light', weight: 300 },
|
|
84
|
+
{ label: 'Regular', weight: 400 },
|
|
85
|
+
{ label: 'Medium', weight: 500 },
|
|
86
|
+
{ label: 'Semibold', weight: 600 },
|
|
87
|
+
{ label: 'Bold', weight: 700 },
|
|
88
|
+
{ label: 'Extrabold', weight: 800 },
|
|
89
|
+
{ label: 'Black', weight: 900 }
|
|
90
|
+
],
|
|
91
|
+
widths: [
|
|
92
|
+
{ label: 'Narrow', width: 100 },
|
|
93
|
+
{ label: 'Semi-Narrow', width: 175 },
|
|
94
|
+
{ label: 'Normal', width: 250 },
|
|
95
|
+
{ label: 'Semi-Extended', width: 325 },
|
|
96
|
+
{ label: 'Extended', width: 400 }
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
dylgjur: {
|
|
102
|
+
id: 'dylgjur',
|
|
103
|
+
name: 'Dylgjur',
|
|
104
|
+
displayName: 'Dylgjur',
|
|
105
|
+
fontFamily: 'TGDylgjur',
|
|
106
|
+
fontUrl: '/fonts/TGDylgjur-Regular.otf',
|
|
107
|
+
fontStyle: 'normal',
|
|
108
|
+
category: 'Display Font',
|
|
109
|
+
description: 'Sharp angles and pointed character for critical discourse',
|
|
110
|
+
badgeText: 'Dylgjur Aa',
|
|
111
|
+
specimenLink: '/foundry/specimen/dylgjur',
|
|
112
|
+
|
|
113
|
+
photos: [
|
|
114
|
+
`${cdnBase}/foundry-typefaces/03-dylgjur/typefaces-dylgjur/01-typefaces-hero/typefaces-hero-1200.jpg`,
|
|
115
|
+
`${cdnBase}/foundry-typefaces/03-dylgjur/typefaces-dylgjur/02-typefaces-image/typefaces-image-1200.jpg`,
|
|
116
|
+
`${cdnBase}/foundry-typefaces/03-dylgjur/typefaces-dylgjur/03-typefaces-image/typefaces-image-1200.jpg`,
|
|
117
|
+
`${cdnBase}/foundry-typefaces/03-dylgjur/typefaces-dylgjur/04-typefaces-image/typefaces-image-1200.jpg`,
|
|
118
|
+
`${cdnBase}/foundry-typefaces/03-dylgjur/typefaces-dylgjur/05-typefaces-image/typefaces-image-1200.jpg`
|
|
119
|
+
],
|
|
120
|
+
|
|
121
|
+
styles: {
|
|
122
|
+
hasWeight: false,
|
|
123
|
+
hasWidth: false,
|
|
124
|
+
hasItalic: false,
|
|
125
|
+
weights: [
|
|
126
|
+
{ label: 'Regular', weight: 400 }
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
gullhamrar: {
|
|
132
|
+
id: 'gullhamrar',
|
|
133
|
+
name: 'Gullhamrar',
|
|
134
|
+
displayName: 'Gullhamrar',
|
|
135
|
+
fontFamily: 'TGGullhamrar',
|
|
136
|
+
fontUrl: '/fonts/TGGullhamrarVF.ttf',
|
|
137
|
+
fontStyle: 'normal',
|
|
138
|
+
category: 'Variable Font',
|
|
139
|
+
description: 'Variable weight typeface with warm, graceful forms',
|
|
140
|
+
badgeText: 'Gullhamrar Aa',
|
|
141
|
+
specimenLink: '/foundry/specimen/gullhamrar',
|
|
142
|
+
|
|
143
|
+
photos: [
|
|
144
|
+
`${cdnBase}/foundry-typefaces/04-gullhamrar/typefaces-gullhamrar/01-typefaces-hero/typefaces-hero-1200.jpg`,
|
|
145
|
+
`${cdnBase}/foundry-typefaces/04-gullhamrar/typefaces-gullhamrar/02-typefaces-image/typefaces-image-1200.jpg`,
|
|
146
|
+
`${cdnBase}/foundry-typefaces/04-gullhamrar/typefaces-gullhamrar/03-typefaces-image/typefaces-image-1200.jpg`,
|
|
147
|
+
`${cdnBase}/foundry-typefaces/04-gullhamrar/typefaces-gullhamrar/04-typefaces-image/typefaces-image-1200.jpg`,
|
|
148
|
+
`${cdnBase}/foundry-typefaces/04-gullhamrar/typefaces-gullhamrar/05-typefaces-image/typefaces-image-1200.jpg`
|
|
149
|
+
],
|
|
150
|
+
|
|
151
|
+
styles: {
|
|
152
|
+
hasWeight: true,
|
|
153
|
+
hasWidth: false,
|
|
154
|
+
hasItalic: false,
|
|
155
|
+
weights: [
|
|
156
|
+
{ label: 'Thin', weight: 100 },
|
|
157
|
+
{ label: 'Extralight', weight: 200 },
|
|
158
|
+
{ label: 'Light', weight: 300 },
|
|
159
|
+
{ label: 'Regular', weight: 400 },
|
|
160
|
+
{ label: 'Medium', weight: 500 },
|
|
161
|
+
{ label: 'Semibold', weight: 600 },
|
|
162
|
+
{ label: 'Bold', weight: 700 },
|
|
163
|
+
{ label: 'Extrabold', weight: 800 }
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
trollatunga: {
|
|
169
|
+
id: 'trollatunga',
|
|
170
|
+
name: 'Tröllatunga',
|
|
171
|
+
displayName: 'Tröllatunga',
|
|
172
|
+
fontFamily: 'TGTrollatunga',
|
|
173
|
+
fontUrl: '/fonts/TGTrollatunga-Regular.otf',
|
|
174
|
+
fontStyle: 'normal',
|
|
175
|
+
category: 'Display Font',
|
|
176
|
+
description: 'Bold expressive display font for impactful messaging',
|
|
177
|
+
badgeText: 'Tröllatunga Aa',
|
|
178
|
+
specimenLink: '/foundry/specimen/trollatunga',
|
|
179
|
+
|
|
180
|
+
photos: [
|
|
181
|
+
`${cdnBase}/foundry-typefaces/05-trollatunga/typefaces-trollatunga/01-typefaces-hero/typefaces-hero-1200.jpg`,
|
|
182
|
+
`${cdnBase}/foundry-typefaces/05-trollatunga/typefaces-trollatunga/02-typefaces-image/typefaces-image-1200.jpg`,
|
|
183
|
+
`${cdnBase}/foundry-typefaces/05-trollatunga/typefaces-trollatunga/03-typefaces-image/typefaces-image-1200.jpg`,
|
|
184
|
+
`${cdnBase}/foundry-typefaces/05-trollatunga/typefaces-trollatunga/04-typefaces-image/typefaces-image-1200.jpg`,
|
|
185
|
+
`${cdnBase}/foundry-typefaces/05-trollatunga/typefaces-trollatunga/05-typefaces-image/typefaces-image-1200.jpg`
|
|
186
|
+
],
|
|
187
|
+
|
|
188
|
+
styles: {
|
|
189
|
+
hasWeight: false,
|
|
190
|
+
hasWidth: false,
|
|
191
|
+
hasItalic: false,
|
|
192
|
+
weights: [
|
|
193
|
+
{ label: 'Regular', weight: 400 }
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Helper to get typeface config by ID
|
|
201
|
+
* @param {string} id - Typeface ID (malromur, rot, dylgjur, etc.)
|
|
202
|
+
* @returns {object} Typeface configuration object
|
|
203
|
+
*/
|
|
204
|
+
export function getTypefaceConfig(id) {
|
|
205
|
+
const config = typefaceConfig[id]
|
|
206
|
+
if (!config) {
|
|
207
|
+
console.warn(`Typeface config not found for: ${id}`)
|
|
208
|
+
return typefaceConfig.malromur // fallback
|
|
209
|
+
}
|
|
210
|
+
return config
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get all typeface IDs
|
|
215
|
+
* @returns {string[]} Array of typeface IDs
|
|
216
|
+
*/
|
|
217
|
+
export function getAllTypefaceIds() {
|
|
218
|
+
return Object.keys(typefaceConfig)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get all typefaces
|
|
223
|
+
* @returns {object[]} Array of typeface configuration objects
|
|
224
|
+
*/
|
|
225
|
+
export function getAllTypefaces() {
|
|
226
|
+
return Object.values(typefaceConfig)
|
|
227
|
+
}
|