@jjlmoya/utils-astronomy 1.40.0 → 1.42.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.
@@ -10,10 +10,44 @@ import { getCategoryUi } from "../mfe/category-ui";
10
10
  import { getCategoryPath, getUtilityPath } from "../mfe/routes";
11
11
  import { getUtilityCssPath, TABLETOP_ASSET_VERSION } from "../mfe/assets";
12
12
  import type { AstroComponentFactory } from "astro/runtime/server/index.js";
13
-
14
- interface RelatedTool { title: string; description: string; href: string; icon: string; }
15
- interface Props { locale: KnownLocale; content: ToolLocaleContent; englishSlug: string; categoryTitle: string; categorySlug: string; categoryHref: string; relatedTools: RelatedTool[]; Component: AstroComponentFactory; SEOComponent: AstroComponentFactory; BibliographyComponent: AstroComponentFactory; alternates: { language: KnownLocale; url: string }[]; image?: string | undefined; }
16
- const { locale, content, englishSlug, categoryTitle, categorySlug, categoryHref, relatedTools, Component, SEOComponent, BibliographyComponent, alternates, image } = Astro.props;
13
+ import "./production-utility-page.css";
14
+
15
+ interface RelatedTool {
16
+ title: string;
17
+ description: string;
18
+ href: string;
19
+ icon: string;
20
+ }
21
+
22
+ interface Props {
23
+ locale: KnownLocale;
24
+ content: ToolLocaleContent;
25
+ englishSlug: string;
26
+ categoryTitle: string;
27
+ categorySlug: string;
28
+ categoryHref: string;
29
+ relatedTools: RelatedTool[];
30
+ Component: AstroComponentFactory;
31
+ SEOComponent: AstroComponentFactory;
32
+ BibliographyComponent: AstroComponentFactory;
33
+ alternates: { language: KnownLocale; url: string }[];
34
+ image?: string | undefined;
35
+ }
36
+
37
+ const {
38
+ locale,
39
+ content,
40
+ englishSlug,
41
+ categoryTitle,
42
+ categorySlug,
43
+ categoryHref,
44
+ relatedTools,
45
+ Component,
46
+ SEOComponent,
47
+ BibliographyComponent,
48
+ alternates,
49
+ image,
50
+ } = Astro.props;
17
51
  const words = content.title.split(/\s+/).filter(Boolean);
18
52
  const titleHighlight = words[0] ?? content.title;
19
53
  const titleBase = words.slice(1).join(" ");
@@ -22,43 +56,133 @@ const publicCategoryPath = getCategoryPath(locale, categorySlug);
22
56
  const ui = getCategoryUi(locale);
23
57
  ---
24
58
 
25
- <ProductionPage title={content.title} description={content.description} {locale} {publicPath} manifestPath={`${publicPath}manifest.json?version=${TABLETOP_ASSET_VERSION}`} {alternates} {image} toolCss={getUtilityCssPath(englishSlug)}>
59
+ <ProductionPage
60
+ title={content.title}
61
+ description={content.description}
62
+ {locale}
63
+ {publicPath}
64
+ manifestPath={`${publicPath}manifest.json?version=${TABLETOP_ASSET_VERSION}`}
65
+ {alternates}
66
+ {image}
67
+ toolCss={getUtilityCssPath(englishSlug)}
68
+ >
26
69
  <div class="utility-production">
27
70
  <div class="utility-production-inner">
28
- <ProductionBreadcrumb locale={locale} categoryTitle={categoryTitle} categoryHref={categoryHref || publicCategoryPath} currentTitle={content.title} />
29
- <UtilityHeader titleHighlight={titleHighlight} titleBase={titleBase} description={content.description} gradientFrom="#10b981" gradientTo="#0d9488" />
30
- <section class="utility-tool-production"><Component ui={content.ui} locale={locale} /></section>
71
+ <ProductionBreadcrumb
72
+ locale={locale}
73
+ categoryTitle={categoryTitle}
74
+ categoryHref={categoryHref || publicCategoryPath}
75
+ currentTitle={content.title}
76
+ />
77
+ <UtilityHeader
78
+ titleHighlight={titleHighlight}
79
+ titleBase={titleBase}
80
+ description={content.description}
81
+ gradientFrom="#10b981"
82
+ gradientTo="#0d9488"
83
+ />
84
+ <section class="utility-tool-production">
85
+ <Component ui={content.ui} locale={locale} />
86
+ </section>
31
87
  <div class="utility-zoom-production" aria-label={ui.zoomControls}>
