@jjlmoya/utils-astronomy 1.41.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.
@@ -7,14 +7,33 @@ import type { KnownLocale, CategoryLocaleContent } from "../types";
7
7
  import { getCategoryPath } from "../mfe/routes";
8
8
  import { getCategoryUi } from "../mfe/category-ui";
9
9
 
10
- interface CategoryTool { id: string; icon: string; title: string; description: string; href: string; }
11
- interface Props { locale: KnownLocale; category: CategoryLocaleContent; tools: CategoryTool[]; alternates: { language: KnownLocale; url: string }[]; image?: string | undefined; }
10
+ interface CategoryTool {
11
+ id: string;
12
+ icon: string;
13
+ title: string;
14
+ description: string;
15
+ href: string;
16
+ }
17
+ interface Props {
18
+ locale: KnownLocale;
19
+ category: CategoryLocaleContent;
20
+ tools: CategoryTool[];
21
+ alternates: { language: KnownLocale; url: string }[];
22
+ image?: string | undefined;
23
+ }
12
24
  const { locale, category, tools, alternates, image } = Astro.props;
13
25
  const publicPath = getCategoryPath(locale, category.slug);
14
26
  const ui = getCategoryUi(locale);
15
27
  ---
16
28
 
17
- <ProductionPage title={category.title} description={category.description} {locale} {publicPath} {alternates} {image}>
29
+ <ProductionPage
30
+ title={category.title}
31
+ description={category.description}
32
+ {locale}
33
+ {publicPath}
34
+ {alternates}
35
+ {image}
36
+ >
18
37
  <div class="category-production">
19
38
  <div class="category-production-inner">
20
39
  <ProductionBreadcrumb locale={locale} currentTitle={category.title} />
@@ -22,16 +41,29 @@ const ui = getCategoryUi(locale);
22
41
  <h1>{category.title}</h1><p>{category.description}</p>
23
42
  </header>
24
43
  <div class="category-tools-grid">
25
- {tools.map((tool, index) => (
26
- <a href={tool.href} class="category-tool-card">
27
- <span class="category-tool-index">{String(index + 1).padStart(2, "0")}</span>
28
- <div class="category-tool-icon"><Icon name={tool.icon} /></div>
29
- <h2>{tool.title}</h2><p>{tool.description}</p>
30
- <span class="category-tool-cta">{ui.useTool} <span aria-hidden="true">→</span></span>
31
- </a>
32
- ))}
44
+ {
45
+ tools.map((tool, index) => (
46
+ <a href={tool.href} class="category-tool-card">
47
+ <span class="category-tool-index">
48
+ {String(index + 1).padStart(2, "0")}
49
+ </span>
50
+ <div class="category-tool-icon">
51
+ <Icon name={tool.icon} />
52
+ </div>
53
+ <>
54
+ <h2>{tool.title}</h2>
55
+ <p>{tool.description}</p>
56
+ </>
57
+ <span class="category-tool-cta">
58
+ {ui.useTool} <span aria-hidden="true">→</span>
59
+ </span>
60
+ </a>
61
+ ))
62
+ }
33
63
  </div>
34
- <section class="category-seo-production"><SEORenderer content={{ locale, sections: category.seo }} /></section>
64
+ <section class="category-seo-production">
65
+ <SEORenderer content={{ locale, sections: category.seo }} />
66
+ </section>
35
67
  </div>
36
68
  </div>
37
69
  </ProductionPage>
@@ -40,7 +72,12 @@ const ui = getCategoryUi(locale);
40
72
  .category-production {
41
73
  min-height: 100vh;
42
74
  overflow-x: clip;
43
- background: radial-gradient(ellipse at top, var(--primary-10), var(--bg-surface) 50%, var(--bg-main));
75
+ background: radial-gradient(
76
+ ellipse at top,
77
+ var(--primary-10),
78
+ var(--bg-surface) 50%,
79
+ var(--bg-main)
80
+ );
44
81
  }
45
82
 
