@anglefeint/astro-theme 0.1.0-alpha.0 → 0.1.0-alpha.1

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.
@@ -1,4 +1,4 @@
1
- body.mesh-page main {
1
+ body.ai-page main {
2
2
  width: calc(100% - 2em);
3
3
  max-width: 100%;
4
4
  margin: 0;
@@ -117,14 +117,14 @@
117
117
  letter-spacing: 0.08em;
118
118
  }
119
119
  }
120
- body.mesh-page .prose {
120
+ body.ai-page .prose {
121
121
  width: 720px;
122
122
  max-width: calc(100% - 2em);
123
123
  margin: auto;
124
124
  padding: 1em;
125
125
  color: rgb(var(--text));
126
126
  }
127
- body.mesh-page .hero-shell + .prose {
127
+ body.ai-page .hero-shell + .prose {
128
128
  margin-top: 1.1rem;
129
129
  }
130
130
  .rq-tv {
@@ -284,30 +284,30 @@
284
284
  to { transform: rotate(360deg); }
285
285
  }
286
286
  @media (max-width: 720px) {
287
- body.mesh-page .hero-shell + .prose {
287
+ body.ai-page .hero-shell + .prose {
288
288
  margin-top: 0.85rem;
289
289
  }
290
290
  }
291
291
  @media (max-width: 1300px) {
292
292
  .rq-tv { display: none; }
293
293
  }
294
- body.mesh-page .title {
294
+ body.ai-page .title {
295
295
  margin-bottom: 1em;
296
296
  padding: 1em 0;
297
297
  text-align: center;
298
298
  line-height: 1;
299
299
  }
300
- body.mesh-page .title h1 {
300
+ body.ai-page .title h1 {
301
301
  margin: 0 0 0.5em 0;
302
302
  }
303
- .mesh-subtitle-text {
303
+ .ai-subtitle-text {
304
304
  margin: -0.15rem 0 0.85rem;
305
305
  font-size: 1rem;
306
306
  line-height: 1.5;
307
307
  color: rgba(210, 240, 255, 0.86);
308
308
  text-wrap: balance;
309
309
  }
310
- .mesh-title-flow {
310
+ .ai-title-flow {
311
311
  position: relative;
312
312
  height: 3px;
313
313
  margin: 0.75rem 0 0.1rem;
@@ -323,7 +323,7 @@
323
323
  0 0 0 1px rgba(148, 216, 244, 0.08),
324
324
  0 0 14px rgba(124, 206, 246, 0.24);
325
325
  }
326
- .mesh-title-flow::before {
326
+ .ai-title-flow::before {
327
327
  content: "";
328
328
  position: absolute;
329
329
  inset: 0;
@@ -333,7 +333,7 @@
333
333
  rgba(162, 224, 250, 0) 9px 16px
334
334
  );
335
335
  }
336
- .mesh-title-flow::after {
336
+ .ai-title-flow::after {
337
337
  content: "";
338
338
  position: absolute;
339
339
  top: 0;
@@ -346,14 +346,14 @@
346
346
  rgba(164, 228, 255, 0.88),
347
347
  rgba(132, 214, 255, 0)
348
348
  );
349
- animation: mesh-title-flow-run 3.8s linear infinite;
349
+ animation: ai-title-flow-run 3.8s linear infinite;
350
350
  }
351
- @keyframes mesh-title-flow-run {
351
+ @keyframes ai-title-flow-run {
352
352
  to {
353
353
  left: 100%;
354
354
  }
355
355
  }
356
- .mesh-stage-toast {
356
+ .ai-stage-toast {
357
357
  position: fixed;
358
358
  right: 1rem;
359
359
  bottom: 1rem;
@@ -377,14 +377,14 @@
377
377
  opacity 0.18s ease,
378
378
  transform 0.18s ease;
379
379
  }
380
- .mesh-stage-toast.visible {
380
+ .ai-stage-toast.visible {
381
381
  opacity: 1;
382
382
  transform: translateY(0);
383
383
  }
384
- body.mesh-page .date {
384
+ body.ai-page .date {
385
385
  margin-bottom: 0.5em;
386
386
  color: rgb(var(--text-muted));
387
387
  }
388
- body.mesh-page .last-updated-on {
388
+ body.ai-page .last-updated-on {
389
389
  font-style: italic;
390
390
  }
@@ -4,6 +4,13 @@ import path from 'node:path';
4
4
 
5
5
  const THEMES = ['base', 'cyber', 'ai', 'hacker', 'matrix'];
6
6
  const PAGES_ROOT = path.resolve(process.cwd(), 'src/pages/[lang]');
7
+ const LAYOUT_BY_THEME = {
8
+ base: 'BasePageLayout',
9
+ ai: 'AiPageLayout',
10
+ cyber: 'CyberPageLayout',
11
+ hacker: 'HackerPageLayout',
12
+ matrix: 'MatrixPageLayout',
13
+ };
7
14
 
8
15
  function usage() {
9
16
  console.error('Usage: npm run new-page -- <slug> [--theme <base|cyber|ai|hacker|matrix>]');
@@ -54,12 +61,11 @@ async function exists(filePath) {
54
61
 
55
62
  function templateFor({ slug, theme }) {
56
63
  const title = toTitleFromSlug(slug) || 'New Page';
64
+ const layoutName = LAYOUT_BY_THEME[theme];
57
65
  return `---
58
66
  import type { GetStaticPaths } from 'astro';
59
- import BasePageLayout from '@anglefeint/astro-theme/layouts/BasePageLayout.astro';
60
- import { SUPPORTED_LOCALES } from '@anglefeint/astro-theme/i18n/config';
61
-
62
- const PAGE_THEME = '${theme}';
67
+ import ${layoutName} from '@anglefeint/astro-theme/layouts/${layoutName}.astro';
68
+ import { SUPPORTED_LOCALES } from '../../i18n/config';
63
69
 
64
70
  export const getStaticPaths = (() => SUPPORTED_LOCALES.map((lang) => ({ params: { lang } }))) satisfies GetStaticPaths;
65
71
 
@@ -68,10 +74,10 @@ const pageTitle = '${title}';
68
74
  const pageDescription = 'A custom page built from the ${theme} theme shell.';
69
75
  ---
70
76
 
71
- <BasePageLayout locale={locale} title={pageTitle} description={pageDescription} theme={PAGE_THEME}>
77
+ <${layoutName} locale={locale} title={pageTitle} description={pageDescription}>
72
78
  \t<h1>${title}</h1>
73
79
  \t<p>Replace this content with your own page content.</p>
74
- </BasePageLayout>
80
+ </${layoutName}>
75
81
  `;
76
82
  }
77
83
 
File without changes
@@ -1,74 +1,5 @@
1
1
  ---
2
- import { SITE_TAGLINE, SITE_TITLE } from '@anglefeint/site-config/site';
3
- import { SOCIAL_LINKS } from '@anglefeint/site-config/social';
4
-
5
- const today = new Date();
6
- interface Props {
7
- tagline?: string;
8
- /** Show Blade Runner scanlines overlay on mesh-page (header/footer only) */
9
- scanlines?: boolean;
10
- }
11
-
12
- const { tagline = SITE_TAGLINE, scanlines = false } = Astro.props as Props;
2
+ import CommonFooter from './shared/CommonFooter.astro';
13
3
  ---
14
4
 
15
- <footer>
16
- &copy; {today.getFullYear()} {SITE_TITLE}. {tagline}
17
- {SOCIAL_LINKS.length > 0 && (
18
- <div class="social-links">
19
- {SOCIAL_LINKS.map((link) => (
20
- <a href={link.href} target="_blank" rel="noopener noreferrer">
21
- <span class="sr-only">{link.label}</span>
22
- {link.icon === 'mastodon' && (
23
- <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
24
- <path
25
- fill="currentColor"
26
- d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"
27
- />
28
- </svg>
29
- )}
30
- {link.icon === 'twitter' && (
31
- <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
32
- <path
33
- fill="currentColor"
34
- d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
35
- />
36
- </svg>
37
- )}
38
- {link.icon === 'github' && (
39
- <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
40
- <path
41
- fill="currentColor"
42
- d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
43
- />
44
- </svg>
45
- )}
46
- {!link.icon && <span>{link.label}</span>}
47
- </a>
48
- ))}
49
- </div>
50
- )}
51
- {scanlines && <div class="mesh-scanlines" aria-hidden="true"></div>}
52
- </footer>
53
- <style>
54
- footer {
55
- padding: 2em 1em 3em 1em;
56
- background: var(--chrome-bg, var(--bg));
57
- border-top: 1px solid var(--chrome-border, rgb(var(--border)));
58
- color: var(--chrome-text-muted, rgb(var(--text-muted)));
59
- text-align: center;
60
- }
61
- .social-links {
62
- display: flex;
63
- justify-content: center;
64
- gap: 1em;
65
- margin-top: 1em;
66
- }
67
- .social-links a {
68
- text-decoration: none;
69
- color: var(--chrome-text-muted, rgb(var(--text-muted)));
70
- }
71
- .social-links a:hover {
72
- color: var(--chrome-link-hover, var(--chrome-link, rgb(var(--text))));
73
- }
74
- </style>
5
+ <CommonFooter {...Astro.props} />
@@ -1,328 +1,5 @@
1
1
  ---
2
- import { SITE_TITLE } from '../consts';
3
- import { SOCIAL_LINKS } from '@anglefeint/site-config/social';
4
- import { THEME } from '@anglefeint/site-config/theme';
5
- import HeaderLink from './HeaderLink.astro';
6
- import {
7
- DEFAULT_LOCALE,
8
- LOCALE_LABELS,
9
- type Locale,
10
- SUPPORTED_LOCALES,
11
- alternatePathForLocale,
12
- isLocale,
13
- stripLocaleFromPath,
14
- } from '../i18n/config';
15
-
16
- interface Props {
17
- locale?: Locale;
18
- homeHref?: string;
19
- /** Optional per-locale overrides (used for existence-aware language fallback on some routes). */
20
- localeHrefs?: Partial<Record<Locale, string>>;
21
- /** Show Blade Runner scanlines overlay on mesh-page (header/footer only) */
22
- scanlines?: boolean;
23
- labels?: {
24
- home: string;
25
- blog: string;
26
- about: string;
27
- status: string;
28
- language: string;
29
- };
30
- }
31
-
32
- const props = Astro.props as Props;
33
- const locale: Locale = props.locale && isLocale(props.locale) ? props.locale : DEFAULT_LOCALE;
34
- const labels = {
35
- home: props.labels?.home ?? 'Home',
36
- blog: props.labels?.blog ?? 'Blog',
37
- about: props.labels?.about ?? 'About',
38
- status: props.labels?.status ?? 'system: online',
39
- language: props.labels?.language ?? 'Language',
40
- };
41
- const showAbout = THEME.ENABLE_ABOUT_PAGE;
42
- const currentSubpath = stripLocaleFromPath(Astro.url.pathname, locale);
43
- const homeHref = props.homeHref ?? alternatePathForLocale(locale, '/');
44
- const buildLocaleHref = (targetLocale: Locale) => {
45
- // Language switcher: preserve current route when switching locales.
46
- // Only fall back for About when the feature is disabled.
47
- const sectionSubpath =
48
- currentSubpath.startsWith('/about') && !showAbout ? '/' : currentSubpath || '/';
49
- return alternatePathForLocale(targetLocale, sectionSubpath);
50
- };
51
- const localeOptions = SUPPORTED_LOCALES.map((targetLocale) => ({
52
- locale: targetLocale,
53
- label: LOCALE_LABELS[targetLocale],
54
- href: props.localeHrefs?.[targetLocale] ?? buildLocaleHref(targetLocale),
55
- }));
2
+ import CommonHeader from './shared/CommonHeader.astro';
56
3
  ---
57
4
 
58
- <header>
59
- <nav>
60
- <div class="nav-left">
61
- <h2><a href={homeHref}>{SITE_TITLE}</a></h2>
62
- </div>
63
- <div class="internal-links">
64
- <HeaderLink href={homeHref}>{labels.home}</HeaderLink>
65
- <HeaderLink href={alternatePathForLocale(locale, '/blog/')}>{labels.blog}</HeaderLink>
66
- {showAbout && <HeaderLink href={alternatePathForLocale(locale, '/about/')}>{labels.about}</HeaderLink>}
67
- </div>
68
- <div class="nav-right">
69
- <div class="social-links">
70
- <div class="lang-switcher" aria-label={labels.language}>
71
- <label class="lang-label" for="lang-select">{labels.language}</label>
72
- <select
73
- id="lang-select"
74
- class="lang-select"
75
- aria-label={labels.language}
76
- onchange="if (this.value) window.location.href = this.value;"
77
- >
78
- {
79
- localeOptions.map((option) => (
80
- <option value={option.href} selected={option.locale === locale}>
81
- {option.label}
82
- </option>
83
- ))
84
- }
85
- </select>
86
- </div>
87
- <div class="nav-status" aria-label="System status">
88
- <span class="nav-status-dot" aria-hidden="true"></span>
89
- <span class="nav-status-text">{labels.status}</span>
90
- </div>
91
- {
92
- SOCIAL_LINKS.map((link) => (
93
- <a href={link.href} target="_blank" rel="noopener noreferrer">
94
- <span class="sr-only">{link.label}</span>
95
- {link.icon === 'mastodon' && (
96
- <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
97
- <path
98
- fill="currentColor"
99
- d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"
100
- />
101
- </svg>
102
- )}
103
- {link.icon === 'twitter' && (
104
- <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
105
- <path
106
- fill="currentColor"
107
- d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
108
- />
109
- </svg>
110
- )}
111
- {link.icon === 'github' && (
112
- <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
113
- <path
114
- fill="currentColor"
115
- d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
116
- />
117
- </svg>
118
- )}
119
- {!link.icon && <span>{link.label}</span>}
120
- </a>
121
- ))
122
- }
123
- </div>
124
- </div>
125
- </nav>
126
- {props.scanlines && <div class="mesh-scanlines" aria-hidden="true"></div>}
127
- </header>
128
- <style>
129
- header {
130
- margin: 0;
131
- padding: 0 1em;
132
- background: var(--chrome-bg, var(--bg));
133
- border-bottom: 1px solid var(--chrome-border, rgb(var(--border)));
134
- }
135
- h2 {
136
- margin: 0;
137
- font-size: 1em;
138
- }
139
-
140
- h2 a,
141
- h2 a.active {
142
- text-decoration: none;
143
- color: var(--chrome-link, rgb(var(--text)));
144
- border-bottom: none;
145
- }
146
- nav {
147
- display: grid;
148
- grid-template-columns: 1fr auto 1fr;
149
- align-items: center;
150
- gap: 0.6rem;
151
- min-height: 56px;
152
- width: 100%;
153
- }
154
- .nav-left {
155
- justify-self: start;
156
- }
157
- .internal-links {
158
- justify-self: center;
159
- display: flex;
160
- align-items: center;
161
- gap: 0.1rem;
162
- }
163
- .nav-right {
164
- justify-self: end;
165
- display: flex;
166
- align-items: center;
167
- gap: 0.35rem;
168
- min-width: max-content;
169
- }
170
- .internal-links :global(a) {
171
- padding: 1em 0.5em;
172
- color: var(--chrome-link, rgb(var(--text)));
173
- border-bottom: 4px solid transparent;
174
- text-decoration: none;
175
- }
176
- .internal-links :global(a.active) {
177
- text-decoration: none;
178
- border-bottom-color: var(--chrome-active, var(--accent));
179
- }
180
- .social-links a {
181
- color: var(--chrome-link, rgb(var(--text)));
182
- align-items: center;
183
- justify-content: center;
184
- padding: 0.6rem 0.35rem;
185
- border-bottom: none;
186
- }
187
- .social-links a:hover {
188
- color: var(--chrome-link-hover, var(--chrome-link, rgb(var(--text))));
189
- }
190
- .social-links,
191
- .social-links a {
192
- display: flex;
193
- }
194
- .social-links {
195
- align-items: center;
196
- gap: 0.4rem;
197
- position: relative;
198
- flex-wrap: nowrap;
199
- }
200
- .lang-switcher {
201
- display: inline-flex;
202
- align-items: center;
203
- gap: 0.4rem;
204
- padding: 0.2rem 0.4rem;
205
- margin-right: 0.1rem;
206
- border: 1px solid rgba(132, 214, 255, 0.2);
207
- border-radius: 999px;
208
- background: rgba(6, 16, 30, 0.35);
209
- flex-shrink: 0;
210
- }
211
- .lang-label {
212
- display: inline-block;
213
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
214
- font-size: 0.6rem;
215
- text-transform: uppercase;
216
- letter-spacing: 0.12em;
217
- color: rgba(190, 226, 248, 0.78);
218
- white-space: nowrap;
219
- }
220
- .lang-select {
221
- appearance: none;
222
- -webkit-appearance: none;
223
- -moz-appearance: none;
224
- background:
225
- linear-gradient(45deg, transparent 50%, rgba(204, 236, 252, 0.82) 50%) calc(100% - 0.9rem) 50% / 0.35rem 0.35rem no-repeat,
226
- linear-gradient(135deg, rgba(204, 236, 252, 0.82) 50%, transparent 50%) calc(100% - 0.7rem) 50% / 0.35rem 0.35rem no-repeat,
227
- rgba(9, 22, 40, 0.68);
228
- border: 1px solid rgba(132, 214, 255, 0.2);
229
- border-radius: 999px;
230
- padding: 0.2rem 1.45rem 0.2rem 0.55rem;
231
- min-width: 6rem;
232
- font-size: 0.62rem;
233
- line-height: 1;
234
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
235
- color: rgba(204, 236, 252, 0.86);
236
- cursor: pointer;
237
- }
238
- .lang-select:focus {
239
- outline: none;
240
- border-color: rgba(132, 214, 255, 0.5);
241
- box-shadow: 0 0 0 2px rgba(98, 180, 228, 0.18);
242
- }
243
- .lang-select option {
244
- color: #ccecfb;
245
- background: #0b1c32;
246
- }
247
- .nav-status {
248
- display: none;
249
- align-items: center;
250
- gap: 0.45rem;
251
- padding: 0.24rem 0.5rem;
252
- border-radius: 999px;
253
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
254
- font-size: 0.62rem;
255
- letter-spacing: 0.12em;
256
- text-transform: uppercase;
257
- color: rgba(186, 232, 252, 0.86);
258
- background: rgba(6, 16, 30, 0.52);
259
- border: 1px solid rgba(132, 214, 255, 0.22);
260
- box-shadow:
261
- 0 0 0 1px rgba(132, 214, 255, 0.08),
262
- 0 0 16px rgba(90, 180, 255, 0.16);
263
- white-space: nowrap;
264
- position: absolute;
265
- right: calc(100% + 0.5rem);
266
- top: 50%;
267
- transform: translateY(-50%);
268
- pointer-events: none;
269
- }
270
- .nav-status-dot {
271
- width: 0.44rem;
272
- height: 0.44rem;
273
- border-radius: 50%;
274
- background: rgba(150, 226, 255, 0.96);
275
- box-shadow: 0 0 10px rgba(122, 210, 255, 0.72);
276
- animation: nav-status-pulse 1.8s steps(1, end) infinite;
277
- }
278
- @keyframes nav-status-pulse {
279
- 0%,
280
- 78%,
281
- 100% {
282
- opacity: 1;
283
- transform: scale(1);
284
- }
285
- 82% {
286
- opacity: 0.2;
287
- transform: scale(0.7);
288
- }
289
- 86% {
290
- opacity: 1;
291
- transform: scale(1.05);
292
- }
293
- 90% {
294
- opacity: 0.28;
295
- transform: scale(0.78);
296
- }
297
- }
298
- body.mesh-page .nav-status {
299
- display: inline-flex;
300
- }
301
- body.about-page .nav-status {
302
- display: none;
303
- }
304
- @media (max-width: 720px) {
305
- nav {
306
- grid-template-columns: 1fr;
307
- gap: 0;
308
- }
309
- .nav-left {
310
- display: none;
311
- }
312
- .internal-links {
313
- justify-self: start;
314
- }
315
- .nav-right {
316
- justify-self: end;
317
- }
318
- .nav-status {
319
- display: none !important;
320
- }
321
- .social-links {
322
- display: none;
323
- }
324
- .lang-switcher {
325
- margin-right: 0;
326
- }
327
- }
328
- </style>
5
+ <CommonHeader {...Astro.props} />
@@ -0,0 +1,74 @@
1
+ ---
2
+ import { SITE_TAGLINE, SITE_TITLE } from '@anglefeint/site-config/site';
3
+ import { SOCIAL_LINKS } from '@anglefeint/site-config/social';
4
+
5
+ const today = new Date();
6
+ interface Props {
7
+ tagline?: string;
8
+ /** Show Blade Runner scanlines overlay on ai-page (header/footer only) */
9
+ scanlines?: boolean;
10
+ }
11
+
12
+ const { tagline = SITE_TAGLINE, scanlines = false } = Astro.props as Props;
13
+ ---
14
+
15
+ <footer>
16
+ &copy; {today.getFullYear()} {SITE_TITLE}. {tagline}
17
+ {SOCIAL_LINKS.length > 0 && (
18
+ <div class="social-links">
19
+ {SOCIAL_LINKS.map((link) => (
20
+ <a href={link.href} target="_blank" rel="noopener noreferrer">
21
+ <span class="sr-only">{link.label}</span>
22
+ {link.icon === 'mastodon' && (
23
+ <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
24
+ <path
25
+ fill="currentColor"
26
+ d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"
27
+ />
28
+ </svg>
29
+ )}
30
+ {link.icon === 'twitter' && (
31
+ <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
32
+ <path
33
+ fill="currentColor"
34
+ d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
35
+ />
36
+ </svg>
37
+ )}
38
+ {link.icon === 'github' && (
39
+ <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
40
+ <path
41
+ fill="currentColor"
42
+ d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
43
+ />
44
+ </svg>
45
+ )}
46
+ {!link.icon && <span>{link.label}</span>}
47
+ </a>
48
+ ))}
49
+ </div>
50
+ )}
51
+ {scanlines && <div class="ai-scanlines" aria-hidden="true"></div>}
52
+ </footer>
53
+ <style>
54
+ footer {
55
+ padding: 2em 1em 3em 1em;
56
+ background: var(--chrome-bg, var(--bg));
57
+ border-top: 1px solid var(--chrome-border, rgb(var(--border)));
58
+ color: var(--chrome-text-muted, rgb(var(--text-muted)));
59
+ text-align: center;
60
+ }
61
+ .social-links {
62
+ display: flex;
63
+ justify-content: center;
64
+ gap: 1em;
65
+ margin-top: 1em;
66
+ }
67
+ .social-links a {
68
+ text-decoration: none;
69
+ color: var(--chrome-text-muted, rgb(var(--text-muted)));
70
+ }
71
+ .social-links a:hover {
72
+ color: var(--chrome-link-hover, var(--chrome-link, rgb(var(--text))));
73
+ }
74
+ </style>