@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.
@@ -0,0 +1,224 @@
1
+ ---
2
+ import { Icon } from "astro-icon/components";
3
+ import { SEORenderer } from "@jjlmoya/utils-shared";
4
+ import ProductionBreadcrumb from "../components/ProductionBreadcrumb.astro";
5
+ import ProductionPage from "./ProductionPage.astro";
6
+ import { getCategoryPath } from "../mfe/routes";
7
+ import { getCategoryUi } from "../mfe/category-ui";
8
+ import type { CategoryLocaleContent, KnownLocale } from "../types";
9
+ import "../category/astronomy-category-home.css";
10
+
11
+ interface CategoryTool {
12
+ id: string;
13
+ icon: string;
14
+ title: string;
15
+ description: string;
16
+ href: string;
17
+ }
18
+
19
+ interface Props {
20
+ locale: KnownLocale;
21
+ category: CategoryLocaleContent;
22
+ tools: CategoryTool[];
23
+ alternates: { language: KnownLocale; url: string }[];
24
+ image?: string | undefined;
25
+ }
26
+
27
+ const { locale, category, tools, alternates, image } = Astro.props;
28
+ const publicPath = getCategoryPath(locale, category.slug);
29
+ const ui = getCategoryUi(locale);
30
+ const firstTool = tools[0];
31
+ const constellationTools = tools;
32
+ const getToolPointSize = (count: number): string => {
33
+ if (count > 500) return "0.28rem";
34
+ if (count > 80) return "0.4rem";
35
+ return "0.72rem";
36
+ };
37
+ const toolPointSize = getToolPointSize(tools.length);
38
+ const getStarSize = (index: number): string => {
39
+ if (index % 11 === 0) return "0.28rem";
40
+ if (index % 4 === 0) return "0.16rem";
41
+ return "0.1rem";
42
+ };
43
+ const getStarOpacity = (index: number): string => {
44
+ if (index % 7 === 0) return "0.95";
45
+ if (index % 3 === 0) return "0.65";
46
+ return "0.38";
47
+ };
48
+ const stars = Array.from({ length: 72 }, (_, index) => ({
49
+ left: `${((index * 47) % 97) + 1}%`,
50
+ top: `${((index * 61) % 91) + 3}%`,
51
+ size: getStarSize(index),
52
+ opacity: getStarOpacity(index),
53
+ delay: `${index % 9}`,
54
+ }));
55
+ const getConstellationSide = (left: number, top: number): "left" | "right" | "bottom" | "top" => {
56
+ if (left > 62) return "right";
57
+ if (left < 38) return "left";
58
+ return top > 50 ? "bottom" : "top";
59
+ };
60
+ const handPlacedPositions = [
61
+ [55, 12],
62
+ [80, 28],
63
+ [88, 57],
64
+ [69, 81],
65
+ [43, 91],
66
+ [17, 73],
67
+ [11, 43],
68
+ [30, 20],
69
+ [63, 24],
70
+ [83, 73],
71
+ [25, 57],
72
+ [39, 11],
73
+ ] as const;
74
+ const constellationPositions = constellationTools.map((_, index) => {
75
+ const count = Math.max(constellationTools.length, 1);
76
+ const handPlaced = handPlacedPositions[index];
77
+ const drift = ((index * 17) % 13) - 6;
78
+ const angle = index * 2.399963 + drift * 0.025;
79
+ const radius = 12 + Math.sqrt((index + 0.5) / count) * 34 + drift * 0.25;
80
+ const left = handPlaced?.[0] ?? 50 + Math.cos(angle) * radius;
81
+ const top = handPlaced?.[1] ?? 50 + Math.sin(angle) * radius;
82
+ return {
83
+ left: `${left.toFixed(2)}%`,
84
+ top: `${top.toFixed(2)}%`,
85
+ side: getConstellationSide(left, top),
86
+ };
87
+ });
88
+ ---
89
+
90
+ <ProductionPage
91
+ title={category.title}
92
+ description={category.description}
93
+ {locale}
94
+ {publicPath}
95
+ {alternates}
96
+ {image}
97
+ >
98
+ <div class="astronomy-home" data-astronomy-home>
99
+ <div class="astronomy-page-stars" aria-hidden="true">
100
+ {stars.slice(0, 42).map((star) => (
101
+ <i
102
+ class="astronomy-page-star"
103
+ style={`--star-left: ${star.left}; --star-top: ${star.top}; --star-size: ${star.size}; --star-opacity: ${star.opacity}; --star-delay: ${star.delay}s;`}
104
+ ></i>
105
+ ))}
106
+ </div>
107
+ <div class="astronomy-shell">
108
+ <ProductionBreadcrumb locale={locale} currentTitle={category.title} />
109
+
110
+ <section class="astronomy-orbit" aria-labelledby="astronomy-title">
111
+ <div id="astronomy-sky" class:list={["astronomy-sky", tools.length > 12 && "astronomy-sky-dense"]} aria-label={category.title}>
112
+ <div class="astronomy-sky-stars" aria-hidden="true">
113
+ {stars.map((star) => (
114
+ <i
115
+ class="astronomy-star"
116
+ style={`--star-left: ${star.left}; --star-top: ${star.top}; --star-size: ${star.size}; --star-opacity: ${star.opacity}; --star-delay: ${star.delay}s;`}
117
+ ></i>
118
+ ))}
119
+ </div>
120
+ <div class="astronomy-sky-dust" aria-hidden="true"></div>
121
+ <div class="astronomy-sky-coordinates" aria-hidden="true">
122
+ <span>α 05h 55m 10s</span>
123
+ <span>δ +07° 24′ 25″</span>
124
+ </div>
125
+ <div class="astronomy-celestial-scene" aria-hidden="true">
126
+ <div class="astronomy-orbit-track astronomy-orbit-track-one"><span class="astronomy-orbit-body astronomy-orbit-body-one"></span></div>
127
+ <div class="astronomy-orbit-track astronomy-orbit-track-two"><span class="astronomy-orbit-body astronomy-orbit-body-two"></span></div>
128
+ <div class="astronomy-orbit-track astronomy-orbit-track-three"><span class="astronomy-orbit-body astronomy-orbit-body-three"></span></div>
129
+ <div class="astronomy-planet"><span></span></div>
130
+ </div>
131
+ <div class="astronomy-constellation-center" aria-hidden="true">
132
+ <span>ASTRONOMIA</span>
133
+ <b>*</b>
134
+ </div>
135
+
136
+ {constellationTools.map((tool, index) => (
137
+ <a
138
+ class:list={["astronomy-star-link", `astronomy-star-link-${constellationPositions[index]?.side ?? "top"}`]}
139
+ href={tool.href}
140
+ data-tool-node
141
+ data-tool-index={index}
142
+ style={`--node-left: ${constellationPositions[index]?.left}; --node-top: ${constellationPositions[index]?.top}; --node-size: ${toolPointSize};`}
143
+ aria-label={`${tool.title}: ${ui.openTool}`}
144
+ >
145
+ <span class="astronomy-star-link-point" aria-hidden="true"></span>
146
+ <span class="astronomy-star-link-label">
147
+ <span class="astronomy-star-link-icon" aria-hidden="true"><Icon name={tool.icon} /></span>
148
+ <b>{String(index + 1).padStart(2, "0")}</b>
149
+ <strong>{tool.title}</strong>
150
+ <small>{tool.description}</small>
151
+ </span>
152
+ </a>
153
+ ))}
154
+
155
+ <p class="astronomy-sky-caption">{String(tools.length).padStart(2, "0")} {ui.catalogLabel}</p>
156
+ </div>
157
+
158
+ <div class="astronomy-editorial">
159
+ <p class="astronomy-overline">{ui.observationEyebrow}</p>
160
+ <h1 id="astronomy-title">{category.title}</h1>
161
+ <p class="astronomy-lede">{category.description}</p>
162
+
163
+ {firstTool && (
164
+ <div class="astronomy-observation" data-mission-panel>
165
+ <div class="astronomy-observation-heading">
166
+ <span class="astronomy-observation-dot" aria-hidden="true"></span>
167
+ <p>{ui.missionLabel}</p>
168
+ </div>
169
+ <h2 data-mission-title>{firstTool.title}</h2>
170
+ <p data-mission-description>{firstTool.description}</p>
171
+ <div class="astronomy-observation-actions">
172
+ <button type="button" data-route-button aria-controls="astronomy-sky">
173
+ {ui.exploreRoute}<span aria-hidden="true">↗</span>
174
+ </button>
175
+ <a data-mission-link href={firstTool.href}>
176
+ {ui.openTool}<span aria-hidden="true">→</span>
177
+ </a>
178
+ </div>
179
+ <p class="astronomy-observation-status" aria-live="polite" data-mission-status>
180
+ {firstTool.title}
181
+ </p>
182
+ </div>
183
+ )}
184
+
185
+ <nav class="astronomy-tool-index" aria-label={category.title}>
186
+ <details open={tools.length <= 12}>
187
+ <summary class="astronomy-tool-index-title">
188
+ <span>{ui.toolIndexLabel}</span>
189
+ <span>{String(tools.length).padStart(2, "0")}</span>
190
+ </summary>
191
+ <ol>
192
+ {tools.map((tool, index) => (
193
+ <li>
194
+ <a href={tool.href} data-tool-index-link data-tool-index={index}>
195
+ <span>{String(index + 1).padStart(2, "0")}</span>
196
+ <strong>{tool.title}</strong>
197
+ <i aria-hidden="true">↗</i>
198
+ </a>
199
+ </li>
200
+ ))}
201
+ </ol>
202
+ </details>
203
+ </nav>
204
+ </div>
205
+ </section>
206
+
207
+ <section class="astronomy-field-notes" aria-labelledby="astronomy-notes-title">
208
+ <div class="astronomy-field-notes-title">
209
+ <span class="astronomy-overline">{ui.fieldNotesLabel}</span>
210
+ <h2 id="astronomy-notes-title">{ui.fieldNotesTitle}</h2>
211
+ </div>
212
+ <div class="astronomy-seo">
213
+ <SEORenderer content={{ locale, sections: category.seo }} />
214
+ </div>
215
+ </section>
216
+ </div>
217
+ </div>
218
+ </ProductionPage>
219
+
220
+ <script>
221
+ import { enhanceAstronomyHome } from "../category/astronomy-category-home";
222
+
223
+ enhanceAstronomyHome();
224
+ </script>
@@ -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>