astro-better-release-notes 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
@@ -18,9 +18,9 @@ import 'astro-better-release-notes/style.css';
18
18
 
19
19
  ## Components
20
20
 
21
- - `ReleaseNotes` - renders the filter chips and the full version list
21
+ - `ReleaseNotes` - renders a unified sticky header (version selector + category filter chips) and the full version list
22
22
  - `ReleaseNotesItem` - a single categorized release note row (used inside MDX content)
23
- - `ReleaseNotesSelector` - a sticky version jump dropdown
23
+ - `ReleaseNotesSelector` - a standalone version jump dropdown (for custom layouts that need it separately)
24
24
 
25
25
  ## Usage
26
26
 
@@ -54,14 +54,12 @@ import { ReleaseNotesItem } from 'astro-better-release-notes';
54
54
 
55
55
  ### Page component
56
56
 
57
+ `ReleaseNotes` renders everything in one call: a sticky header containing the version selector and filter chips, then the full version list below.
58
+
57
59
  ```astro
58
60
  ---
59
61
  import { getCollection, render } from 'astro:content';
60
- import {
61
- ReleaseNotes,
62
- ReleaseNotesSelector,
63
- DEFAULT_CATEGORIES,
64
- } from 'astro-better-release-notes';
62
+ import { ReleaseNotes, DEFAULT_CATEGORIES } from 'astro-better-release-notes';
65
63
 
66
64
  const allUpdates = await getCollection('releases');
67
65
  const updates = allUpdates
@@ -78,10 +76,60 @@ const rendered = await Promise.all(
78
76
  );
79
77
  ---
80
78
 
81
- <ReleaseNotesSelector updates={rendered} />
82
79
  <ReleaseNotes updates={rendered} categories={DEFAULT_CATEGORIES} />
83
80
  ```
84
81
 