46
83
  .category-production-inner {
@@ -86,24 +123,35 @@ const ui = getCategoryUi(locale);
86
123
  min-height: 230px;
87
124
  flex-direction: column;
88
125
  padding: var(--space-lg);
89
- border: 1px solid color-mix(in srgb, var(--astronomy-accent) 18%, var(--border-base));
126
+ border: 1px solid
127
+ color-mix(in srgb, var(--astronomy-accent) 18%, var(--border-base));
90
128
  border-radius: 1.25rem;
91
- background: linear-gradient(145deg, var(--bg-surface), color-mix(in srgb, var(--astronomy-accent) 4%, var(--bg-surface)));
129
+ background: linear-gradient(
130
+ 145deg,
131
+ var(--bg-surface),
132
+ color-mix(in srgb, var(--astronomy-accent) 4%, var(--bg-surface))
133
+ );
92
134
  color: inherit;
93
135
  text-decoration: none;
94
- box-shadow: 0 1rem 2rem color-mix(in srgb, var(--shadow-base, #000) 35%, transparent);
95
- transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
136
+ box-shadow: 0 1rem 2rem
137
+ color-mix(in srgb, var(--shadow-base, #000) 35%, transparent);
138
+ transition:
139
+ transform 0.25s ease,
140
+ border-color 0.25s ease,
141
+ box-shadow 0.25s ease;
96
142
  }
97
143
 
98
144
  .category-tool-card:hover,
99
145
  .category-tool-card:focus-visible {
100
146
  border-color: var(--astronomy-accent);
101
- box-shadow: 0 1.25rem 2.5rem color-mix(in srgb, var(--astronomy-accent) 18%, transparent);
147
+ box-shadow: 0 1.25rem 2.5rem
148
+ color-mix(in srgb, var(--astronomy-accent) 18%, transparent);
102
149
  transform: translateY(-4px);
103
150
  }
104
151
 
105
152
  .category-tool-card:focus-visible {
106
- outline: 3px solid color-mix(in srgb, var(--astronomy-accent) 55%, transparent);
153
+ outline: 3px solid
154
+ color-mix(in srgb, var(--astronomy-accent) 55%, transparent);
107
155
  outline-offset: 4px;
108
156
  }
109
157
 
@@ -127,9 +175,14 @@ const ui = getCategoryUi(locale);
127
175
  height: 3.25rem;
128
176
  place-items: center;
129
177
  margin-bottom: var(--space-md);
130
- border: 1px solid color-mix(in srgb, var(--astronomy-accent) 28%, transparent);
178
+ border: 1px solid
179
+ color-mix(in srgb, var(--astronomy-accent) 28%, transparent);
131
180
  border-radius: 1rem;
132
- background: color-mix(in srgb, var(--astronomy-accent) 12%, var(--bg-surface));
181
+ background: color-mix(
182
+ in srgb,
183
+ var(--astronomy-accent) 12%,
184
+ var(--bg-surface)
185
+ );
133
186
  color: var(--astronomy-accent);
134
187
  font-size: 1.6rem;
135
188
  }
@@ -139,7 +192,6 @@ const ui = getCategoryUi(locale);
139
192
  top: var(--space-md);
140
193
  right: var(--space-lg);
141
194
  color: var(--text-dimmed, var(--text-muted));
142
- font-family: ui-monospace, SFMono-Regular, monospace;
143
195
  font-size: 0.72rem;
144
196
  letter-spacing: 0.12em;
145
197
  }
@@ -178,7 +230,12 @@ const ui = getCategoryUi(locale);
178
230
  }
179
231
 
180
232
  @media (width <= 768px) {
181
- .category-production-inner { padding: var(--space-lg) var(--space-sm) var(--space-3xl); }
182
- .category-tools-grid { grid-template-columns: 1fr; }
233
+ .category-production-inner {
234
+ padding: var(--space-lg) var(--space-sm) var(--space-3xl);
235
+ }
236
+
237
+ .category-tools-grid {
238
+ grid-template-columns: 1fr;
239
+ }
183
240
  }
184
241
  </style>
@@ -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>