@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rxova/brand",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "Design tokens, Starlight theme and shared site chrome for rxova.org",
6
6
  "keywords": [
@@ -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] {