@abstractdata/starlight-theme 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 ADDED
@@ -0,0 +1,43 @@
1
+ # @abstractdata/starlight-theme
2
+
3
+ Branded Astro Starlight theme by Abstract Data. HUD and Calm surfaces, light + dark, motion-aware.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ bun add @abstractdata/starlight-theme
9
+ ```
10
+
11
+ ## Use
12
+
13
+ ```js
14
+ // astro.config.mjs
15
+ import { defineConfig } from 'astro/config';
16
+ import starlight from '@astrojs/starlight';
17
+ import abstractData from '@abstractdata/starlight-theme';
18
+
19
+ export default defineConfig({
20
+ integrations: [
21
+ starlight({
22
+ title: 'Your Docs',
23
+ plugins: [abstractData({ motion: 'full' })],
24
+ }),
25
+ ],
26
+ });
27
+ ```
28
+
29
+ ## Options
30
+
31
+ | Option | Type | Default | Description |
32
+ |--------|------|---------|-------------|
33
+ | `motion` | `'full' \| 'calm'` | `'full'` | HUD = animated hexagon grid, scanline, holographic shimmer, glitch. Calm = same palette, no animations. |
34
+ | `credit` | `'auto' \| 'hide'` | `'auto'` | _(Round 3b)_ Show "Built by Abstract Data" in footer. Set `'hide'` for white-label client work. |
35
+
36
+ `motion: 'full'` automatically collapses to Calm behavior when the user's OS reports `prefers-reduced-motion: reduce`. No additional config required.
37
+
38
+ ## Brand
39
+
40
+ - Cyan `#00D9FF` · Gold `#D4AF37` · Burgundy `#8B2635`
41
+ - Display Orbitron · Body Inter · Mono JetBrains Mono
42
+
43
+ See the repo root `README.md` for full brand reference.
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@abstractdata/starlight-theme",
3
+ "version": "0.2.0",
4
+ "description": "Branded Astro Starlight theme by Abstract Data — HUD and Calm surfaces, light + dark, motion-aware.",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
+ "exports": {
9
+ ".": "./src/index.ts",
10
+ "./types": "./src/types.d.ts",
11
+ "./shiki": "./src/shiki/index.ts",
12
+ "./styles/*": "./src/styles/*",
13
+ "./components/*": "./src/components/*",
14
+ "./assets/*": "./src/assets/*"
15
+ },
16
+ "files": [
17
+ "src/"
18
+ ],
19
+ "scripts": {
20
+ "typecheck": "tsc --noEmit"
21
+ },
22
+ "keywords": [
23
+ "astro",
24
+ "starlight",
25
+ "starlight-theme",
26
+ "documentation",
27
+ "abstract-data"
28
+ ],
29
+ "homepage": "https://github.com/Abstract-Data/abstract-data-doc-theme",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/Abstract-Data/abstract-data-doc-theme.git",
33
+ "directory": "packages/starlight-theme"
34
+ },
35
+ "license": "MIT",
36
+ "author": "Abstract Data <dev@abstractdata.io>",
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "peerDependencies": {
41
+ "@astrojs/starlight": ">=0.32.0",
42
+ "astro": ">=5.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "@astrojs/starlight": "^0.34.0",
46
+ "astro": "^5.10.0",
47
+ "typescript": "^5.6.0"
48
+ }
49
+ }
@@ -0,0 +1,26 @@
1
+ <svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <style>
3
+ @media (prefers-color-scheme: light) {
4
+ .background { fill: black; }
5
+ .foreground { fill: white; }
6
+ }
7
+ @media (prefers-color-scheme: dark) {
8
+ .background { fill: white; }
9
+ .foreground { fill: black; }
10
+ }
11
+ </style>
12
+ <g clip-path="url(#clip0_7960_43945)">
13
+ <rect class="background" width="180" height="180" rx="37" />
14
+ <g style="transform: scale(95%); transform-origin: center">
15
+ <path class="foreground"
16
+ d="M101.141 53H136.632C151.023 53 162.689 64.6662 162.689 79.0573V112.904H148.112V79.0573C148.112 78.7105 148.098 78.3662 148.072 78.0251L112.581 112.898C112.701 112.902 112.821 112.904 112.941 112.904H148.112V126.672H112.941C98.5504 126.672 86.5638 114.891 86.5638 100.5V66.7434H101.141V100.5C101.141 101.15 101.191 101.792 101.289 102.422L137.56 66.7816C137.255 66.7563 136.945 66.7434 136.632 66.7434H101.141V53Z" />
17
+ <path class="foreground"
18
+ d="M65.2926 124.136L14 66.7372H34.6355L64.7495 100.436V66.7372H80.1365V118.47C80.1365 126.278 70.4953 129.958 65.2926 124.136Z" />
19
+ </g>
20
+ </g>
21
+ <defs>
22
+ <clipPath id="clip0_7960_43945">
23
+ <rect width="180" height="180" fill="white" />
24
+ </clipPath>
25
+ </defs>
26
+ </svg>
@@ -0,0 +1,22 @@
1
+ ---
2
+ /**
3
+ * Override of Starlight's <Footer /> slot.
4
+ * Renders the default footer (edit link, last updated, pagination),
5
+ * then appends a "Built by Abstract Data" credit unless the theme
6
+ * was configured with `credit: 'hide'` (white-label mode).
7
+ */
8
+ import type { Props } from '@astrojs/starlight/props';
9
+ import Default from '@astrojs/starlight/components/Footer.astro';
10
+ import { config } from 'virtual:abstractdata/config';
11
+ ---
12
+
13
+ <Default {...Astro.props} />
14
+
15
+ {
16
+ config.credit !== 'hide' && (
17
+ <div class="ad-credit" role="contentinfo">
18
+ <span>Built by</span>
19
+ <a href="https://abstractdata.io" rel="noopener">Abstract Data</a>
20
+ </div>
21
+ )
22
+ }
@@ -0,0 +1,36 @@
1
+ ---
2
+ /**
3
+ * <Glitch /> — inline glitch text.
4
+ *
5
+ * Two ways to use it (both work in MDX):
6
+ *
7
+ * <Glitch text="ABSTRACT DATA" />
8
+ * <Glitch>some content</Glitch>
9
+ *
10
+ * The styling lives in `hud.css` — the glitch only animates when the
11
+ * theme is loaded with `motion: 'full'`. Under `motion: 'calm'` the
12
+ * text renders cleanly with no animation.
13
+ */
14
+ interface Props {
15
+ /** Text to render. If omitted, the default slot is used. */
16
+ text?: string;
17
+ /** Render as block element instead of inline. */
18
+ block?: boolean;
19
+ }
20
+
21
+ const { text, block = false } = Astro.props;
22
+ const tag = block ? 'div' : 'span';
23
+ const content = text ?? '';
24
+ ---
25
+
26
+ {
27
+ block ? (
28
+ <div class="ad-glitch" data-text={content}>
29
+ {text ?? <slot />}
30
+ </div>
31
+ ) : (
32
+ <span class="ad-glitch" data-text={content}>
33
+ {text ?? <slot />}
34
+ </span>
35
+ )
36
+ }
@@ -0,0 +1,170 @@
1
+ ---
2
+ /**
3
+ * <Hero /> — branded splash hero for the docs landing page.
4
+ *
5
+ * Uses Astro's <Image> component for the logo so it goes through the asset
6
+ * pipeline (lazy loading, optimized format, no perf warnings).
7
+ *
8
+ * Use it in `src/content/docs/index.mdx` like this:
9
+ *
10
+ * import { Image } from 'astro:assets';
11
+ * import logo from '../../assets/your-logo.png';
12
+ * import Hero from '@abstractdata/starlight-theme/components/Hero.astro';
13
+ *
14
+ * <Hero
15
+ * title="Your title"
16
+ * tagline="Your tagline"
17
+ * image={logo}
18
+ * primary={{ text: 'Get Started', href: '/quickstart/' }}
19
+ * secondary={{ text: 'GitHub', href: 'https://github.com/...' }}
20
+ * />
21
+ */
22
+ import { Image } from 'astro:assets';
23
+ import type { ImageMetadata } from 'astro';
24
+
25
+ interface Action {
26
+ text: string;
27
+ href: string;
28
+ }
29
+
30
+ interface Props {
31
+ title: string;
32
+ tagline?: string;
33
+ image: ImageMetadata;
34
+ alt?: string;
35
+ primary?: Action;
36
+ secondary?: Action;
37
+ }
38
+
39
+ const { title, tagline, image, alt = title, primary, secondary } = Astro.props;
40
+ ---
41
+
42
+ <section class="ad-hero-section">
43
+ <div class="ad-hero-content">
44
+ <h1 class="ad-hero-title">{title}</h1>
45
+ {tagline && <p class="ad-hero-tagline">{tagline}</p>}
46
+ {(primary || secondary) && (
47
+ <div class="ad-hero-actions">
48
+ {primary && (
49
+ <a class="ad-hero-btn ad-hero-btn-primary" href={primary.href}>
50
+ {primary.text}
51
+ </a>
52
+ )}
53
+ {secondary && (
54
+ <a class="ad-hero-btn ad-hero-btn-secondary" href={secondary.href}>
55
+ {secondary.text}
56
+ </a>
57
+ )}
58
+ </div>
59
+ )}
60
+ </div>
61
+ <div class="ad-hero-image">
62
+ <Image
63
+ src={image}
64
+ alt={alt}
65
+ widths={[180, 360, 540, 720]}
66
+ sizes="(min-width: 1024px) 360px, 50vw"
67
+ loading="eager"
68
+ decoding="sync"
69
+ />
70
+ </div>
71
+ </section>
72
+
73
+ <style>
74
+ .ad-hero-section {
75
+ display: grid;
76
+ grid-template-columns: 1fr;
77
+ gap: 2.5rem;
78
+ align-items: center;
79
+ padding: 2rem 0 3rem;
80
+ }
81
+ @media (min-width: 768px) {
82
+ .ad-hero-section {
83
+ grid-template-columns: 1fr auto;
84
+ gap: 3.5rem;
85
+ }
86
+ }
87
+ .ad-hero-content { min-width: 0; }
88
+ .ad-hero-title {
89
+ font-family: 'Orbitron', sans-serif;
90
+ font-weight: 700;
91
+ font-size: clamp(2rem, 5vw, 3.5rem);
92
+ line-height: 1.05;
93
+ margin: 0 0 1rem;
94
+ color: var(--sl-color-text);
95
+ letter-spacing: 0.01em;
96
+ position: relative;
97
+ padding-left: 14px;
98
+ }
99
+ .ad-hero-title::before {
100
+ content: '';
101
+ position: absolute;
102
+ left: 0;
103
+ top: 0.2em;
104
+ bottom: 0.2em;
105
+ width: 3px;
106
+ background: linear-gradient(180deg, var(--ad-cyan), var(--ad-gold));
107
+ }
108
+ :global(:root[data-theme='light']) .ad-hero-title::before {
109
+ background: linear-gradient(180deg, var(--ad-cyan-deep), var(--ad-burgundy));
110
+ }
111
+ .ad-hero-tagline {
112
+ font-size: 1.05rem;
113
+ line-height: 1.55;
114
+ color: var(--sl-color-gray-3);
115
+ margin: 0 0 1.75rem;
116
+ max-width: 38ch;
117
+ }
118
+ .ad-hero-actions {
119
+ display: flex;
120
+ gap: 0.75rem;
121
+ flex-wrap: wrap;
122
+ }
123
+ .ad-hero-btn {
124
+ font-family: 'Orbitron', sans-serif;
125
+ font-size: 0.8rem;
126
+ letter-spacing: 0.12em;
127
+ text-transform: uppercase;
128
+ padding: 0.7rem 1.25rem;
129
+ border-radius: 6px;
130
+ text-decoration: none;
131
+ border: 1px solid;
132
+ transition: box-shadow .15s, background .15s;
133
+ }
134
+ .ad-hero-btn-primary {
135
+ background: var(--ad-cyan);
136
+ color: var(--ad-charcoal);
137
+ border-color: var(--ad-cyan);
138
+ }
139
+ .ad-hero-btn-primary:hover {
140
+ box-shadow: 0 0 24px rgba(0, 217, 255, 0.5);
141
+ }
142
+ .ad-hero-btn-secondary {
143
+ background: transparent;
144
+ color: var(--ad-gold);
145
+ border-color: var(--ad-gold);
146
+ }
147
+ .ad-hero-btn-secondary:hover {
148
+ background: rgba(212, 175, 55, 0.10);
149
+ }
150
+ :global(:root[data-theme='light']) .ad-hero-btn-primary {
151
+ background: var(--ad-cyan-deep);
152
+ color: #ffffff;
153
+ border-color: var(--ad-cyan-deep);
154
+ }
155
+ :global(:root[data-theme='light']) .ad-hero-btn-secondary {
156
+ color: var(--ad-burgundy-deep);
157
+ border-color: var(--ad-burgundy-deep);
158
+ }
159
+ :global(:root[data-theme='light']) .ad-hero-btn-secondary:hover {
160
+ background: rgba(122, 31, 44, 0.06);
161
+ }
162
+ .ad-hero-image img {
163
+ max-width: 360px;
164
+ height: auto;
165
+ filter: drop-shadow(0 0 24px rgba(0, 217, 255, 0.35));
166
+ }
167
+ :global(:root[data-theme='light']) .ad-hero-image img {
168
+ filter: drop-shadow(0 4px 16px rgba(122, 31, 44, 0.20));
169
+ }
170
+ </style>
@@ -0,0 +1,27 @@
1
+ ---
2
+ /**
3
+ * Override of Starlight's <SocialIcons /> slot.
4
+ * Renders the default content, then appends the Abstract Data version chip
5
+ * if `version` is configured on the theme plugin.
6
+ *
7
+ * The chip carries the `ad-version-chip` class. When `motion: 'full'`,
8
+ * `hud.css` adds an idle glitch-pulse + a hover-triggered glitch.
9
+ */
10
+ import type { Props } from '@astrojs/starlight/props';
11
+ import Default from '@astrojs/starlight/components/SocialIcons.astro';
12
+ import { config } from 'virtual:abstractdata/config';
13
+ ---
14
+
15
+ <Default {...Astro.props} />
16
+
17
+ {
18
+ config.version && (
19
+ <span
20
+ class="ad-version-chip"
21
+ data-text={config.version}
22
+ aria-label={`Version ${config.version}`}
23
+ >
24
+ {config.version}
25
+ </span>
26
+ )
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1,142 @@
1
+ import type { StarlightPlugin } from '@astrojs/starlight/types';
2
+ import { abstractDataDark } from './shiki/abstract-data-dark.ts';
3
+ import { abstractDataLight } from './shiki/abstract-data-light.ts';
4
+
5
+ export interface AbstractDataThemeConfig {
6
+ /**
7
+ * Visual motion intensity.
8
+ * - `full` (default): HUD surface — animated hex grid, holographic shimmer,
9
+ * glitch pulse on the version chip.
10
+ * - `calm`: Blueprint surface — same palette and fonts, no animations.
11
+ *
12
+ * `full` automatically degrades to `calm` behavior when the user's OS reports
13
+ * `prefers-reduced-motion: reduce`.
14
+ */
15
+ motion?: 'full' | 'calm';
16
+
17
+ /**
18
+ * "Built by Abstract Data" credit in the footer.
19
+ * - `auto` (default): show the credit (recommended for Abstract Data properties).
20
+ * - `hide`: omit entirely (white-label client work).
21
+ */
22
+ credit?: 'auto' | 'hide';
23
+
24
+ /**
25
+ * Optional version string shown as a chip next to the social icons.
26
+ * When set under `motion: 'full'`, the chip carries a glitch pulse plus
27
+ * a hover-triggered glitch effect. Leave undefined to hide it entirely.
28
+ *
29
+ * @example "v1.4.2"
30
+ */
31
+ version?: string;
32
+
33
+ /**
34
+ * Apply the branded Shiki / expressive-code themes (cyan/gold/burgundy
35
+ * tokens). Defaults to `true`. Set `false` if you want to ship your own
36
+ * `expressiveCode.themes` in `astro.config.mjs`.
37
+ */
38
+ shiki?: boolean;
39
+ }
40
+
41
+ const PLUGIN_NAME = '@abstractdata/starlight-theme';
42
+ const VIRTUAL_ID = 'virtual:abstractdata/config';
43
+ const RESOLVED_VIRTUAL_ID = `\0${VIRTUAL_ID}`;
44
+
45
+ /**
46
+ * Abstract Data Starlight theme plugin.
47
+ *
48
+ * @example
49
+ * ```js
50
+ * // astro.config.mjs
51
+ * import abstractData from '@abstractdata/starlight-theme';
52
+ *
53
+ * starlight({
54
+ * plugins: [abstractData({ motion: 'full', version: 'v1.4.2' })],
55
+ * });
56
+ * ```
57
+ */
58
+ export default function abstractDataTheme(
59
+ opts: AbstractDataThemeConfig = {},
60
+ ): StarlightPlugin {
61
+ const motion = opts.motion ?? 'full';
62
+ const credit = opts.credit ?? 'auto';
63
+ const version = opts.version ?? null;
64
+ const shiki = opts.shiki ?? true;
65
+
66
+ const runtimeConfig = JSON.stringify({ motion, credit, version });
67
+
68
+ return {
69
+ name: PLUGIN_NAME,
70
+ hooks: {
71
+ 'config:setup'({ updateConfig, addIntegration, logger }) {
72
+ const customCss: string[] = [
73
+ '@abstractdata/starlight-theme/styles/theme.css',
74
+ ];
75
+ if (motion === 'full') {
76
+ customCss.push('@abstractdata/starlight-theme/styles/hud.css');
77
+ }
78
+
79
+ const updates: Parameters<typeof updateConfig>[0] = {
80
+ customCss,
81
+ components: {
82
+ SocialIcons:
83
+ '@abstractdata/starlight-theme/components/SocialIcons.astro',
84
+ Footer:
85
+ '@abstractdata/starlight-theme/components/Footer.astro',
86
+ },
87
+ };
88
+
89
+ if (shiki) {
90
+ // Branded code-block syntax. First theme = dark, second = light;
91
+ // expressive-code auto-switches based on Starlight's data-theme.
92
+ updates.expressiveCode = {
93
+ themes: [abstractDataDark, abstractDataLight],
94
+ styleOverrides: {
95
+ borderRadius: '8px',
96
+ borderColor: 'var(--sl-color-hairline)',
97
+ codeFontFamily: "'JetBrains Mono', ui-monospace, monospace",
98
+ uiFontFamily: "'Inter', system-ui, sans-serif",
99
+ },
100
+ };
101
+ }
102
+
103
+ updateConfig(updates);
104
+
105
+ // Inject the runtime config as a Vite virtual module so components
106
+ // (SocialIcons, Footer, Glitch) can import it without a build step.
107
+ addIntegration({
108
+ name: '@abstractdata/starlight-theme/runtime',
109
+ hooks: {
110
+ 'astro:config:setup': ({ updateConfig: updateAstroConfig }) => {
111
+ updateAstroConfig({
112
+ vite: {
113
+ plugins: [
114
+ {
115
+ name: 'abstractdata-virtual-config',
116
+ resolveId(id) {
117
+ if (id === VIRTUAL_ID) return RESOLVED_VIRTUAL_ID;
118
+ return null;
119
+ },
120
+ load(id) {
121
+ if (id === RESOLVED_VIRTUAL_ID) {
122
+ return `export const config = ${runtimeConfig};`;
123
+ }
124
+ return null;
125
+ },
126
+ },
127
+ ],
128
+ },
129
+ });
130
+ },
131
+ },
132
+ });
133
+
134
+ logger.info(
135
+ `Abstract Data theme · motion: ${motion} · credit: ${credit}` +
136
+ (version ? ` · version: ${version}` : '') +
137
+ (shiki ? ' · shiki: branded' : ' · shiki: user-managed'),
138
+ );
139
+ },
140
+ },
141
+ };
142
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Abstract Data — Shiki dark theme.
3
+ * Used by expressive-code in HUD/Calm dark mode.
4
+ */
5
+ export const abstractDataDark = {
6
+ name: 'abstract-data-dark',
7
+ type: 'dark',
8
+ semanticHighlighting: true,
9
+ colors: {
10
+ 'editor.background': '#0c0d11',
11
+ 'editor.foreground': '#f0f0f5',
12
+ 'editor.lineHighlightBackground': '#14151a',
13
+ 'editorLineNumber.foreground': '#5a5b62',
14
+ 'editorLineNumber.activeForeground': '#00d9ff',
15
+ 'editor.selectionBackground': '#00d9ff2e',
16
+ 'editor.findMatchHighlightBackground': '#d4af3733',
17
+ },
18
+ tokenColors: [
19
+ {
20
+ scope: ['comment', 'punctuation.definition.comment'],
21
+ settings: { foreground: '#5a5b62', fontStyle: 'italic' },
22
+ },
23
+ {
24
+ scope: [
25
+ 'keyword',
26
+ 'storage',
27
+ 'storage.type',
28
+ 'storage.modifier',
29
+ 'keyword.control',
30
+ 'keyword.operator.new',
31
+ 'keyword.operator.expression',
32
+ ],
33
+ settings: { foreground: '#00d9ff' },
34
+ },
35
+ {
36
+ scope: [
37
+ 'string',
38
+ 'string.quoted',
39
+ 'string.template',
40
+ 'punctuation.definition.string',
41
+ ],
42
+ settings: { foreground: '#d4af37' },
43
+ },
44
+ {
45
+ scope: ['constant.numeric', 'constant.language', 'constant.character'],
46
+ settings: { foreground: '#c04a5b' },
47
+ },
48
+ {
49
+ scope: [
50
+ 'entity.name.function',
51
+ 'support.function',
52
+ 'meta.function-call entity.name.function',
53
+ ],
54
+ settings: { foreground: '#c0e0ff' },
55
+ },
56
+ {
57
+ scope: ['variable', 'variable.parameter', 'variable.other'],
58
+ settings: { foreground: '#f0f0f5' },
59
+ },
60
+ {
61
+ scope: ['entity.name.type', 'support.type', 'support.class', 'entity.name.class'],
62
+ settings: { foreground: '#d4af37' },
63
+ },
64
+ {
65
+ scope: ['entity.name.tag', 'meta.tag'],
66
+ settings: { foreground: '#00d9ff' },
67
+ },
68
+ {
69
+ scope: ['entity.other.attribute-name'],
70
+ settings: { foreground: '#d4af37' },
71
+ },
72
+ {
73
+ scope: ['punctuation', 'meta.brace', 'meta.delimiter'],
74
+ settings: { foreground: '#8a8a93' },
75
+ },
76
+ {
77
+ scope: ['keyword.operator', 'meta.operator'],
78
+ settings: { foreground: '#8a8a93' },
79
+ },
80
+ {
81
+ scope: ['markup.heading'],
82
+ settings: { foreground: '#00d9ff', fontStyle: 'bold' },
83
+ },
84
+ {
85
+ scope: ['markup.bold'],
86
+ settings: { fontStyle: 'bold' },
87
+ },
88
+ {
89
+ scope: ['markup.italic'],
90
+ settings: { fontStyle: 'italic' },
91
+ },
92
+ {
93
+ scope: ['markup.inserted'],
94
+ settings: { foreground: '#00d9ff' },
95
+ },
96
+ {
97
+ scope: ['markup.deleted'],
98
+ settings: { foreground: '#c04a5b' },
99
+ },
100
+ ],
101
+ } as const;
102
+
103
+ export default abstractDataDark;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Abstract Data — Shiki light theme.
3
+ * Used by expressive-code in HUD/Calm light mode.
4
+ */
5
+ export const abstractDataLight = {
6
+ name: 'abstract-data-light',
7
+ type: 'light',
8
+ semanticHighlighting: true,
9
+ colors: {
10
+ 'editor.background': '#f4efe5',
11
+ 'editor.foreground': '#1a1a1a',
12
+ 'editor.lineHighlightBackground': '#ece5d5',
13
+ 'editorLineNumber.foreground': '#8e8a7e',
14
+ 'editorLineNumber.activeForeground': '#7a1f2c',
15
+ 'editor.selectionBackground': '#007a8e2e',
16
+ 'editor.findMatchHighlightBackground': '#8a6d1f40',
17
+ },
18
+ tokenColors: [
19
+ {
20
+ scope: ['comment', 'punctuation.definition.comment'],
21
+ settings: { foreground: '#8e8a7e', fontStyle: 'italic' },
22
+ },
23
+ {
24
+ scope: [
25
+ 'keyword',
26
+ 'storage',
27
+ 'storage.type',
28
+ 'storage.modifier',
29
+ 'keyword.control',
30
+ 'keyword.operator.new',
31
+ 'keyword.operator.expression',
32
+ ],
33
+ settings: { foreground: '#7a1f2c' },
34
+ },
35
+ {
36
+ scope: [
37
+ 'string',
38
+ 'string.quoted',
39
+ 'string.template',
40
+ 'punctuation.definition.string',
41
+ ],
42
+ settings: { foreground: '#8a6d1f' },
43
+ },
44
+ {
45
+ scope: ['constant.numeric', 'constant.language', 'constant.character'],
46
+ settings: { foreground: '#7a1f2c' },
47
+ },
48
+ {
49
+ scope: [
50
+ 'entity.name.function',
51
+ 'support.function',
52
+ 'meta.function-call entity.name.function',
53
+ ],
54
+ settings: { foreground: '#007a8e' },
55
+ },
56
+ {
57
+ scope: ['variable', 'variable.parameter', 'variable.other'],
58
+ settings: { foreground: '#1a1a1a' },
59
+ },
60
+ {
61
+ scope: ['entity.name.type', 'support.type', 'support.class', 'entity.name.class'],
62
+ settings: { foreground: '#8a6d1f' },
63
+ },
64
+ {
65
+ scope: ['entity.name.tag', 'meta.tag'],
66
+ settings: { foreground: '#7a1f2c' },
67
+ },
68
+ {
69
+ scope: ['entity.other.attribute-name'],
70
+ settings: { foreground: '#007a8e' },
71
+ },
72
+ {
73
+ scope: ['punctuation', 'meta.brace', 'meta.delimiter'],
74
+ settings: { foreground: '#5e5e66' },
75
+ },
76
+ {
77
+ scope: ['keyword.operator', 'meta.operator'],
78
+ settings: { foreground: '#5e5e66' },
79
+ },
80
+ {
81
+ scope: ['markup.heading'],
82
+ settings: { foreground: '#7a1f2c', fontStyle: 'bold' },
83
+ },
84
+ {
85
+ scope: ['markup.bold'],
86
+ settings: { fontStyle: 'bold' },
87
+ },
88
+ {
89
+ scope: ['markup.italic'],
90
+ settings: { fontStyle: 'italic' },
91
+ },
92
+ {
93
+ scope: ['markup.inserted'],
94
+ settings: { foreground: '#007a8e' },
95
+ },
96
+ {
97
+ scope: ['markup.deleted'],
98
+ settings: { foreground: '#7a1f2c' },
99
+ },
100
+ ],
101
+ } as const;
102
+
103
+ export default abstractDataLight;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Branded Shiki / expressive-code themes.
3
+ *
4
+ * The theme plugin applies these by default. Consumers can opt out by
5
+ * passing their own `expressiveCode.themes` in `astro.config.mjs` —
6
+ * Starlight's config wins over plugin defaults.
7
+ *
8
+ * @example
9
+ * ```js
10
+ * import { abstractDataThemes } from '@abstractdata/starlight-theme/shiki';
11
+ *
12
+ * starlight({
13
+ * expressiveCode: { themes: abstractDataThemes },
14
+ * });
15
+ * ```
16
+ */
17
+ export { abstractDataDark } from './abstract-data-dark.ts';
18
+ export { abstractDataLight } from './abstract-data-light.ts';
19
+
20
+ import { abstractDataDark } from './abstract-data-dark.ts';
21
+ import { abstractDataLight } from './abstract-data-light.ts';
22
+
23
+ /** Tuple [dark, light] suitable for Starlight's `expressiveCode.themes`. */
24
+ export const abstractDataThemes = [abstractDataDark, abstractDataLight] as const;
@@ -0,0 +1,229 @@
1
+ /* ============================================================
2
+ @abstractdata/starlight-theme — hud.css
3
+ Loaded only when motion: 'full'. Animated decoration on top of theme.css.
4
+ Auto-collapses via @media (prefers-reduced-motion: reduce).
5
+ ============================================================ */
6
+
7
+ /* ------------------------------------------------------------
8
+ Hexagon grid background on main content
9
+ ------------------------------------------------------------ */
10
+ .main-frame .main-pane,
11
+ [data-has-toc] .main-pane {
12
+ position: relative;
13
+ }
14
+
15
+ .main-frame .main-pane::before {
16
+ content: '';
17
+ position: absolute;
18
+ inset: 0;
19
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='87' viewBox='0 0 100 87'><path d='M25 0L50 14.43V43.3L25 57.74L0 43.3V14.43L25 0Z M75 0L100 14.43V43.3L75 57.74L50 43.3V14.43L75 0Z M50 43.3L75 57.74V86.6L50 101L25 86.6V57.74L50 43.3Z' fill='none' stroke='%2300d9ff' stroke-width='0.5' opacity='0.18'/></svg>");
20
+ background-size: 100px 87px;
21
+ pointer-events: none;
22
+ z-index: 0;
23
+ opacity: 0.55;
24
+ animation: ad-hex-pulse 5s ease-in-out infinite;
25
+ }
26
+ :root[data-theme='light'] .main-frame .main-pane::before {
27
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='87' viewBox='0 0 100 87'><path d='M25 0L50 14.43V43.3L25 57.74L0 43.3V14.43L25 0Z M75 0L100 14.43V43.3L75 57.74L50 43.3V14.43L75 0Z M50 43.3L75 57.74V86.6L50 101L25 86.6V57.74L50 43.3Z' fill='none' stroke='%237a1f2c' stroke-width='0.5' opacity='0.18'/></svg>");
28
+ opacity: 1;
29
+ animation: none; /* hex pulse off in light — feels more refined */
30
+ }
31
+
32
+ .main-frame .main-pane > * {
33
+ position: relative;
34
+ z-index: 1;
35
+ }
36
+
37
+ /* ------------------------------------------------------------
38
+ H1 cyan glow + underline fade (covers content h1 and hero h1)
39
+ ------------------------------------------------------------ */
40
+ .sl-markdown-content > h1:first-child,
41
+ .content-panel h1[data-page-title],
42
+ .hero h1 {
43
+ text-shadow: 0 0 14px rgba(0, 217, 255, 0.45),
44
+ 0 0 28px rgba(0, 217, 255, 0.18);
45
+ }
46
+ .sl-markdown-content > h1:first-child::after,
47
+ .content-panel h1[data-page-title]::after,
48
+ .hero h1::after {
49
+ content: '';
50
+ position: absolute;
51
+ left: 14px;
52
+ right: 0;
53
+ bottom: -8px;
54
+ height: 1px;
55
+ background: linear-gradient(90deg, var(--ad-cyan), transparent);
56
+ opacity: 0.7;
57
+ box-shadow: 0 0 8px rgba(0, 217, 255, 0.5);
58
+ }
59
+ :root[data-theme='light'] .sl-markdown-content > h1:first-child,
60
+ :root[data-theme='light'] .content-panel h1[data-page-title],
61
+ :root[data-theme='light'] .hero h1 {
62
+ text-shadow: none;
63
+ }
64
+ :root[data-theme='light'] .sl-markdown-content > h1:first-child::after,
65
+ :root[data-theme='light'] .content-panel h1[data-page-title]::after,
66
+ :root[data-theme='light'] .hero h1::after {
67
+ background: linear-gradient(90deg, var(--ad-burgundy), transparent);
68
+ opacity: 0.45;
69
+ box-shadow: none;
70
+ }
71
+
72
+ /* ------------------------------------------------------------
73
+ Active sidebar item — inset glow
74
+ ------------------------------------------------------------ */
75
+ .sidebar-pane a[aria-current='page'] {
76
+ box-shadow: inset 0 0 16px rgba(0, 217, 255, 0.18);
77
+ text-shadow: 0 0 8px rgba(0, 217, 255, 0.4);
78
+ }
79
+ :root[data-theme='light'] .sidebar-pane a[aria-current='page'] {
80
+ box-shadow: inset 0 0 12px rgba(0, 122, 142, 0.12);
81
+ text-shadow: none;
82
+ }
83
+
84
+ /* ------------------------------------------------------------
85
+ Code blocks — holographic shimmer + cyan halo
86
+ ------------------------------------------------------------ */
87
+ .expressive-code .frame {
88
+ position: relative;
89
+ box-shadow: 0 0 28px rgba(0, 217, 255, 0.12),
90
+ 0 0 0 1px rgba(0, 217, 255, 0.18);
91
+ }
92
+ .expressive-code .frame::after {
93
+ content: '';
94
+ position: absolute;
95
+ inset: 0;
96
+ background: linear-gradient(105deg,
97
+ transparent 30%,
98
+ rgba(0, 217, 255, 0.08) 50%,
99
+ transparent 70%);
100
+ background-size: 200% 100%;
101
+ pointer-events: none;
102
+ animation: ad-holographic-shift 6s linear infinite;
103
+ border-radius: inherit;
104
+ }
105
+ :root[data-theme='light'] .expressive-code .frame {
106
+ box-shadow: 0 0 18px rgba(122, 31, 44, 0.06),
107
+ 0 0 0 1px rgba(122, 31, 44, 0.12);
108
+ }
109
+ :root[data-theme='light'] .expressive-code .frame::after {
110
+ background: linear-gradient(105deg,
111
+ transparent 30%,
112
+ rgba(122, 31, 44, 0.05) 50%,
113
+ transparent 70%);
114
+ background-size: 200% 100%;
115
+ animation: ad-holographic-shift 8s linear infinite;
116
+ }
117
+
118
+ /* ------------------------------------------------------------
119
+ Callouts — matching color glow
120
+ ------------------------------------------------------------ */
121
+ .starlight-aside--tip,
122
+ .starlight-aside--note { box-shadow: 0 0 16px rgba(0, 217, 255, 0.10); }
123
+ .starlight-aside--caution { box-shadow: 0 0 16px rgba(212, 175, 55, 0.10); }
124
+ .starlight-aside--danger { box-shadow: 0 0 16px rgba(192, 74, 91, 0.14); }
125
+
126
+ /* ------------------------------------------------------------
127
+ Animations
128
+ ------------------------------------------------------------ */
129
+ @keyframes ad-hex-pulse {
130
+ 0%, 100% { opacity: 0.45; }
131
+ 50% { opacity: 0.75; }
132
+ }
133
+ @keyframes ad-holographic-shift {
134
+ 0% { background-position: -100% 0; }
135
+ 100% { background-position: 200% 0; }
136
+ }
137
+ @keyframes ad-glitch-text {
138
+ 0%, 95%, 100% { text-shadow: none; }
139
+ 96% { text-shadow: -1px 0 var(--ad-magenta), 1px 0 var(--ad-cyan); }
140
+ 97% { text-shadow: 1px 0 var(--ad-magenta), -1px 0 var(--ad-cyan); }
141
+ 98% { text-shadow: -1px 0 var(--ad-cyan), 1px 0 var(--ad-magenta); }
142
+ }
143
+
144
+ /* ------------------------------------------------------------
145
+ Version chip — gold glow + 9s glitch pulse + hover-trigger
146
+ ------------------------------------------------------------ */
147
+ .ad-version-chip {
148
+ box-shadow: 0 0 12px rgba(212, 175, 55, 0.4);
149
+ animation: ad-version-glitch-pulse 9s infinite;
150
+ }
151
+ .ad-version-chip:hover {
152
+ animation: ad-glitch-text 0.4s steps(2);
153
+ }
154
+ :root[data-theme='light'] .ad-version-chip {
155
+ box-shadow: 0 0 10px rgba(138, 109, 31, 0.25);
156
+ animation: none; /* idle pulse off in light mode — feels more refined */
157
+ }
158
+
159
+ @keyframes ad-version-glitch-pulse {
160
+ 0%, 95%, 100% { text-shadow: none; }
161
+ 96% { text-shadow: -1px 0 var(--ad-magenta), 1px 0 var(--ad-cyan); }
162
+ 97% { text-shadow: 1px 0 var(--ad-magenta), -1px 0 var(--ad-cyan); }
163
+ 98% { text-shadow: -1px 0 var(--ad-cyan), 1px 0 var(--ad-magenta); }
164
+ }
165
+
166
+ /* ------------------------------------------------------------
167
+ <Glitch /> — RGB-channel split with clip-path animation
168
+ ------------------------------------------------------------ */
169
+ .ad-glitch::before,
170
+ .ad-glitch::after {
171
+ content: attr(data-text);
172
+ position: absolute;
173
+ top: 0;
174
+ left: 0;
175
+ width: 100%;
176
+ height: 100%;
177
+ opacity: 0.85;
178
+ pointer-events: none;
179
+ }
180
+ .ad-glitch::before {
181
+ color: var(--ad-magenta);
182
+ animation: ad-glitch-clip-a 3s steps(1) infinite;
183
+ }
184
+ .ad-glitch::after {
185
+ color: var(--ad-cyan);
186
+ animation: ad-glitch-clip-b 3s steps(1) infinite;
187
+ }
188
+ :root[data-theme='light'] .ad-glitch::before {
189
+ color: var(--ad-burgundy);
190
+ }
191
+ :root[data-theme='light'] .ad-glitch::after {
192
+ color: var(--ad-cyan-deep);
193
+ }
194
+
195
+ @keyframes ad-glitch-clip-a {
196
+ 0%, 90%, 100% { transform: translate(0); clip-path: inset(100% 0 0 0); }
197
+ 91% { transform: translate(-2px, 1px); clip-path: inset(20% 0 60% 0); }
198
+ 93% { transform: translate(2px, -1px); clip-path: inset(60% 0 20% 0); }
199
+ 95% { transform: translate(-1px, -1px); clip-path: inset(40% 0 40% 0); }
200
+ 97% { transform: translate(1px, 0); clip-path: inset(10% 0 80% 0); }
201
+ }
202
+ @keyframes ad-glitch-clip-b {
203
+ 0%, 90%, 100% { transform: translate(0); clip-path: inset(100% 0 0 0); }
204
+ 91% { transform: translate(2px, -1px); clip-path: inset(60% 0 20% 0); }
205
+ 93% { transform: translate(-2px, 1px); clip-path: inset(20% 0 60% 0); }
206
+ 95% { transform: translate(1px, 1px); clip-path: inset(50% 0 30% 0); }
207
+ 97% { transform: translate(-1px, 0); clip-path: inset(70% 0 10% 0); }
208
+ }
209
+
210
+ /* ------------------------------------------------------------
211
+ prefers-reduced-motion: collapse to Calm behavior
212
+ ------------------------------------------------------------ */
213
+ @media (prefers-reduced-motion: reduce) {
214
+ .main-frame .main-pane::before,
215
+ .expressive-code .frame::after,
216
+ .ad-version-chip,
217
+ .ad-glitch::before,
218
+ .ad-glitch::after {
219
+ animation: none !important;
220
+ }
221
+ .ad-glitch::before,
222
+ .ad-glitch::after {
223
+ display: none;
224
+ }
225
+ .sl-markdown-content > h1:first-child,
226
+ .content-panel h1[data-page-title] {
227
+ text-shadow: none;
228
+ }
229
+ }
@@ -0,0 +1,380 @@
1
+ /* ============================================================
2
+ @abstractdata/starlight-theme — theme.css
3
+ Always loaded. Brand palette, typography, signature decoration.
4
+ ============================================================ */
5
+
6
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&family=Orbitron:wght@500;600;700;800&display=swap');
7
+
8
+ /* ------------------------------------------------------------
9
+ Palette tokens — overridden onto Starlight's CSS vars
10
+ ------------------------------------------------------------ */
11
+ :root {
12
+ /* Brand atoms (always available regardless of theme) */
13
+ --ad-cyan: #00d9ff;
14
+ --ad-cyan-deep: #007a8e;
15
+ --ad-gold: #d4af37;
16
+ --ad-gold-deep: #8a6d1f;
17
+ --ad-burgundy: #8b2635;
18
+ --ad-burgundy-deep: #7a1f2c;
19
+ --ad-magenta: #ff00de;
20
+ --ad-charcoal: #0a0a0a;
21
+ --ad-surface-dark: #101116;
22
+ --ad-cream: #faf7f2;
23
+
24
+ /* Typography */
25
+ --sl-font: 'Inter', system-ui, -apple-system, sans-serif;
26
+ --sl-font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
27
+ --ad-font-display: 'Orbitron', sans-serif;
28
+
29
+ /* Sizing tweaks */
30
+ --sl-content-width: 52rem;
31
+ }
32
+
33
+ /* ------------------------------------------------------------
34
+ Dark theme (HUD Dark / Calm Dark — same palette)
35
+ ------------------------------------------------------------ */
36
+ :root[data-theme='dark'] {
37
+ --sl-color-accent-low: rgba(0, 217, 255, 0.10);
38
+ --sl-color-accent: var(--ad-cyan);
39
+ --sl-color-accent-high: var(--ad-cyan);
40
+ --sl-color-text-accent: var(--ad-cyan);
41
+
42
+ --sl-color-bg: var(--ad-charcoal);
43
+ --sl-color-bg-nav: var(--ad-surface-dark);
44
+ --sl-color-bg-sidebar: var(--ad-surface-dark);
45
+ --sl-color-bg-inline-code: #14151a;
46
+ --sl-color-hairline: #2a2c34;
47
+ --sl-color-hairline-light: #25262d;
48
+ --sl-color-hairline-shade: #1a1b20;
49
+
50
+ --sl-color-text: #f0f0f5;
51
+ --sl-color-text-invert: var(--ad-charcoal);
52
+ --sl-color-gray-1: #ffffff;
53
+ --sl-color-gray-2: #d8d8df;
54
+ --sl-color-gray-3: #b8b8c0;
55
+ --sl-color-gray-4: #8a8a93;
56
+ --sl-color-gray-5: #5a5b62;
57
+ --sl-color-gray-6: #2a2c34;
58
+
59
+ --ad-text-dim: #8a8a93;
60
+ --ad-callout-cyan-bg: rgba(0, 217, 255, 0.06);
61
+ --ad-callout-gold-bg: rgba(212, 175, 55, 0.06);
62
+ --ad-callout-burgundy-bg: rgba(192, 74, 91, 0.08);
63
+ }
64
+
65
+ /* ------------------------------------------------------------
66
+ Light theme (cream / burgundy / deeper teal)
67
+ ------------------------------------------------------------ */
68
+ :root[data-theme='light'] {
69
+ --sl-color-accent-low: rgba(0, 122, 142, 0.10);
70
+ --sl-color-accent: var(--ad-cyan-deep);
71
+ --sl-color-accent-high: var(--ad-cyan-deep);
72
+ --sl-color-text-accent: var(--ad-cyan-deep);
73
+
74
+ --sl-color-bg: var(--ad-cream);
75
+ --sl-color-bg-nav: #ffffff;
76
+ --sl-color-bg-sidebar: #ffffff;
77
+ --sl-color-bg-inline-code: #f4efe5;
78
+ --sl-color-hairline: #d8d0bf;
79
+ --sl-color-hairline-light: #e6dfd1;
80
+ --sl-color-hairline-shade: #ece5d5;
81
+
82
+ --sl-color-text: #18181c;
83
+ --sl-color-text-invert: #ffffff;
84
+ --sl-color-gray-1: #0a0a0a;
85
+ --sl-color-gray-2: #18181c;
86
+ --sl-color-gray-3: #2a2a30;
87
+ --sl-color-gray-4: #5e5e66;
88
+ --sl-color-gray-5: #8e8a7e;
89
+ --sl-color-gray-6: #d8d0bf;
90
+
91
+ --ad-text-dim: #5e5e66;
92
+ --ad-callout-cyan-bg: rgba(0, 122, 142, 0.06);
93
+ --ad-callout-gold-bg: rgba(138, 109, 31, 0.08);
94
+ --ad-callout-burgundy-bg: rgba(122, 31, 44, 0.06);
95
+ }
96
+
97
+ /* ------------------------------------------------------------
98
+ Display typography — Orbitron on hero + h1/h2 only
99
+ (h3+ stays in Inter for body readability)
100
+ ------------------------------------------------------------ */
101
+ .sl-markdown-content h1,
102
+ .sl-markdown-content h2,
103
+ header.header .site-title,
104
+ .sidebar-pane .group-label,
105
+ .content-panel h1[data-page-title] {
106
+ font-family: var(--ad-font-display);
107
+ letter-spacing: 0.01em;
108
+ font-weight: 700;
109
+ }
110
+
111
+ /* Site title in top nav */
112
+ header.header .site-title {
113
+ font-size: 0.85rem;
114
+ letter-spacing: 0.16em;
115
+ text-transform: uppercase;
116
+ }
117
+
118
+ /* Sidebar group labels */
119
+ .sidebar-pane .group-label,
120
+ .sidebar-pane .group-label > span {
121
+ font-family: var(--ad-font-display);
122
+ font-size: 0.7rem;
123
+ letter-spacing: 0.16em;
124
+ text-transform: uppercase;
125
+ }
126
+
127
+ /* ------------------------------------------------------------
128
+ H1 signature: cyan→gold gradient bracket bar on the left
129
+ ------------------------------------------------------------ */
130
+ .sl-markdown-content > h1:first-child,
131
+ .content-panel h1[data-page-title],
132
+ .hero h1 {
133
+ position: relative;
134
+ padding-left: 14px;
135
+ }
136
+ .sl-markdown-content > h1:first-child::before,
137
+ .content-panel h1[data-page-title]::before,
138
+ .hero h1::before {
139
+ content: '';
140
+ position: absolute;
141
+ left: 0;
142
+ top: 0.3em;
143
+ bottom: 0.3em;
144
+ width: 3px;
145
+ background: linear-gradient(180deg, var(--ad-cyan), var(--ad-gold));
146
+ }
147
+ :root[data-theme='light'] .sl-markdown-content > h1:first-child::before,
148
+ :root[data-theme='light'] .content-panel h1[data-page-title]::before,
149
+ :root[data-theme='light'] .hero h1::before {
150
+ background: linear-gradient(180deg, var(--ad-cyan-deep), var(--ad-burgundy));
151
+ }
152
+
153
+ /* ------------------------------------------------------------
154
+ Inline code — gold on dark, burgundy on light
155
+ ------------------------------------------------------------ */
156
+ .sl-markdown-content :not(pre) > code {
157
+ color: var(--ad-gold);
158
+ border: 1px solid var(--sl-color-hairline);
159
+ border-radius: 4px;
160
+ padding: 1px 6px;
161
+ font-size: 0.9em;
162
+ }
163
+ :root[data-theme='light'] .sl-markdown-content :not(pre) > code {
164
+ color: var(--ad-burgundy-deep);
165
+ }
166
+
167
+ /* ------------------------------------------------------------
168
+ Callouts (asides) — branded variants
169
+ ------------------------------------------------------------ */
170
+ .starlight-aside {
171
+ border-left-width: 3px;
172
+ border-radius: 8px;
173
+ background: var(--ad-callout-cyan-bg);
174
+ }
175
+ .starlight-aside--tip { border-color: var(--ad-cyan); background: var(--ad-callout-cyan-bg); }
176
+ .starlight-aside--note { border-color: var(--ad-cyan); background: var(--ad-callout-cyan-bg); }
177
+ .starlight-aside--caution { border-color: var(--ad-gold); background: var(--ad-callout-gold-bg); }
178
+ .starlight-aside--danger { border-color: var(--ad-burgundy); background: var(--ad-callout-burgundy-bg); }
179
+ :root[data-theme='light'] .starlight-aside--tip,
180
+ :root[data-theme='light'] .starlight-aside--note { border-color: var(--ad-cyan-deep); }
181
+ :root[data-theme='light'] .starlight-aside--caution { border-color: var(--ad-gold-deep); }
182
+ :root[data-theme='light'] .starlight-aside--danger { border-color: var(--ad-burgundy-deep); }
183
+
184
+ .starlight-aside__title {
185
+ font-family: var(--ad-font-display);
186
+ font-size: 0.7rem;
187
+ letter-spacing: 0.16em;
188
+ text-transform: uppercase;
189
+ }
190
+
191
+ /* ------------------------------------------------------------
192
+ Code blocks — bracket dot indicator + cyan border accent
193
+ ------------------------------------------------------------ */
194
+ .expressive-code .frame.is-terminal,
195
+ .expressive-code .frame.has-title {
196
+ border: 1px solid var(--sl-color-hairline);
197
+ border-radius: 8px;
198
+ overflow: hidden;
199
+ }
200
+ .expressive-code .frame .header {
201
+ font-family: var(--sl-font-mono);
202
+ font-size: 0.7rem;
203
+ letter-spacing: 0.05em;
204
+ border-bottom: 1px solid var(--sl-color-hairline);
205
+ }
206
+
207
+ /* ------------------------------------------------------------
208
+ Sidebar — active item with cyan border + faint fill
209
+ ------------------------------------------------------------ */
210
+ .sidebar-pane a[aria-current='page'] {
211
+ color: var(--ad-cyan);
212
+ border-left: 2px solid var(--ad-cyan);
213
+ background: var(--sl-color-accent-low);
214
+ font-weight: 500;
215
+ }
216
+ :root[data-theme='light'] .sidebar-pane a[aria-current='page'] {
217
+ color: var(--ad-cyan-deep);
218
+ border-left-color: var(--ad-cyan-deep);
219
+ }
220
+
221
+ /* ------------------------------------------------------------
222
+ Search box — bracket frame motif
223
+ ------------------------------------------------------------ */
224
+ site-search button[data-open-modal] {
225
+ border: 1px solid var(--sl-color-hairline);
226
+ border-radius: 6px;
227
+ font-family: var(--sl-font-mono);
228
+ }
229
+ site-search button[data-open-modal] kbd {
230
+ font-family: var(--sl-font-mono);
231
+ font-size: 0.65rem;
232
+ }
233
+
234
+ /* ------------------------------------------------------------
235
+ Version chip — outlined gold pill in the header
236
+ (Static look. HUD layers on glitch in hud.css.)
237
+ ------------------------------------------------------------ */
238
+ .ad-version-chip {
239
+ display: inline-flex;
240
+ align-items: center;
241
+ font-family: var(--sl-font-mono);
242
+ font-size: 0.7rem;
243
+ padding: 3px 8px;
244
+ margin-left: 8px;
245
+ color: var(--ad-gold);
246
+ border: 1px solid var(--ad-gold);
247
+ border-radius: 4px;
248
+ letter-spacing: 0.04em;
249
+ user-select: none;
250
+ cursor: default;
251
+ position: relative;
252
+ }
253
+ :root[data-theme='light'] .ad-version-chip {
254
+ color: var(--ad-gold-deep);
255
+ border-color: var(--ad-gold-deep);
256
+ background: rgba(138, 109, 31, 0.08);
257
+ }
258
+
259
+ /* ------------------------------------------------------------
260
+ "Built by Abstract Data" credit
261
+ Hidden when the plugin is configured with `credit: 'hide'`.
262
+ ------------------------------------------------------------ */
263
+ .ad-credit {
264
+ margin-top: 1.5rem;
265
+ padding-top: 1rem;
266
+ border-top: 1px solid var(--sl-color-hairline-light);
267
+ font-family: var(--ad-font-display);
268
+ font-size: 0.7rem;
269
+ letter-spacing: 0.16em;
270
+ text-transform: uppercase;
271
+ color: var(--sl-color-gray-4);
272
+ display: flex;
273
+ gap: 6px;
274
+ align-items: center;
275
+ }
276
+ .ad-credit a {
277
+ color: var(--ad-cyan);
278
+ text-decoration: none;
279
+ font-weight: 600;
280
+ }
281
+ .ad-credit a:hover { text-decoration: underline; }
282
+ :root[data-theme='light'] .ad-credit a { color: var(--ad-cyan-deep); }
283
+
284
+ /* ------------------------------------------------------------
285
+ <Glitch /> base — clean render under motion: 'calm'
286
+ HUD layers on the actual glitch animation in hud.css.
287
+ ------------------------------------------------------------ */
288
+ .ad-glitch {
289
+ position: relative;
290
+ display: inline-block;
291
+ font-family: var(--ad-font-display);
292
+ font-weight: 700;
293
+ letter-spacing: 0.02em;
294
+ color: var(--ad-cyan);
295
+ }
296
+ :root[data-theme='light'] .ad-glitch {
297
+ color: var(--ad-burgundy);
298
+ }
299
+
300
+ /* ------------------------------------------------------------
301
+ Splash page hero — constrain logo width on wide screens
302
+ ------------------------------------------------------------ */
303
+ .hero .hero-html img,
304
+ .hero img[alt][src] {
305
+ max-width: 360px;
306
+ width: 100%;
307
+ height: auto;
308
+ }
309
+
310
+ /* ------------------------------------------------------------
311
+ Hero action buttons — primary cyan, secondary gold ghost
312
+ (Starlight uses .sl-link-button.primary / .secondary inside .hero .actions)
313
+ ------------------------------------------------------------ */
314
+ .hero .actions .sl-link-button.primary {
315
+ background: var(--ad-cyan);
316
+ color: var(--ad-charcoal);
317
+ border: 1px solid var(--ad-cyan);
318
+ font-family: var(--ad-font-display);
319
+ font-size: 0.8rem;
320
+ letter-spacing: 0.12em;
321
+ text-transform: uppercase;
322
+ }
323
+ .hero .actions .sl-link-button.primary:hover {
324
+ box-shadow: 0 0 24px rgba(0, 217, 255, 0.5);
325
+ }
326
+ .hero .actions .sl-link-button.secondary,
327
+ .hero .actions .sl-link-button.minimal {
328
+ background: transparent;
329
+ color: var(--ad-gold);
330
+ border: 1px solid var(--ad-gold);
331
+ font-family: var(--ad-font-display);
332
+ font-size: 0.8rem;
333
+ letter-spacing: 0.12em;
334
+ text-transform: uppercase;
335
+ }
336
+ .hero .actions .sl-link-button.secondary:hover,
337
+ .hero .actions .sl-link-button.minimal:hover {
338
+ background: rgba(212, 175, 55, 0.10);
339
+ color: var(--ad-gold);
340
+ }
341
+ :root[data-theme='light'] .hero .actions .sl-link-button.primary {
342
+ background: var(--ad-cyan-deep);
343
+ color: #ffffff;
344
+ border-color: var(--ad-cyan-deep);
345
+ }
346
+ :root[data-theme='light'] .hero .actions .sl-link-button.secondary,
347
+ :root[data-theme='light'] .hero .actions .sl-link-button.minimal {
348
+ color: var(--ad-burgundy-deep);
349
+ border-color: var(--ad-burgundy-deep);
350
+ }
351
+ :root[data-theme='light'] .hero .actions .sl-link-button.secondary:hover,
352
+ :root[data-theme='light'] .hero .actions .sl-link-button.minimal:hover {
353
+ background: rgba(122, 31, 44, 0.06);
354
+ color: var(--ad-burgundy-deep);
355
+ }
356
+
357
+ /* ------------------------------------------------------------
358
+ "On this page" right sidebar (TOC) — branded
359
+ ------------------------------------------------------------ */
360
+ .right-sidebar h2,
361
+ .right-sidebar-panel h2 {
362
+ font-family: var(--ad-font-display);
363
+ font-size: 0.7rem;
364
+ font-weight: 700;
365
+ letter-spacing: 0.16em;
366
+ text-transform: uppercase;
367
+ color: var(--sl-color-gray-3);
368
+ }
369
+ .right-sidebar a[aria-current='true'],
370
+ starlight-toc a[aria-current='true'] {
371
+ color: var(--ad-cyan);
372
+ border-left: 2px solid var(--ad-cyan);
373
+ padding-left: 8px;
374
+ margin-left: -10px;
375
+ }
376
+ :root[data-theme='light'] .right-sidebar a[aria-current='true'],
377
+ :root[data-theme='light'] starlight-toc a[aria-current='true'] {
378
+ color: var(--ad-cyan-deep);
379
+ border-left-color: var(--ad-cyan-deep);
380
+ }
package/src/types.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Virtual module exposed by @abstractdata/starlight-theme.
3
+ * Available inside Astro components after the plugin is registered.
4
+ *
5
+ * Reference this in your project's `env.d.ts`:
6
+ * ```ts
7
+ * /// <reference types="@abstractdata/starlight-theme/types" />
8
+ * ```
9
+ */
10
+ declare module 'virtual:abstractdata/config' {
11
+ export const config: {
12
+ motion: 'full' | 'calm';
13
+ credit: 'auto' | 'hide';
14
+ version: string | null;
15
+ };
16
+ }