32
88
  <span class="utility-zoom-label">{ui.zoom}</span>
33
- <div class="utility-zoom-controls"><button type="button" data-zoom="out" aria-label={ui.zoomOut}>−</button><button type="button" data-zoom="reset" aria-label={ui.resetZoom}>100%</button><button type="button" data-zoom="in" aria-label={ui.zoomIn}>+</button></div>
89
+ <div class="utility-zoom-controls">
90
+ <button type="button" data-zoom="out" aria-label={ui.zoomOut}>−</button>
91
+ <button type="button" data-zoom="reset" aria-label={ui.resetZoom}>100%</button>
92
+ <button type="button" data-zoom="in" aria-label={ui.zoomIn}>+</button>
93
+ </div>
34
94
  </div>
35
95
  <ProductionWidget locale={locale} slug={content.slug} />
36
- {content.faq.length > 0 && <section class="utility-section-production"><FAQSection items={content.faq} /></section>}
96
+ {
97
+ content.faq.length > 0 && (
98
+ <section class="utility-section-production">
99
+ <FAQSection items={content.faq} />
100
+ </section>
101
+ )
102
+ }
37
103
  <Fragment slot="head">
38
- <ProductionStructuredData locale={locale} {content} {publicPath} categoryTitle={categoryTitle} categoryHref={categoryHref || publicCategoryPath} />
104
+ <ProductionStructuredData
105
+ locale={locale}
106
+ {content}
107
+ {publicPath}
108
+ categoryTitle={categoryTitle}
109
+ categoryHref={categoryHref || publicCategoryPath}
110
+ />
39
111
  </Fragment>
40
- <section class="utility-section-production"><SEOComponent locale={locale} /></section>
41
- {content.bibliography.length > 0 && <section class="utility-section-production"><BibliographyComponent locale={locale} /></section>}
42
- {relatedTools.length > 0 && <section class="utility-related-production" aria-labelledby="related-tools-title"><div class="utility-related-heading"><span class="utility-related-eyebrow">{ui.relatedEyebrow}</span><h2 id="related-tools-title">{ui.moreToolsIn} {categoryTitle}</h2></div><div class="utility-related-grid">{relatedTools.map((tool, index) => <a href={tool.href} class="utility-related-card"><span class="utility-related-index">{String(index + 1).padStart(2, "0")}</span><span class="utility-related-icon"><Icon name={tool.icon} /></span><h3>{tool.title}</h3><p>{tool.description}</p><span class="utility-related-cta">{ui.openTool} <Icon name="mdi:arrow-top-right" /></span></a>)}</div></section>}
112
+ <section class="utility-section-production">
113
+ <SEOComponent locale={locale} />
114
+ </section>
115
+ {
116
+ content.bibliography.length > 0 && (
117
+ <section class="utility-section-production">
118
+ <BibliographyComponent locale={locale} />
119
+ </section>
120
+ )
121
+ }
122
+ {
123
+ relatedTools.length > 0 && (
124
+ <section class="utility-related-production" aria-labelledby="related-tools-title">
125
+ <div class="utility-related-heading">
126
+ <span class="utility-related-eyebrow">{ui.relatedEyebrow}</span>
127
+ <h2 id="related-tools-title">{ui.moreToolsIn} {categoryTitle}</h2>
128
+ </div>
129
+ <div class="utility-related-grid">
130
+ {relatedTools.map((tool, index) => (
131
+ <a href={tool.href} class="utility-related-card">
132
+ <span class="utility-related-index">{String(index + 1).padStart(2, "0")}</span>
133
+ <span class="utility-related-icon"><Icon name={tool.icon} /></span>
134
+ <h3>{tool.title}</h3>
135
+ <p>{tool.description}</p>
136
+ <span class="utility-related-cta">
137
+ {ui.openTool} <Icon name="mdi:arrow-top-right" />
138
+ </span>
139
+ </a>
140
+ ))}
141
+ </div>
142
+ </section>
143
+ )
144
+ }
43
145
  </div>
