@rxova/brand 0.4.0 → 0.5.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/package.json +1 -1
- package/src/components/SiteShell.astro +278 -0
- package/src/sites.ts +1 -1
package/package.json
CHANGED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* The shell for rxova.org's non-Starlight surfaces — /blog and /updates.
|
|
4
|
+
*
|
|
5
|
+
* It lived in both packages as a 244-line file that differed on four lines, which
|
|
6
|
+
* is exactly how the menu came to render in a different order on each: a fix has to
|
|
7
|
+
* be made twice, and once was enough to forget.
|
|
8
|
+
*
|
|
9
|
+
* Not for the docs sites — those are Starlight and take their chrome from
|
|
10
|
+
* `sharedStarlightConfig`. This is for the surfaces that are plain Astro.
|
|
11
|
+
*/
|
|
12
|
+
import '../fonts.css'
|
|
13
|
+
import '../astro.css'
|
|
14
|
+
|
|
15
|
+
import { RXOVA_ORIGIN, siteUrl } from '../sites.ts'
|
|
16
|
+
|
|
17
|
+
import ThemeScript from './ThemeScript.astro'
|
|
18
|
+
import ThemeToggle from './ThemeToggle.astro'
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
title: string
|
|
22
|
+
description: string
|
|
23
|
+
/** Canonical path, e.g. `/blog/why-rxova-has-a-blog`. */
|
|
24
|
+
path: string
|
|
25
|
+
ogType?: 'website' | 'article'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { title, description, path, ogType = 'website' } = Astro.props
|
|
29
|
+
const canonical = `${RXOVA_ORIGIN}${path}`
|
|
30
|
+
const year = new Date().getFullYear()
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The menu, in one fixed order for every surface.
|
|
34
|
+
*
|
|
35
|
+
* Which item is current is *derived* from the base the surface was built for, not
|
|
36
|
+
* declared per package — declaring it is what let the two drift into different
|
|
37
|
+
* orders. `BASE_URL` is substituted by the consuming app's build, so this is
|
|
38
|
+
* `/blog` inside @rxova/blog and `/updates` inside @rxova/updates.
|
|
39
|
+
*/
|
|
40
|
+
const base = import.meta.env.BASE_URL.replace(/\/$/, '')
|
|
41
|
+
|
|
42
|
+
const SECTIONS = [
|
|
43
|
+
{ label: 'Blog', path: '/blog' },
|
|
44
|
+
{ label: 'Updates', path: '/updates' },
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A URL inside this surface.
|
|
49
|
+
*
|
|
50
|
+
* Anything *leaving* it goes through `siteUrl`: a bare `/updates` would resolve
|
|
51
|
+
* against the mount and point at `/blog/updates`.
|
|
52
|
+
*/
|
|
53
|
+
const inside = (p = '') => `${base}/${p}`.replace(/\/{2,}/g, '/')
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
<!doctype html>
|
|
57
|
+
<html lang="en">
|
|
58
|
+
<head>
|
|
59
|
+
<meta charset="utf-8" />
|
|
60
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
61
|
+
<link rel="icon" type="image/png" href={siteUrl('/rxova-logo-256.png')} />
|
|
62
|
+
<link rel="apple-touch-icon" href={siteUrl('/rxova-logo-256.png')} />
|
|
63
|
+
<link rel="canonical" href={canonical} />
|
|
64
|
+
<title>{title}</title>
|
|
65
|
+
<meta name="description" content={description} />
|
|
66
|
+
<meta property="og:title" content={title} />
|
|
67
|
+
<meta property="og:description" content={description} />
|
|
68
|
+
<meta property="og:type" content={ogType} />
|
|
69
|
+
<meta property="og:url" content={canonical} />
|
|
70
|
+
<meta property="og:image" content={siteUrl('/og/rxova.png')} />
|
|
71
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
72
|
+
<ThemeScript />
|
|
73
|
+
</head>
|
|
74
|
+
<body>
|
|
75
|
+
<ThemeToggle />
|
|
76
|
+
<main>
|
|
77
|
+
<header class="site">
|
|
78
|
+
<a class="brand" href={siteUrl('/')}>
|
|
79
|
+
<img src={siteUrl('/rxova-logo-256.png')} alt="" width="28" height="28" />
|
|
80
|
+
<span>rxova</span>
|
|
81
|
+
</a>
|
|
82
|
+
{
|
|
83
|
+
/* Fixed order on every surface. It used to render the current item
|
|
84
|
+
second, so Blog and Updates swapped places as you moved between them —
|
|
85
|
+
the menu shifting under the cursor you just clicked with. */
|
|
86
|
+
}
|
|
87
|
+
<nav aria-label="Sections">
|
|
88
|
+
<a href={siteUrl('/')}>Projects</a>
|
|
89
|
+
{
|
|
90
|
+
SECTIONS.map((section) =>
|
|
91
|
+
section.path === base ? (
|
|
92
|
+
<a href={inside()} aria-current="page">
|
|
93
|
+
{section.label}
|
|
94
|
+
</a>
|
|
95
|
+
) : (
|
|
96
|
+
<a href={siteUrl(section.path)}>{section.label}</a>
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
</nav>
|
|
101
|
+
</header>
|
|
102
|
+
|
|
103
|
+
<slot />
|
|
104
|
+
|
|
105
|
+
<footer class="foot">
|
|
106
|
+
<span>© {year} rxova</span>
|
|
107
|
+
<span class="dot" aria-hidden="true">·</span>
|
|
108
|
+
<a href={siteUrl('/')}>Projects</a>
|
|
109
|
+
<span class="dot" aria-hidden="true">·</span>
|
|
110
|
+
<a href={siteUrl('/blog')}>Blog</a>
|
|
111
|
+
<span class="dot" aria-hidden="true">·</span>
|
|
112
|
+
<a href={siteUrl('/updates')}>Updates</a>
|
|
113
|
+
<span class="dot" aria-hidden="true">·</span>
|
|
114
|
+
<a href={siteUrl('/privacy')}>Privacy</a>
|
|
115
|
+
<span class="dot" aria-hidden="true">·</span>
|
|
116
|
+
<a href={siteUrl('/terms')}>Terms</a>
|
|
117
|
+
</footer>
|
|
118
|
+
</main>
|
|
119
|
+
|
|
120
|
+
<style>
|
|
121
|
+
.site {
|
|
122
|
+
display: flex;
|
|
123
|
+
align-items: center;
|
|
124
|
+
justify-content: space-between;
|
|
125
|
+
gap: 1rem;
|
|
126
|
+
flex-wrap: wrap;
|
|
127
|
+
padding-bottom: 1.25rem;
|
|
128
|
+
margin-bottom: clamp(2rem, 6vh, 3rem);
|
|
129
|
+
border-bottom: 1px solid var(--rule);
|
|
130
|
+
}
|
|
131
|
+
.brand {
|
|
132
|
+
display: inline-flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
gap: 0.55rem;
|
|
135
|
+
color: var(--fg);
|
|
136
|
+
text-decoration: none;
|
|
137
|
+
font-weight: 640;
|
|
138
|
+
letter-spacing: -0.01em;
|
|
139
|
+
}
|
|
140
|
+
.brand img {
|
|
141
|
+
display: block;
|
|
142
|
+
border-radius: 6px;
|
|
143
|
+
}
|
|
144
|
+
.site nav {
|
|
145
|
+
display: flex;
|
|
146
|
+
gap: 1.4rem;
|
|
147
|
+
}
|
|
148
|
+
.site nav a {
|
|
149
|
+
color: var(--muted);
|
|
150
|
+
text-decoration: none;
|
|
151
|
+
font-size: 0.95rem;
|
|
152
|
+
}
|
|
153
|
+
.site nav a:hover {
|
|
154
|
+
color: var(--fg);
|
|
155
|
+
}
|
|
156
|
+
.site nav a[aria-current='page'] {
|
|
157
|
+
color: var(--fg);
|
|
158
|
+
text-decoration: underline;
|
|
159
|
+
text-underline-offset: 5px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
main {
|
|
163
|
+
max-width: var(--max);
|
|
164
|
+
margin: 0 auto;
|
|
165
|
+
padding: clamp(2rem, 6vh, 3.5rem) 1.5rem 4rem;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.foot {
|
|
169
|
+
margin-top: clamp(3rem, 8vh, 5rem);
|
|
170
|
+
padding-top: 1.75rem;
|
|
171
|
+
border-top: 1px solid var(--rule);
|
|
172
|
+
color: var(--faint);
|
|
173
|
+
font-size: 0.9rem;
|
|
174
|
+
display: flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
gap: 0.6rem;
|
|
177
|
+
flex-wrap: wrap;
|
|
178
|
+
}
|
|
179
|
+
.foot a {
|
|
180
|
+
color: var(--muted);
|
|
181
|
+
text-decoration: none;
|
|
182
|
+
}
|
|
183
|
+
.foot a:hover {
|
|
184
|
+
color: var(--fg);
|
|
185
|
+
}
|
|
186
|
+
.dot {
|
|
187
|
+
color: var(--rule);
|
|
188
|
+
}
|
|
189
|
+
</style>
|
|
190
|
+
|
|
191
|
+
<!-- Prose styling for rendered markdown. Global rather than scoped: the
|
|
192
|
+
markup comes from <Content /> at build time, so it carries none of
|
|
193
|
+
Astro's scoping attributes. Kept here so both /blog and /updates read
|
|
194
|
+
the same. -->
|
|
195
|
+
<style is:global>
|
|
196
|
+
.prose {
|
|
197
|
+
color: var(--muted);
|
|
198
|
+
line-height: 1.7;
|
|
199
|
+
}
|
|
200
|
+
.prose > * + * {
|
|
201
|
+
margin-top: 1.1rem;
|
|
202
|
+
}
|
|
203
|
+
.prose h2 {
|
|
204
|
+
color: var(--fg);
|
|
205
|
+
font-size: 1.25rem;
|
|
206
|
+
font-weight: 640;
|
|
207
|
+
letter-spacing: -0.01em;
|
|
208
|
+
margin-top: 2.5rem;
|
|
209
|
+
}
|
|
210
|
+
.prose h3 {
|
|
211
|
+
color: var(--fg);
|
|
212
|
+
font-size: 1.05rem;
|
|
213
|
+
font-weight: 620;
|
|
214
|
+
margin-top: 2rem;
|
|
215
|
+
}
|
|
216
|
+
.prose a {
|
|
217
|
+
color: var(--fg);
|
|
218
|
+
text-decoration: underline;
|
|
219
|
+
text-underline-offset: 2px;
|
|
220
|
+
}
|
|
221
|
+
.prose strong {
|
|
222
|
+
color: var(--fg);
|
|
223
|
+
font-weight: 620;
|
|
224
|
+
}
|
|
225
|
+
.prose ul,
|
|
226
|
+
.prose ol {
|
|
227
|
+
margin-left: 1.2rem;
|
|
228
|
+
}
|
|
229
|
+
.prose li + li {
|
|
230
|
+
margin-top: 0.35rem;
|
|
231
|
+
}
|
|
232
|
+
.prose code {
|
|
233
|
+
font-family: var(--mono);
|
|
234
|
+
font-size: 0.88em;
|
|
235
|
+
background: var(--tag-bg);
|
|
236
|
+
padding: 0.05rem 0.35rem;
|
|
237
|
+
border-radius: 4px;
|
|
238
|
+
}
|
|
239
|
+
.prose pre {
|
|
240
|
+
background: var(--card);
|
|
241
|
+
border: 1px solid var(--rule);
|
|
242
|
+
border-radius: 10px;
|
|
243
|
+
padding: 1rem 1.1rem;
|
|
244
|
+
/* Long lines scroll inside the block; the page itself never does. */
|
|
245
|
+
overflow-x: auto;
|
|
246
|
+
}
|
|
247
|
+
.prose pre code {
|
|
248
|
+
background: none;
|
|
249
|
+
padding: 0;
|
|
250
|
+
}
|
|
251
|
+
.prose blockquote {
|
|
252
|
+
border-left: 2px solid var(--rule);
|
|
253
|
+
padding-left: 1rem;
|
|
254
|
+
color: var(--faint);
|
|
255
|
+
}
|
|
256
|
+
.prose img {
|
|
257
|
+
max-width: 100%;
|
|
258
|
+
height: auto;
|
|
259
|
+
border-radius: 10px;
|
|
260
|
+
}
|
|
261
|
+
.prose table {
|
|
262
|
+
width: 100%;
|
|
263
|
+
border-collapse: collapse;
|
|
264
|
+
font-size: 0.95rem;
|
|
265
|
+
}
|
|
266
|
+
.prose th,
|
|
267
|
+
.prose td {
|
|
268
|
+
border-bottom: 1px solid var(--rule);
|
|
269
|
+
padding: 0.5rem 0.6rem;
|
|
270
|
+
text-align: left;
|
|
271
|
+
}
|
|
272
|
+
.prose th {
|
|
273
|
+
color: var(--fg);
|
|
274
|
+
font-weight: 620;
|
|
275
|
+
}
|
|
276
|
+
</style>
|
|
277
|
+
</body>
|
|
278
|
+
</html>
|
package/src/sites.ts
CHANGED
|
@@ -102,7 +102,7 @@ export const REPOS = [
|
|
|
102
102
|
|
|
103
103
|
export type RepoId = (typeof REPOS)[number]['id']
|
|
104
104
|
|
|
105
|
-
/** The ids only, for schema validation — see packages/schemas. */
|
|
105
|
+
/** The ids only, for schema validation — see packages/website-schemas. */
|
|
106
106
|
export const REPO_IDS: readonly RepoId[] = REPOS.map((r) => r.id)
|
|
107
107
|
|
|
108
108
|
export function getRepo(id: RepoId): (typeof REPOS)[number] {
|