82
+ ### ReleaseNotes props
83
+
84
+ | Prop | Type | Default | Description |
85
+ |------|------|---------|-------------|
86
+ | `updates` | `ReleaseEntry[]` | required | Pre-rendered version entries |
87
+ | `categories` | `Category[]` | `DEFAULT_CATEGORIES` | Category definitions |
88
+ | `components` | `Record<string, any>` | `{}` | Component overrides (e.g. custom `ReleaseNotesItem`) |
89
+ | `showSelector` | `boolean` | `true` | Set `false` to hide the version jump dropdown |
90
+
91
+ ### Sticky offset and scroll margins
92
+
93
+ Two CSS custom properties control the positioning:
94
+
95
+ `--release-notes-top` — how far the sticky header sits from the viewport top (should match your site's fixed nav height). Default: `3.5rem`.
96
+
97
+ `--release-notes-scroll-margin` — the `scroll-margin-top` applied to both version articles and individual release note rows when navigating via anchor links. Should be at least as large as your total sticky height (nav + release-notes sticky header). Default: `5rem`.
98
+
99
+ ```css
100
+ /* in your root layout or global CSS */
101
+ html {
102
+ --release-notes-top: 56px;
103
+ --release-notes-scroll-margin: 6rem; /* nav + sticky header height */
104
+ }
105
+
106
+ @media (min-width: 1024px) {
107
+ html {
108
+ --release-notes-top: 80px;
109
+ --release-notes-scroll-margin: 11rem;
110
+ }
111
+ }
112
+ ```
113
+
114
+ Set both variables on `html` or any ancestor of `.release-container`.
115
+
116
+ ### Link colors
117
+
118
+ The github issue links and category badge hover colors are configurable:
119
+
120
+ | Variable | Default | Description |
121
+ |----------|---------|-------------|
122
+ | `--release-notes-link-color` | `#4338ca` (indigo-700) | Issue link color |
123
+ | `--release-notes-link-hover-color` | `#9a3412` (orange-800) | Hover color for links and badge |
124
+ | `--release-notes-link-color-dark` | `#818cf8` (indigo-400) | Dark mode link color |
125
+ | `--release-notes-link-hover-color-dark` | `#fed7aa` (orange-200) | Dark mode hover color |
126
+
127
+ ### Mobile filter behavior
128
+
129
+ On narrow screens the category filter chips are hidden behind a "Filter" toggle button that sits next to the version selector. Tapping it expands the chips below the selector row. When any filter is active, the button changes color as a visual indicator.
130
+
131
+ On screens 768px and wider, the chips are always visible.
132
+
85
133
  ## Custom categories
86
134
 
87
135
  Pass a `categories` array to `ReleaseNotes` to define your own set:
@@ -131,8 +179,6 @@ Note: for the `components` injection to work with categories, you also need to p
131
179
  import { getCategoryColor, getCategoryLabel, DEFAULT_CATEGORIES } from 'astro-better-release-notes';
132
180
  ```
133
181
 
134
- Or provide these values in your own wrapper by computing them from your category list.
135
-
136
182
  ## ReleaseNotesItem props
137
183
 
138
184
  | Prop | Type | Description |
@@ -153,6 +199,28 @@ Or provide these values in your own wrapper by computing them from your category
153
199
 
154
200
  `ReleaseNotes.astro` does not automatically inject `categoryColor`/`categoryLabel` into the MDX-rendered items -- those props come from the MDX markup. To automate this, create a wrapper that looks up the category in your list and injects the values, then pass it via `components`.
155
201
 
202
+ ## Standalone ReleaseNotesSelector
203
+
204
+ `ReleaseNotesSelector` is available as a standalone component for custom layouts that need the version dropdown without the rest of the notes. It is not sticky by default; wrap it in your own sticky container if needed.
205
+
206
+ ```astro
207
+ import { ReleaseNotesSelector } from 'astro-better-release-notes';
208
+ ---
209
+ <div style="position: sticky; top: 56px;">
210
+ <ReleaseNotesSelector {updates} />
211
+ </div>
212
+ ```
213
+
156
214
  ## Dark mode
157
215
 
158
- The stylesheet supports both `@media (prefers-color-scheme: dark)` and a `.dark` ancestor class.
216
+ The stylesheet uses a `.dark` ancestor class (compatible with Tailwind's `darkMode: 'class'`). To also enable automatic OS-based dark mode, add to your project CSS:
217
+
218
+ ```css
219
+ @media (prefers-color-scheme: dark) {
220
+ html:not([data-theme="light"]) {
221
+ /* copy the .dark rules you need, or add the .dark class via JS */
222
+ }
223
+ }
224
+ ```
225
+
226
+ Most Tailwind setups initialize the `.dark` class from OS preference on first load, so no extra CSS is needed.
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  import ReleaseNotesItem from './ReleaseNotesItem.astro';
3
+ import ReleaseNotesSelector from './ReleaseNotesSelector.astro';
3
4
 
4
5
  export interface Category {
5
6
  id: string;
@@ -8,14 +9,14 @@ export interface Category {
8
9
  }
9
10
 
10
11
  export const DEFAULT_CATEGORIES: Category[] = [
11
- { id: 'new-feature', label: 'New Feature', color: '#16a34a' },
12
- { id: 'enhancement', label: 'Enhancement', color: '#2563eb' },
13
- { id: 'fix', label: 'Fix', color: '#d97706' },
14
- { id: 'security', label: 'Security', color: '#566f98' },
15
- { id: 'deprecated', label: 'Deprecated', color: '#ffcc48' },
16
- { id: 'breaking-change', label: 'Breaking Change', color: '#aaaa33' },
17
- { id: 'known-issue', label: 'Known Issue', color: '#bb4448' },
18
- { id: 'internal', label: 'Internal', color: '#71717a' },
12
+ { id: 'new-feature', label: 'New Feature', color: 'var(--rn-color-new-feature, #16a34a)' },
13
+ { id: 'enhancement', label: 'Enhancement', color: 'var(--rn-color-enhancement, #2563eb)' },
14
+ { id: 'fix', label: 'Fix', color: 'var(--rn-color-fix, #d97706)' },
15
+ { id: 'security', label: 'Security', color: 'var(--rn-color-security, #566f98)' },
16
+ { id: 'deprecated', label: 'Deprecated', color: 'var(--rn-color-deprecated, #ffcc48)' },
17
+ { id: 'breaking-change', label: 'Breaking Change', color: 'var(--rn-color-breaking-change, #aaaa33)' },
18
+ { id: 'known-issue', label: 'Known Issue', color: 'var(--rn-color-known-issue, #bb4448)' },
19
+ { id: 'internal', label: 'Internal', color: 'var(--rn-color-internal, #71717a)' },
19
20
  ];
20
21
 
21
22
  export const getCategoryColor = (id: string, categories: Category[]) =>
@@ -41,12 +42,15 @@ export interface Props {
41
42
  categories?: Category[];
42
43
  // pass { ReleaseNotesItem: YourWrapper } to inject a custom item component
43
44
  components?: Record<string, any>;
45
+ // set false to hide the version jump dropdown
46
+ showSelector?: boolean;
44
47
  }
45
48
 
46
49
  const {
47
50
  updates,
48
51
  categories = DEFAULT_CATEGORIES,
49
52
  components: extraComponents = {},
53
+ showSelector = true,
50
54
  } = Astro.props as Props;
51
55
 
52
56
  const mergedComponents = { ReleaseNotesItem, ...extraComponents };
@@ -63,14 +67,31 @@ const filterCSS = [
63
67
  <Fragment set:html={`<style>${filterCSS}</style>`} />
64
68
 
65
69
  <div class="release-container">
66
- <nav class="filter-controls" aria-label="Filter release notes by category">
67
- {categories.map(cat => (
68
- <label class="filter-chip" style={`--cat-color: ${cat.color}`}>
69
- <input type="checkbox" id={`toggle-${cat.id}`} class="filter-input" aria-label={`Show ${cat.label}`} />
70
- <span aria-hidden="true">{cat.label}</span>
71
- </label>
72
- ))}
73
- </nav>
70
+ <div class="release-sticky-header">
71
+ {showSelector && <ReleaseNotesSelector {updates} />}
72
+
73
+ <button
74
+ class="filter-toggle-btn"
75
+ aria-expanded="false"
76
+ aria-controls="release-notes-filters"
77
+ >
78
+ <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 16 16" aria-hidden="true" fill="currentColor">
79
+ <path d="M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/>
80
+ </svg>
81
+ Filter
82
+ </button>
83
+
84
+ <nav id="release-notes-filters" class="filter-controls" aria-label="Filter release notes by category">
85
+ {categories.map(cat => (
86
+ <label class="filter-chip" style={`--cat-color: ${cat.color}`}>
87
+ <input type="checkbox" id={`toggle-${cat.id}`} class="filter-input" aria-label={`Show ${cat.label}`} />
88
+ <span aria-hidden="true">{cat.label}</span>
89
+ </label>
90
+ ))}
91
+ </nav>
92
+
93
+ <noscript><style>@media (max-width: 1023px) { #release-notes-filters { display: flex !important; flex-wrap: wrap; } .filter-toggle-btn { display: none !important; } }</style></noscript>
94
+ </div>
74
95
 
75
96
  <div class="version-list">
76
97
  {updates.map((item) => {
@@ -96,3 +117,18 @@ const filterCSS = [
96
117
  })}
97
118
  </div>
98
119
  </div>
120
+
121
+ <script is:inline>
122
+ const initFilterToggle = () => {
123
+ const btn = document.querySelector('.filter-toggle-btn');
124
+ const nav = document.getElementById('release-notes-filters');
125
+ if (!btn || !nav) return;
126
+ btn.addEventListener('click', () => {
127
+ const expanded = btn.getAttribute('aria-expanded') === 'true';
128
+ btn.setAttribute('aria-expanded', String(!expanded));
129
+ nav.classList.toggle('is-open', !expanded);
130
+ });
131
+ };
132
+ document.addEventListener('DOMContentLoaded', initFilterToggle);
133
+ document.addEventListener('astro:page-load', initFilterToggle);
134
+ </script>
@@ -11,7 +11,7 @@ export interface Props {
11
11
  // base URL for issue links, e.g. "https://github.com/org/repo/issues/"
12
12
  // if omitted, issue numbers are shown as plain text
13
13
  issueBaseUrl?: string;
14
- // color for this item's badge; if omitted, uses a neutral gray
14
+ // color for this item's badge; if omitted, CSS handles it via --rn-color-{category}
15
15
  categoryColor?: string;
16
16
  // human-readable category label; if omitted, generated from id
17
17
  categoryLabel?: string;
@@ -27,7 +27,7 @@ const {
27
27
  resolvedIn,
28
28
  viaIssue,
29
29
  issueBaseUrl = '',
30
- categoryColor = '#71717a',
30
+ categoryColor,
31
31
  categoryLabel,
32
32
  } = Astro.props as Props;
33
33
 
@@ -63,11 +63,11 @@ const rowId = baseId ? `${baseId}--${contentSlug}` : contentSlug;
63
63
  id={rowId}
64
64
  class="issue-row"
65
65
  data-category={category}
66
- style={`--cat-color: ${categoryColor}`}
66
+ style={categoryColor ? `--cat-color: ${categoryColor}` : undefined}
67
67
  >
68
68
  <a
69
69
  href={`#${rowId}`}
70
- class="issue-badge"
70
+ class="issue-badge not-prose"
71
71
  aria-label={`${label} - permalink to this release note`}
72
72
  >
73
73
  {label}
@@ -106,12 +106,12 @@ const rowId = baseId ? `${baseId}--${contentSlug}` : contentSlug;
106
106
 
107
107
  {issue && (
108
108
  issueBaseUrl
109
- ? <a class="github-link" target="_blank" rel="noopener noreferrer" href={`${issueBaseUrl}${issue}`}>#{issue}</a>
109
+ ? <a class="github-link not-prose" target="_blank" rel="noopener noreferrer" href={`${issueBaseUrl}${issue}`}>#{issue}</a>
110
110
  : <span class="github-link">#{issue}</span>
111
111
  )}
112
112
  {bonusIssue && (
113
113
  issueBaseUrl
114
- ? <a class="github-link" target="_blank" rel="noopener noreferrer" href={`${issueBaseUrl}${bonusIssue}`}>#{bonusIssue}</a>
114
+ ? <a class="github-link not-prose" target="_blank" rel="noopener noreferrer" href={`${issueBaseUrl}${bonusIssue}`}>#{bonusIssue}</a>
115
115
  : <span class="github-link">#{bonusIssue}</span>
116
116
  )}
117
117
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-better-release-notes",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Configurable release notes components with category filtering and version navigation for Astro",
5
5
  "type": "module",
6
6
  "exports": {
package/style.css CHANGED
@@ -4,20 +4,164 @@
4
4
  Import this in your project root layout to enable release notes styles.
5
5
  No external dependencies -- no Tailwind, no framework.
6
6
 
7
- Dark mode: supports both @media (prefers-color-scheme: dark) and a .dark
8
- ancestor class (e.g. Tailwind's darkMode: 'class').
9
-
10
- Category colors are driven by --cat-color, set inline by ReleaseNotesItem.
7
+ Dark mode: uses a .dark ancestor class (e.g. Tailwind's darkMode: 'class').
8
+ See README for enabling OS-based dark mode without a class toggle.
9
+
10
+ Category colors: set --rn-color-{id} on :root / .dark to override per-category
11
+ badge and chip colors without touching component code. E.g.:
12
+ :root { --rn-color-enhancement: #7c3aed; }
13
+ .dark { --rn-color-enhancement: #fb923c; }
14
+
15
+ Built-in category defaults (CSS fallback, also used by DEFAULT_CATEGORIES):
16
+ --rn-color-new-feature: #16a34a
17
+ --rn-color-enhancement: #2563eb
18
+ --rn-color-fix: #d97706
19
+ --rn-color-security: #566f98
20
+ --rn-color-deprecated: #ffcc48
21
+ --rn-color-breaking-change: #aaaa33
22
+ --rn-color-known-issue: #bb4448
23
+ --rn-color-internal: #71717a
24
+
25
+ Sticky offset configuration:
26
+ Set --release-notes-top on :root (or any ancestor of .release-sticky-header)
27
+ to match your site's fixed navigation height. Defaults to 3.5rem (56px).
28
+
29
+ Example:
30
+ :root { --release-notes-top: 57px; }
31
+ @media (min-width: 1024px) { :root { --release-notes-top: 80px; } }
11
32
  */
12
33
 
34
+ /* ---------------------------------------------------------------
35
+ Sticky header (selector + filter toggle + chips)
36
+ --------------------------------------------------------------- */
37
+
38
+ .release-sticky-header {
39
+ position: sticky;
40
+ top: var(--release-notes-top, 3.5rem);
41
+ z-index: 11;
42
+ display: flex;
43
+ flex-wrap: wrap;
44
+ align-items: center;
45
+ gap: 0.5rem;
46
+ padding: 0.5rem 0;
47
+ background-color: var(--rn-bg, white);
48
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
49
+ margin-bottom: 0.5rem;
50
+ }
51
+
52
+ .dark .release-sticky-header {
53
+ background-color: var(--rn-bg, #0f172a);
54
+ box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06);
55
+ }
56
+
57
+ /* ---------------------------------------------------------------
58
+ Version selector (jump dropdown)
59
+ --------------------------------------------------------------- */
60
+
61
+ .release-selector {
62
+ flex: 1;
63
+ min-width: 200px;
64
+ }
65
+
66
+ .release-selector-select {
67
+ width: 100%;
68
+ height: 2.5rem;
69
+ padding: 0 0.5rem;
70
+ border-radius: 6px;
71
+ border: 2px solid var(--rn-border, #d1d5db);
72
+ background-color: var(--rn-input-bg, #f3f4f6);
73
+ color: var(--rn-text, #111827);
74
+ cursor: pointer;
75
+ }
76
+
77
+ .release-selector-select:focus {
78
+ outline: 2px solid var(--rn-focus, #6366f1);
79
+ outline-offset: 1px;
80
+ }
81
+
82
+ .dark .release-selector-select {
83
+ border-color: var(--rn-border, #4b5563);
84
+ background-color: var(--rn-input-bg, #1e293b);
85
+ color: var(--rn-text, #cbd5e1);
86
+ }
87
+
88
+ /* ---------------------------------------------------------------
89
+ Filter toggle button (mobile only)
90
+ --------------------------------------------------------------- */
91
+
92
+ .filter-toggle-btn {
93
+ flex: 0 0 auto;
94
+ display: flex;
95
+ align-items: center;
96
+ gap: 0.375rem;
97
+ padding: 0.4375rem 0.75rem;
98
+ height: 2.5rem;
99
+ font-size: 0.875rem;
100
+ font-weight: 500;
101
+ border-radius: 6px;
102
+ border: 2px solid var(--rn-border, #d1d5db);
103
+ background-color: var(--rn-input-bg, #f3f4f6);
104
+ color: var(--rn-text-muted, #374151);
105
+ cursor: pointer;
106
+ white-space: nowrap;
107
+ }
108
+
109
+ .filter-toggle-btn:focus {
110
+ outline: 2px solid var(--rn-focus, #6366f1);
111
+ outline-offset: 1px;
112
+ }
113
+
114
+ .dark .filter-toggle-btn {
115
+ border-color: var(--rn-border, #4b5563);
116
+ background-color: var(--rn-input-bg, #1e293b);
117
+ color: var(--rn-text, #cbd5e1);
118
+ }
119
+
120
+ /* indicate active filters */
121
+ .release-container:has(.filter-input:checked) .filter-toggle-btn {
122
+ border-color: var(--rn-focus, #6366f1);
123
+ background-color: color-mix(in srgb, var(--rn-focus, #6366f1) 12%, transparent);
124
+ color: var(--rn-focus, #6366f1);
125
+ }
126
+
127
+ .dark .release-container:has(.filter-input:checked) .filter-toggle-btn {
128
+ border-color: var(--rn-focus, #818cf8);
129
+ color: var(--rn-focus, #818cf8);
130
+ background-color: color-mix(in srgb, var(--rn-focus, #818cf8) 15%, transparent);
131
+ }
132
+
133
+ @media (min-width: 1024px) {
134
+ .filter-toggle-btn {
135
+ display: none;
136
+ }
137
+ }
138
+
13
139
  /* ---------------------------------------------------------------
14
140
  Filter controls (category chips)
15
141
  --------------------------------------------------------------- */
16
142
 
143
+ /* hidden on mobile by default; shown via .is-open (JS) or desktop breakpoint */
144
+ @media (max-width: 1023px) {
145
+ .filter-controls {
146
+ display: none;
147
+ }
148
+
149
+ .filter-controls.is-open {
150
+ display: flex;
151
+ }
152
+ }
153
+
154
+ @media (min-width: 1024px) {
155
+ .filter-controls {
156
+ display: flex;
157
+ }
158
+ }
159
+
17
160
  .filter-controls {
18
- display: flex;
161
+ flex: 0 0 100%;
19
162
  flex-wrap: wrap;
20
163
  gap: 0.75rem;
164
+ padding: 0.25rem 0;
21
165
  }
22
166
 
23
167
  .filter-input {
@@ -33,35 +177,21 @@
33
177
  transition: filter 0.15s ease-in-out;
34
178
  opacity: 0.7;
35
179
  filter: grayscale(100%);
36
- color: #334155;
37
- background-color: #f1f5f9;
38
- border: 1px solid #cbd5e1;
39
- }
40
-
41
- @media (prefers-color-scheme: dark) {
42
- .filter-chip {
43
- color: #cbd5e1;
44
- background-color: #1e293b;
45
- border-color: #475569;
46
- }
180
+ color: var(--rn-chip-color, #334155);
181
+ background-color: var(--rn-chip-bg, #f1f5f9);
182
+ border: 1px solid var(--rn-chip-border, #cbd5e1);
47
183
  }
48
184
 
49
185
  .dark .filter-chip {
50
- color: #cbd5e1;
51
- background-color: #1e293b;
52
- border-color: #475569;
186
+ color: var(--rn-chip-color, #cbd5e1);
187
+ background-color: var(--rn-chip-bg, #1e293b);
188
+ border-color: var(--rn-chip-border, #475569);
53
189
  }
54
190
 
55
191
  .filter-chip:hover {
56
192
  filter: grayscale(100%) brightness(0.95);
57
193
  }
58
194
 
59
- @media (prefers-color-scheme: dark) {
60
- .filter-chip:hover {
61
- filter: grayscale(100%) brightness(1.1);
62
- }
63
- }
64
-
65
195
  .dark .filter-chip:hover {
66
196
  filter: grayscale(100%) brightness(1.1);
67
197
  }
@@ -74,6 +204,20 @@
74
204
  color: #fff !important;
75
205
  }
76
206
 
207
+ /* ---------------------------------------------------------------
208
+ Release note rows: per-category --cat-color defaults
209
+ These are overridden when the caller sets --cat-color inline.
210
+ --------------------------------------------------------------- */
211
+
212
+ .issue-row[data-category="new-feature"] { --cat-color: var(--rn-color-new-feature, #16a34a); }
213
+ .issue-row[data-category="enhancement"] { --cat-color: var(--rn-color-enhancement, #2563eb); }
214
+ .issue-row[data-category="fix"] { --cat-color: var(--rn-color-fix, #d97706); }
215
+ .issue-row[data-category="security"] { --cat-color: var(--rn-color-security, #566f98); }
216
+ .issue-row[data-category="deprecated"] { --cat-color: var(--rn-color-deprecated, #ffcc48); }
217
+ .issue-row[data-category="breaking-change"] { --cat-color: var(--rn-color-breaking-change, #aaaa33); }
218
+ .issue-row[data-category="known-issue"] { --cat-color: var(--rn-color-known-issue, #bb4448); }
219
+ .issue-row[data-category="internal"] { --cat-color: var(--rn-color-internal, #71717a); }
220
+
77
221
  /* ---------------------------------------------------------------
78
222
  Release note rows
79
223
  Note: per-category show/hide rules are generated dynamically
@@ -89,7 +233,7 @@
89
233
  transition: border-color 0.2s;
90
234
  padding: 6px 8px;
91
235
  border: 2px solid transparent;
92
- scroll-margin-top: 5rem;
236
+ scroll-margin-top: var(--release-notes-scroll-margin, 5rem);
93
237
  }
94
238
 
95
239
  @media (min-width: 768px) {
@@ -114,15 +258,22 @@
114
258
  text-decoration: none;
115
259
  cursor: pointer;
116
260
  user-select: none;
117
- transition: transform 0.15s ease-out;
261
+ transition: transform 0.15s ease-out, color 0.15s ease-out;
118
262
  transform-origin: left center;
119
263
  background-color: color-mix(in srgb, var(--cat-color) 13%, transparent);
120
264
  color: var(--cat-color);
121
265
  border: 1px solid color-mix(in srgb, var(--cat-color) 27%, transparent);
122
266
  }
123
267
 
124
- .issue-badge:hover { transform: scale(1.05); }
268
+ .issue-badge:hover {
269
+ transform: scale(1.05);
270
+ color: var(--release-notes-link-hover-color, #9a3412);
271
+ text-decoration: none;
272
+ }
125
273
  .issue-badge:active { transform: scale(0.95); }
274
+ .dark .issue-badge:hover {
275
+ color: var(--release-notes-link-hover-color-dark, #fed7aa);
276
+ }
126
277
 
127
278
  .issue-content {
128
279
  flex: 1;
@@ -145,29 +296,35 @@
145
296
  color: #475569;
146
297
  }
147
298
 
148
- @media (prefers-color-scheme: dark) {
149
- .rni-thanks { color: #94a3b8; }
150
- }
151
-
152
299
  .dark .rni-thanks { color: #94a3b8; }
153
300
 
154
301
  .github-link {
155
302
  flex-shrink: 0;
156
303
  font-size: 0.875rem;
157
304
  text-decoration: none;
158
- opacity: 0.7;
159
- color: inherit;
305
+ color: var(--release-notes-link-color, #4338ca);
306
+ transition: color 0.15s;
160
307
  }
161
308
 
162
- .github-link:hover { opacity: 1; text-decoration: underline; }
309
+ .github-link:hover {
310
+ color: var(--release-notes-link-hover-color, #9a3412);
311
+ text-decoration: none;
312
+ }
313
+
314
+ .dark .github-link {
315
+ color: var(--release-notes-link-color-dark, #818cf8);
316
+ }
317
+
318
+ .dark .github-link:hover {
319
+ color: var(--release-notes-link-hover-color-dark, #fed7aa);
320
+ }
163
321
 
164
322
  /* ---------------------------------------------------------------
165
323
  Version articles
166
324
  --------------------------------------------------------------- */
167
325
 
168
326
  .version {
169
- content-visibility: auto;
170
- contain-intrinsic-size: 0 800px;
327
+ scroll-margin-top: var(--release-notes-scroll-margin, 5rem);
171
328
  }
172
329
 
173
330
  .version-header {
@@ -181,10 +338,6 @@
181
338
  border-bottom: 1px solid #e2e8f0;
182
339
  }
183
340
 
184
- @media (prefers-color-scheme: dark) {
185
- .version-header { border-bottom-color: #1e293b; }
186
- }
187
-
188
341
  .dark .version-header { border-bottom-color: #1e293b; }
189
342
 
190
343
  .version-date {
@@ -193,42 +346,19 @@
193
346
  }
194
347
 
195
348
  /* ---------------------------------------------------------------
196
- Version selector (jump dropdown)
349
+ Version anchor links (# permalink)
197
350
  --------------------------------------------------------------- */
198
351
 
199
- .release-selector {
200
- position: sticky;
201
- top: 2.75rem;
202
- z-index: 10;
203
- padding: 0.75rem 0;
204
- }
205
-
206
- .release-selector-select {
207
- width: 100%;
208
- height: 2.5rem;
209
- padding: 0 0.5rem;
210
- border-radius: 6px;
211
- border: 2px solid #d1d5db;
212
- background-color: #f3f4f6;
213
- color: #111827;
214
- cursor: pointer;
215
- }
216
-
217
- .release-selector-select:focus {
218
- outline: 2px solid #6366f1;
219
- outline-offset: 1px;
220
- }
221
-
222
- @media (prefers-color-scheme: dark) {
223
- .release-selector-select {
224
- border-color: #4b5563;
225
- background-color: #1e293b;
226
- color: #cbd5e1;
227
- }
352
+ .release-note .anchor-link {
353
+ opacity: 0;
354
+ text-decoration: none;
355
+ border: none;
356
+ margin-left: 0.5rem;
357
+ color: inherit;
358
+ transition: opacity 0.15s ease;
228
359
  }
229
360
 
230
- .dark .release-selector-select {
231
- border-color: #4b5563;
232
- background-color: #1e293b;
233
- color: #cbd5e1;
361
+ .release-note:hover .anchor-link,
362
+ .release-note .anchor-link:focus-visible {
363
+ opacity: 1;
234
364
  }