44
146
  </div>
45
147
  </ProductionPage>
46
148
 
47
149
  <script>
48
150
  import { observeWidgetHeight } from "../mfe/widget-height";
49
- const isWidget = new URLSearchParams(window.location.search).get("widget") === "true";
151
+
152
+ const isWidget =
153
+ new URLSearchParams(window.location.search).get("widget") === "true";
50
154
  const page = document.querySelector<HTMLElement>(".utility-production");
51
- const container = document.querySelector<HTMLElement>(".utility-tool-production");
52
- const zoomButton = document.querySelector<HTMLButtonElement>('[data-zoom="reset"]');
53
- const storedZoom = isWidget ? 1 : Number(localStorage.getItem("gamebob-utility-zoom") ?? 1);
54
- let zoom = Number.isFinite(storedZoom) ? Math.min(1.5, Math.max(0.75, storedZoom)) : 1;
155
+ const container = document.querySelector<HTMLElement>(
156
+ ".utility-tool-production",
157
+ );
158
+ const zoomButton = document.querySelector<HTMLButtonElement>(
159
+ '[data-zoom="reset"]',
160
+ );
161
+ const storedZoom = isWidget
162
+ ? 1
163
+ : Number(localStorage.getItem("gamebob-utility-zoom") ?? 1);
164
+ let zoom = Number.isFinite(storedZoom)
165
+ ? Math.min(1.5, Math.max(0.75, storedZoom))
166
+ : 1;
55
167
  const applyZoom = () => {
56
168
  if (container) container.style.zoom = String(zoom);
57
169
  if (zoomButton) zoomButton.textContent = `${Math.round(zoom * 100)}%`;
58
170
  };
59
- document.querySelector('[data-zoom="out"]')?.addEventListener("click", () => { zoom = Math.max(0.75, zoom - 0.1); applyZoom(); localStorage.setItem("gamebob-utility-zoom", String(zoom)); });
60
- document.querySelector('[data-zoom="in"]')?.addEventListener("click", () => { zoom = Math.min(1.5, zoom + 0.1); applyZoom(); localStorage.setItem("gamebob-utility-zoom", String(zoom)); });
61
- document.querySelector('[data-zoom="reset"]')?.addEventListener("click", () => { zoom = 1; applyZoom(); localStorage.removeItem("gamebob-utility-zoom"); });
171
+ document.querySelector('[data-zoom="out"]')?.addEventListener("click", () => {
172
+ zoom = Math.max(0.75, zoom - 0.1);
173
+ applyZoom();
174
+ localStorage.setItem("gamebob-utility-zoom", String(zoom));
175
+ });
176
+ document.querySelector('[data-zoom="in"]')?.addEventListener("click", () => {
177
+ zoom = Math.min(1.5, zoom + 0.1);
178
+ applyZoom();
179
+ localStorage.setItem("gamebob-utility-zoom", String(zoom));
180
+ });
181
+ document.querySelector('[data-zoom="reset"]')?.addEventListener("click", () => {
182
+ zoom = 1;
183
+ applyZoom();
184
+ localStorage.removeItem("gamebob-utility-zoom");
185
+ });
62
186
  if (isWidget) {
63
187
  document.body.classList.add("utility-widget-body");
64
188
  page?.classList.add("utility-widget-mode");
@@ -66,218 +190,3 @@ const ui = getCategoryUi(locale);
66
190
  applyZoom();
67
191
  if (isWidget && container) observeWidgetHeight(container);
68
192
  </script>
69
-
70
- <style>
71
- .utility-production {
72
- min-height: 100vh;
73
- overflow-x: clip;
74
- background: radial-gradient(ellipse at top, var(--primary-10), var(--bg-surface) 50%, var(--bg-main));
75
- }
76
-
77
- .utility-production.utility-widget-mode {
78
- min-height: auto;
79
- background: transparent;
80
- }
81
-
82
- .utility-production.utility-widget-mode .utility-production-inner {
83
- max-width: none;
84
- padding: 0;
85
- }
86
-
87
- .utility-production.utility-widget-mode .utility-tool-production {
88
- margin-top: 0;
89
- }
90
-
91
- :global(.utility-production.utility-widget-mode .utility-production-inner > :not(.utility-tool-production)) {
92
- display: none;
93
- }
94
-
95
- :global(body.utility-widget-body) {
96
- margin: 0;
97
- padding-top: 0;
98
- background: transparent;
99
- }
100
-
101
- :global(body.utility-widget-body > :not(main)) {
102
- display: none;
103
- }
104
-
105
- :global(body.utility-widget-body main), :global(body.utility-widget-body main .utility-production), :global(body.utility-widget-body main .utility-production-inner) {
106
- min-height: auto;
107
- }
108
-
109
- .utility-production-inner {
110
- max-width: 72rem;
111
- margin: 0 auto;
112
- padding: var(--space-xl) var(--space-lg) var(--space-4xl);
113
- }
114
-
115
- .utility-tool-production {
116
- width: 100%;
117
- margin-top: var(--space-2xl);
118
- }
119
-
120
- .utility-zoom-production {
121
- display: flex;
122
- align-items: center;
123
- justify-content: center;
124
- gap: 0.75rem;
125
- margin: var(--space-xl) auto 0;
126
- color: var(--text-muted);
127
- font-size: 0.8rem;
128
- font-weight: 700;
129
- letter-spacing: 0.08em;
130
- text-transform: uppercase;
131
- }
132
-
133
- .utility-zoom-controls {
134
- display: inline-flex;
135
- overflow: hidden;
136
- border: 1px solid var(--border-base);
137
- border-radius: 0.75rem;
138
- background: color-mix(in srgb, var(--bg-surface) 90%, transparent);
139
- box-shadow: 0 0.5rem 1.5rem color-mix(in srgb, var(--shadow-base, #000) 35%, transparent);
140
- }
141
-
142
- .utility-zoom-production button {
143
- min-width: 2.5rem;
144
- border: 0;
145
- border-right: 1px solid var(--border-base);
146
- padding: 0.5rem 0.75rem;
147
- background: transparent;
148
- color: var(--text-main);
149
- cursor: pointer;
150
- font-size: 1rem;
151
- }
152
- .utility-zoom-production button:last-child { border-right: 0; }
153
-
154
- .utility-zoom-production button:hover,
155
- .utility-zoom-production button:focus-visible {
156
- background: color-mix(in srgb, var(--astronomy-accent) 12%, transparent);
157
- color: var(--astronomy-accent);
158
- }
159
-
160
- .utility-section-production, .utility-related-production {
161
- max-width: 56rem;
162
- margin: var(--space-4xl) auto 0;
163
- }
164
-
165
- .utility-section-production {
166
- font-size: 1.125rem;
167
- line-height: 1.75;
168
- }
169
-
170
- .utility-related-heading {
171
- margin-bottom: var(--space-lg);
172
- text-align: center;
173
- }
174
-
175
- .utility-related-eyebrow {
176
- display: block;
177
- margin-bottom: 0.45rem;
178
- color: var(--astronomy-accent);
179
- font-size: 0.75rem;
180
- font-weight: 800;
181
- letter-spacing: 0.14em;
182
- text-transform: uppercase;
183
- }
184
-
185
- .utility-related-production h2 {
186
- margin: 0;
187
- color: var(--text-main);
188
- font-size: clamp(1.75rem, 4vw, 2.5rem);
189
- }
190
-
191
- .utility-related-grid {
192
- display: grid;
193
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
194
- gap: 1rem;
195
- }
196
-
197
- .utility-related-card {
198
- position: relative;
199
- display: flex;
200
- min-height: 238px;
201
- flex-direction: column;
202
- overflow: hidden;
203
- padding: 1.25rem;
204
- border: 1px solid color-mix(in srgb, var(--astronomy-accent) 18%, var(--border-base));
205
- border-radius: 1rem;
206
- background: linear-gradient(145deg, var(--bg-surface), color-mix(in srgb, var(--astronomy-accent) 4%, var(--bg-surface)));
207
- color: inherit;
208
- text-decoration: none;
209
- box-shadow: 0 0.75rem 1.75rem color-mix(in srgb, var(--shadow-base, #000) 30%, transparent);
210
- transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
211
- }
212
-
213
- .utility-related-card:hover,
214
- .utility-related-card:focus-visible {
215
- border-color: var(--astronomy-accent);
216
- box-shadow: 0 1rem 2rem color-mix(in srgb, var(--astronomy-accent) 16%, transparent);
217
- transform: translateY(-3px);
218
- }
219
-
220
- .utility-related-card:focus-visible {
221
- outline: 3px solid color-mix(in srgb, var(--astronomy-accent) 55%, transparent);
222
- outline-offset: 3px;
223
- }
224
-
225
- .utility-related-index {
226
- position: absolute;
227
- top: 1.25rem;
228
- right: 1.25rem;
229
- color: var(--text-dimmed, var(--text-muted));
230
- font-family: ui-monospace, SFMono-Regular, monospace;
231
- font-size: 0.7rem;
232
- letter-spacing: 0.12em;
233
- }
234
-
235
- .utility-related-icon {
236
- display: grid;
237
- width: 2.75rem;
238
- height: 2.75rem;
239
- place-items: center;
240
- margin-bottom: 1rem;
241
- border: 1px solid color-mix(in srgb, var(--astronomy-accent) 28%, transparent);
242
- border-radius: 0.8rem;
243
- background: color-mix(in srgb, var(--astronomy-accent) 12%, var(--bg-surface));
244
- color: var(--astronomy-accent);
245
- font-size: 1.35rem;
246
- }
247
-
248
- .utility-related-card h3 {
249
- margin: 0 0 0.5rem;
250
- color: var(--text-main);
251
- font-size: 1rem;
252
- }
253
-
254
- .utility-related-card p {
255
- margin: 0 0 1rem;
256
- color: var(--text-muted);
257
- font-size: 0.9rem;
258
- line-height: 1.5;
259
- }
260
-
261
- .utility-related-cta {
262
- display: inline-flex;
263
- align-items: center;
264
- gap: 0.4rem;
265
- margin-top: auto;
266
- color: var(--astronomy-accent);
267
- font-size: 0.75rem;
268
- font-weight: 800;
269
- letter-spacing: 0.08em;
270
- text-transform: uppercase;
271
- }
272
-
273
- .utility-related-cta svg {
274
- width: 1rem;
275
- height: 1rem;
276
- transition: transform 0.2s ease;
277
- }
278
- .utility-related-card:hover .utility-related-cta svg { transform: translate(2px, -2px); }
279
-
280
- @media (width <= 640px) {
281
- .utility-production-inner { padding: var(--space-lg) var(--space-sm) var(--space-3xl); }
282
- }
283
- </style>
@@ -0,0 +1,248 @@
1
+ .utility-production {
2
+ min-height: 100vh;
3
+ overflow-x: clip;
4
+ background: radial-gradient(
5
+ ellipse at top,
6
+ var(--primary-10),
7
+ var(--bg-surface) 50%,
8
+ var(--bg-main)
9
+ );
10
+ }
11
+
12
+ .utility-production.utility-widget-mode {
13
+ min-height: auto;
14
+ background: transparent;
15
+ }
16
+
17
+ .utility-production.utility-widget-mode .utility-production-inner {
18
+ max-width: none;
19
+ padding: 0;
20
+ }
21
+
22
+ .utility-production.utility-widget-mode .utility-tool-production {
23
+ margin-top: 0;
24
+ }
25
+
26
+ :global(
27
+ .utility-production.utility-widget-mode
28
+ .utility-production-inner
29
+ > :not(.utility-tool-production)
30
+ ) {
31
+ display: none;
32
+ }
33
+
34
+ :global(body.utility-widget-body) {
35
+ margin: 0;
36
+ padding-top: 0;
37
+ background: transparent;
38
+ }
39
+
40
+ :global(body.utility-widget-body > :not(main)) {
41
+ display: none;
42
+ }
43
+
44
+ :global(body.utility-widget-body main),
45
+ :global(body.utility-widget-body main .utility-production),
46
+ :global(body.utility-widget-body main .utility-production-inner) {
47
+ min-height: auto;
48
+ }
49
+
50
+ .utility-production-inner {
51
+ max-width: 72rem;
52
+ margin: 0 auto;
53
+ padding: var(--space-xl) var(--space-lg) var(--space-4xl);
54
+ }
55
+
56
+ .utility-tool-production {
57
+ width: 100%;
58
+ margin-top: var(--space-2xl);
59
+ }
60
+
61
+ .utility-zoom-production {
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: center;
65
+ gap: 0.75rem;
66
+ margin: var(--space-xl) auto 0;
67
+ color: var(--text-muted);
68
+ font-size: 0.8rem;
69
+ font-weight: 700;
70
+ letter-spacing: 0.08em;
71
+ text-transform: uppercase;
72
+ }
73
+
74
+ .utility-zoom-controls {
75
+ display: inline-flex;
76
+ overflow: hidden;
77
+ border: 1px solid var(--border-base);
78
+ border-radius: 0.75rem;
79
+ background: color-mix(in srgb, var(--bg-surface) 90%, transparent);
80
+ box-shadow: 0 0.5rem 1.5rem
81
+ color-mix(in srgb, var(--shadow-base, #000) 35%, transparent);
82
+ }
83
+
84
+ .utility-zoom-production button {
85
+ min-width: 2.5rem;
86
+ border: 0;
87
+ border-right: 1px solid var(--border-base);
88
+ padding: 0.5rem 0.75rem;
89
+ background: transparent;
90
+ color: var(--text-main);
91
+ cursor: pointer;
92
+ font-size: 1rem;
93
+ }
94
+
95
+ .utility-zoom-production button:last-child {
96
+ border-right: 0;
97
+ }
98
+
99
+ .utility-zoom-production button:hover,
100
+ .utility-zoom-production button:focus-visible {
101
+ background: color-mix(in srgb, var(--astronomy-accent) 12%, transparent);
102
+ color: var(--astronomy-accent);
103
+ }
104
+
105
+ .utility-section-production,
106
+ .utility-related-production {
107
+ max-width: 56rem;
108
+ margin: var(--space-4xl) auto 0;
109
+ }
110
+
111
+ .utility-section-production {
112
+ font-size: 1.125rem;
113
+ line-height: 1.75;
114
+ }
115
+
116
+ .utility-related-heading {
117
+ margin-bottom: var(--space-lg);
118
+ text-align: center;
119
+ }
120
+
121
+ .utility-related-eyebrow {
122
+ display: block;
123
+ margin-bottom: 0.45rem;
124
+ color: var(--astronomy-accent);
125
+ font-size: 0.75rem;
126
+ font-weight: 800;
127
+ letter-spacing: 0.14em;
128
+ text-transform: uppercase;
129
+ }
130
+
131
+ .utility-related-production h2 {
132
+ margin: 0;
133
+ color: var(--text-main);
134
+ font-size: clamp(1.75rem, 4vw, 2.5rem);
135
+ }
136
+
137
+ .utility-related-grid {
138
+ display: grid;
139
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
140
+ gap: 1rem;
141
+ }
142
+
143
+ .utility-related-card {
144
+ position: relative;
145
+ display: flex;
146
+ min-height: 238px;
147
+ flex-direction: column;
148
+ overflow: hidden;
149
+ padding: 1.25rem;
150
+ border: 1px solid
151
+ color-mix(in srgb, var(--astronomy-accent) 18%, var(--border-base));
152
+ border-radius: 1rem;
153
+ background: linear-gradient(
154
+ 145deg,
155
+ var(--bg-surface),
156
+ color-mix(in srgb, var(--astronomy-accent) 4%, var(--bg-surface))
157
+ );
158
+ color: inherit;
159
+ text-decoration: none;
160
+ box-shadow: 0 0.75rem 1.75rem
161
+ color-mix(in srgb, var(--shadow-base, #000) 30%, transparent);
162
+ transition:
163
+ transform 0.25s ease,
164
+ border-color 0.25s ease,
165
+ box-shadow 0.25s ease;
166
+ }
167
+
168
+ .utility-related-card:hover,
169
+ .utility-related-card:focus-visible {
170
+ border-color: var(--astronomy-accent);
171
+ box-shadow: 0 1rem 2rem
172
+ color-mix(in srgb, var(--astronomy-accent) 16%, transparent);
173
+ transform: translateY(-3px);
174
+ }
175
+
176
+ .utility-related-card:focus-visible {
177
+ outline: 3px solid
178
+ color-mix(in srgb, var(--astronomy-accent) 55%, transparent);
179
+ outline-offset: 3px;
180
+ }
181
+
182
+ .utility-related-index {
183
+ position: absolute;
184
+ top: 1.25rem;
185
+ right: 1.25rem;
186
+ color: var(--text-dimmed, var(--text-muted));
187
+ font-size: 0.7rem;
188
+ letter-spacing: 0.12em;
189
+ }
190
+
191
+ .utility-related-icon {
192
+ display: grid;
193
+ width: 2.75rem;
194
+ height: 2.75rem;
195
+ place-items: center;
196
+ margin-bottom: 1rem;
197
+ border: 1px solid
198
+ color-mix(in srgb, var(--astronomy-accent) 28%, transparent);
199
+ border-radius: 0.8rem;
200
+ background: color-mix(
201
+ in srgb,
202
+ var(--astronomy-accent) 12%,
203
+ var(--bg-surface)
204
+ );
205
+ color: var(--astronomy-accent);
206
+ font-size: 1.35rem;
207
+ }
208
+
209
+ .utility-related-card h3 {
210
+ margin: 0 0 0.5rem;
211
+ color: var(--text-main);
212
+ font-size: 1rem;
213
+ }
214
+
215
+ .utility-related-card p {
216
+ margin: 0 0 1rem;
217
+ color: var(--text-muted);
218
+ font-size: 0.9rem;
219
+ line-height: 1.5;
220
+ }
221
+
222
+ .utility-related-cta {
223
+ display: inline-flex;
224
+ align-items: center;
225
+ gap: 0.4rem;
226
+ margin-top: auto;
227
+ color: var(--astronomy-accent);
228
+ font-size: 0.75rem;
229
+ font-weight: 800;
230
+ letter-spacing: 0.08em;
231
+ text-transform: uppercase;
232
+ }
233
+
234
+ .utility-related-cta svg {
235
+ width: 1rem;
236
+ height: 1rem;
237
+ transition: transform 0.2s ease;
238
+ }
239
+
240
+ .utility-related-card:hover .utility-related-cta svg {
241
+ transform: translate(2px, -2px);
242
+ }
243
+
244
+ @media (width <= 640px) {
245
+ .utility-production-inner {
246
+ padding: var(--space-lg) var(--space-sm) var(--space-3xl);
247
+ }
248
+ }