@astronox/core 0.4.7 → 0.4.9
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/package.json +1 -1
- package/src/ads/adsense/AdsenseHead.astro +6 -40
- package/src/ads/adsense/index.ts +0 -1
- package/src/ads/adsense/provider.ts +0 -32
- package/src/ads/adsense/runtime.ts +1 -257
- package/src/ads/contracts.ts +0 -47
- package/src/ads/index.ts +0 -1
- package/src/components/index.ts +0 -1
- package/src/layouts/BaseLayout.astro +25 -23
- package/src/patterns/MapViewport/MapViewport.css +3 -3
- package/src/styles/base.css +8 -2
- package/src/styles/style.css +0 -1
- package/src/styles/tokens.css +1 -9
- package/src/ads/adsense/AdsenseUnit.astro +0 -45
- package/src/ads/slot.ts +0 -16
- package/src/components/monetization/AdSlot/AdSlot.astro +0 -30
- package/src/components/monetization/AdSlot/AdSlot.css +0 -30
package/package.json
CHANGED
|
@@ -1,55 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { AdsenseAdsConfig } from '../contracts';
|
|
3
|
-
import {
|
|
4
|
-
renderAdsenseControlledMessagingScript,
|
|
5
|
-
renderAdsenseErrorProtectionScript,
|
|
6
|
-
toAdsenseClientId,
|
|
7
|
-
} from './provider';
|
|
3
|
+
import { toAdsenseClientId } from './provider';
|
|
8
4
|
|
|
9
5
|
type Props = {
|
|
10
6
|
config: AdsenseAdsConfig;
|
|
11
|
-
offerwallEligible: boolean;
|
|
12
7
|
};
|
|
13
8
|
|
|
14
|
-
const { config
|
|
9
|
+
const { config }: Props = Astro.props;
|
|
15
10
|
const clientId = toAdsenseClientId(config.publisherId);
|
|
16
|
-
const
|
|
17
|
-
allowedHosts: config.allowedHosts,
|
|
18
|
-
clientId,
|
|
19
|
-
messaging: {
|
|
20
|
-
adBlockingRecovery: config.messaging.adBlockingRecovery,
|
|
21
|
-
offerwall: config.messaging.offerwall,
|
|
22
|
-
offerwallEligible,
|
|
23
|
-
},
|
|
24
|
-
publisherId: config.publisherId,
|
|
25
|
-
}).replaceAll('<', '\\u003c');
|
|
26
|
-
const errorProtectionScript = config.messaging.errorProtectionCode
|
|
27
|
-
? renderAdsenseErrorProtectionScript({
|
|
28
|
-
allowedHosts: config.allowedHosts,
|
|
29
|
-
code: config.messaging.errorProtectionCode,
|
|
30
|
-
})
|
|
31
|
-
: undefined;
|
|
32
|
-
const controlledMessagingScript = renderAdsenseControlledMessagingScript(
|
|
33
|
-
config.messaging.offerwall && offerwallEligible,
|
|
34
|
-
);
|
|
11
|
+
const scriptSource = `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${encodeURIComponent(clientId)}`;
|
|
35
12
|
---
|
|
36
13
|
|
|
37
14
|
<meta name="google-adsense-account" content={clientId} />
|
|
38
|
-
<script
|
|
39
|
-
is:inline
|
|
40
|
-
type="application/json"
|
|
41
|
-
data-astronox-adsense-config
|
|
42
|
-
set:html={runtimeConfig}
|
|
43
|
-
/>
|
|
44
|
-
<script is:inline set:html={controlledMessagingScript} />
|
|
45
|
-
{
|
|
46
|
-
errorProtectionScript ? (
|
|
47
|
-
<script is:inline set:html={errorProtectionScript} />
|
|
48
|
-
) : null
|
|
49
|
-
}
|
|
15
|
+
<script is:inline async crossorigin="anonymous" src={scriptSource}></script>
|
|
50
16
|
|
|
51
17
|
<script>
|
|
52
|
-
import {
|
|
18
|
+
import { initAdAnnotationProtection } from './runtime';
|
|
53
19
|
|
|
54
|
-
|
|
20
|
+
initAdAnnotationProtection();
|
|
55
21
|
</script>
|
package/src/ads/adsense/index.ts
CHANGED
|
@@ -7,35 +7,3 @@ export function toAdsenseClientId(publisherId: string): string {
|
|
|
7
7
|
export function renderAdsenseAdsTxt(publisherId: string): string {
|
|
8
8
|
return `google.com, ${publisherId}, DIRECT, ${adsenseSellerId}\n`;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
export function renderAdsenseErrorProtectionScript({
|
|
12
|
-
allowedHosts,
|
|
13
|
-
code,
|
|
14
|
-
}: {
|
|
15
|
-
allowedHosts: readonly string[];
|
|
16
|
-
code: string;
|
|
17
|
-
}): string {
|
|
18
|
-
const hosts = JSON.stringify(
|
|
19
|
-
allowedHosts.map((host) => host.toLowerCase()),
|
|
20
|
-
).replaceAll('<', '\\u003c');
|
|
21
|
-
|
|
22
|
-
return `if (${hosts}.includes(window.location.hostname.toLowerCase())) {\n${code}\n}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function renderAdsenseControlledMessagingScript(
|
|
26
|
-
offerwallAllowed: boolean,
|
|
27
|
-
): string {
|
|
28
|
-
return `window.googlefc = window.googlefc || {};
|
|
29
|
-
window.googlefc.controlledMessagingFunction = function (message) {
|
|
30
|
-
if (${String(offerwallAllowed)}) {
|
|
31
|
-
message.proceed(true);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
var offerwall = window.googlefc.MessageTypeEnum && window.googlefc.MessageTypeEnum.OFFERWALL;
|
|
35
|
-
if (typeof offerwall === 'number') {
|
|
36
|
-
message.proceed(false, [offerwall]);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
message.proceed(true);
|
|
40
|
-
};`;
|
|
41
|
-
}
|
|
@@ -1,89 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const adsenseScriptTimeoutMs = 12_000;
|
|
4
|
-
const adsenseUnitTimeoutMs = 30_000;
|
|
5
|
-
|
|
6
|
-
type ControlledMessage = {
|
|
7
|
-
proceed(allowed: boolean, messageTypes?: readonly number[]): void;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
type GoogleFundingChoicesCallback =
|
|
11
|
-
(() => void) | { CONSENT_API_READY: () => void };
|
|
12
|
-
|
|
13
|
-
type GoogleFundingChoices = {
|
|
14
|
-
callbackQueue?: {
|
|
15
|
-
push(callback: GoogleFundingChoicesCallback): number;
|
|
16
|
-
};
|
|
17
|
-
controlledMessagingFunction?: (message: ControlledMessage) => void;
|
|
18
|
-
MessageTypeEnum?: {
|
|
19
|
-
OFFERWALL: number;
|
|
20
|
-
};
|
|
21
|
-
showRevocationMessage?: () => void;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
type AdsenseRuntimeConfig = {
|
|
25
|
-
allowedHosts: readonly string[];
|
|
26
|
-
clientId: string;
|
|
27
|
-
messaging: {
|
|
28
|
-
adBlockingRecovery: boolean;
|
|
29
|
-
offerwall: boolean;
|
|
30
|
-
offerwallEligible: boolean;
|
|
31
|
-
};
|
|
32
|
-
publisherId: string;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
declare global {
|
|
36
|
-
interface Window {
|
|
37
|
-
adsbygoogle?: Record<string, unknown>[];
|
|
38
|
-
googlefc?: GoogleFundingChoices;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
let adsenseScript: Promise<void> | undefined;
|
|
43
|
-
|
|
44
|
-
export function initAdsenseFromDocument(): void {
|
|
45
|
-
const config = readRuntimeConfig();
|
|
46
|
-
if (!config || !isAllowedHost(config.allowedHosts)) {
|
|
47
|
-
setAllAdUnitsState('unavailable');
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
1
|
+
export function initAdAnnotationProtection(): void {
|
|
51
2
|
protectAdAnnotations();
|
|
52
|
-
configureControlledMessaging(config);
|
|
53
|
-
initPrivacySettingsControls();
|
|
54
|
-
if (config.messaging.adBlockingRecovery) {
|
|
55
|
-
loadAdBlockingRecovery(config.publisherId);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
void loadAdsenseScript(config.clientId)
|
|
59
|
-
.then(initAdUnits)
|
|
60
|
-
.catch(() => setAllAdUnitsState('blocked'));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function readRuntimeConfig(): AdsenseRuntimeConfig | undefined {
|
|
64
|
-
const element = document.querySelector<HTMLScriptElement>(
|
|
65
|
-
'script[data-astronox-adsense-config]',
|
|
66
|
-
);
|
|
67
|
-
if (!element?.textContent) return undefined;
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
return JSON.parse(element.textContent) as AdsenseRuntimeConfig;
|
|
71
|
-
} catch {
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function isAllowedHost(allowedHosts: readonly string[]): boolean {
|
|
77
|
-
const hostname = window.location.hostname.toLowerCase();
|
|
78
|
-
if (
|
|
79
|
-
hostname === 'localhost' ||
|
|
80
|
-
hostname.endsWith('.localhost') ||
|
|
81
|
-
hostname === '127.0.0.1' ||
|
|
82
|
-
hostname === '::1'
|
|
83
|
-
) {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
return allowedHosts.some((host) => host.toLowerCase() === hostname);
|
|
87
3
|
}
|
|
88
4
|
|
|
89
5
|
function protectAdAnnotations(): void {
|
|
@@ -110,175 +26,3 @@ function protectAdAnnotations(): void {
|
|
|
110
26
|
}
|
|
111
27
|
}).observe(document.documentElement, { childList: true, subtree: true });
|
|
112
28
|
}
|
|
113
|
-
|
|
114
|
-
function configureControlledMessaging(config: AdsenseRuntimeConfig): void {
|
|
115
|
-
const googlefc = (window.googlefc ??= {});
|
|
116
|
-
googlefc.controlledMessagingFunction = (message) => {
|
|
117
|
-
const allowOfferwall =
|
|
118
|
-
config.messaging.offerwall && config.messaging.offerwallEligible;
|
|
119
|
-
if (allowOfferwall) {
|
|
120
|
-
message.proceed(true);
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const offerwallType = window.googlefc?.MessageTypeEnum?.OFFERWALL;
|
|
125
|
-
if (typeof offerwallType === 'number') {
|
|
126
|
-
message.proceed(false, [offerwallType]);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
message.proceed(true);
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function initPrivacySettingsControls(): void {
|
|
135
|
-
const googlefc = (window.googlefc ??= {});
|
|
136
|
-
googlefc.callbackQueue ??= [];
|
|
137
|
-
googlefc.callbackQueue.push({
|
|
138
|
-
CONSENT_API_READY: () => {
|
|
139
|
-
for (const control of document.querySelectorAll<HTMLButtonElement>(
|
|
140
|
-
'[data-ads-consent-settings]:not([data-ads-consent-initialized])',
|
|
141
|
-
)) {
|
|
142
|
-
control.dataset.adsConsentInitialized = 'true';
|
|
143
|
-
control.hidden = false;
|
|
144
|
-
control.addEventListener('click', () => {
|
|
145
|
-
const showRevocationMessage = window.googlefc?.showRevocationMessage;
|
|
146
|
-
if (showRevocationMessage) {
|
|
147
|
-
window.googlefc?.callbackQueue?.push(showRevocationMessage);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function loadAdBlockingRecovery(publisherId: string): void {
|
|
156
|
-
if (document.querySelector('script[data-astronox-ad-recovery]')) return;
|
|
157
|
-
|
|
158
|
-
const script = document.createElement('script');
|
|
159
|
-
script.async = true;
|
|
160
|
-
script.dataset.astronoxAdRecovery = '';
|
|
161
|
-
script.src = `https://fundingchoicesmessages.google.com/i/${encodeURIComponent(publisherId)}?ers=1`;
|
|
162
|
-
document.head.append(script);
|
|
163
|
-
signalFundingChoicesPresence();
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function signalFundingChoicesPresence(): void {
|
|
167
|
-
if (document.querySelector('iframe[name="googlefcPresent"]')) return;
|
|
168
|
-
if (!document.body) {
|
|
169
|
-
window.setTimeout(signalFundingChoicesPresence, 0);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const frame = document.createElement('iframe');
|
|
174
|
-
frame.name = 'googlefcPresent';
|
|
175
|
-
frame.hidden = true;
|
|
176
|
-
frame.style.display = 'none';
|
|
177
|
-
document.body.append(frame);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function loadAdsenseScript(clientId: string): Promise<void> {
|
|
181
|
-
if (adsenseScript) return adsenseScript;
|
|
182
|
-
|
|
183
|
-
const loading = new Promise<void>((resolve, reject) => {
|
|
184
|
-
const existing = document.querySelector<HTMLScriptElement>(
|
|
185
|
-
'script[data-astronox-adsense]',
|
|
186
|
-
);
|
|
187
|
-
if (existing?.dataset.loaded === 'true') {
|
|
188
|
-
resolve();
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const script = existing ?? document.createElement('script');
|
|
193
|
-
const timeout = window.setTimeout(() => {
|
|
194
|
-
reject(new Error('AdSense loading timed out'));
|
|
195
|
-
}, adsenseScriptTimeoutMs);
|
|
196
|
-
script.addEventListener(
|
|
197
|
-
'load',
|
|
198
|
-
() => {
|
|
199
|
-
window.clearTimeout(timeout);
|
|
200
|
-
script.dataset.loaded = 'true';
|
|
201
|
-
resolve();
|
|
202
|
-
},
|
|
203
|
-
{ once: true },
|
|
204
|
-
);
|
|
205
|
-
script.addEventListener(
|
|
206
|
-
'error',
|
|
207
|
-
() => {
|
|
208
|
-
window.clearTimeout(timeout);
|
|
209
|
-
reject(new Error('AdSense failed to load'));
|
|
210
|
-
},
|
|
211
|
-
{ once: true },
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
if (!existing) {
|
|
215
|
-
script.async = true;
|
|
216
|
-
script.crossOrigin = 'anonymous';
|
|
217
|
-
script.dataset.astronoxAdsense = '';
|
|
218
|
-
script.src = `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${encodeURIComponent(clientId)}`;
|
|
219
|
-
document.head.append(script);
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
adsenseScript = loading;
|
|
223
|
-
void loading.catch(() => {
|
|
224
|
-
if (adsenseScript === loading) adsenseScript = undefined;
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
return loading;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function initAdUnits(): void {
|
|
231
|
-
for (const slot of document.querySelectorAll<HTMLElement>(
|
|
232
|
-
'[data-adsense-unit]:not([data-adsense-initialized])',
|
|
233
|
-
)) {
|
|
234
|
-
initAdUnit(slot);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function initAdUnit(slot: HTMLElement): void {
|
|
239
|
-
slot.dataset.adsenseInitialized = 'true';
|
|
240
|
-
setAdSlotState(slot, 'loading');
|
|
241
|
-
const advertisement = slot.querySelector<HTMLElement>('.adsbygoogle');
|
|
242
|
-
if (!advertisement) {
|
|
243
|
-
setAdSlotState(slot, 'unavailable');
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const timeout = window.setTimeout(() => {
|
|
248
|
-
setAdSlotState(slot, 'unavailable');
|
|
249
|
-
}, adsenseUnitTimeoutMs);
|
|
250
|
-
const sync = (): void => {
|
|
251
|
-
if (advertisement.dataset.adStatus === 'filled') {
|
|
252
|
-
window.clearTimeout(timeout);
|
|
253
|
-
observer.disconnect();
|
|
254
|
-
setAdSlotState(slot, 'filled');
|
|
255
|
-
} else if (advertisement.dataset.adStatus === 'unfilled') {
|
|
256
|
-
window.clearTimeout(timeout);
|
|
257
|
-
observer.disconnect();
|
|
258
|
-
setAdSlotState(slot, 'unavailable');
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
const observer = new MutationObserver(sync);
|
|
262
|
-
observer.observe(advertisement, {
|
|
263
|
-
attributes: true,
|
|
264
|
-
attributeFilter: ['data-ad-status'],
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
try {
|
|
268
|
-
window.adsbygoogle ??= [];
|
|
269
|
-
window.adsbygoogle.push({});
|
|
270
|
-
sync();
|
|
271
|
-
} catch {
|
|
272
|
-
window.clearTimeout(timeout);
|
|
273
|
-
observer.disconnect();
|
|
274
|
-
setAdSlotState(slot, 'unavailable');
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function setAllAdUnitsState(state: 'blocked' | 'unavailable'): void {
|
|
279
|
-
for (const slot of document.querySelectorAll<HTMLElement>(
|
|
280
|
-
'[data-adsense-unit]',
|
|
281
|
-
)) {
|
|
282
|
-
setAdSlotState(slot, state);
|
|
283
|
-
}
|
|
284
|
-
}
|
package/src/ads/contracts.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
export type AdSlotState =
|
|
2
|
-
'idle' | 'loading' | 'filled' | 'blocked' | 'unavailable';
|
|
3
|
-
|
|
4
1
|
export type DisabledAdsConfig = {
|
|
5
2
|
enabled: false;
|
|
6
3
|
};
|
|
@@ -8,16 +5,7 @@ export type DisabledAdsConfig = {
|
|
|
8
5
|
export type AdsenseAdsConfig = {
|
|
9
6
|
enabled: true;
|
|
10
7
|
provider: 'adsense';
|
|
11
|
-
allowedHosts: readonly string[];
|
|
12
8
|
publisherId: string;
|
|
13
|
-
units: {
|
|
14
|
-
pageEnd: string;
|
|
15
|
-
};
|
|
16
|
-
messaging: {
|
|
17
|
-
adBlockingRecovery: boolean;
|
|
18
|
-
offerwall: boolean;
|
|
19
|
-
errorProtectionCode?: string;
|
|
20
|
-
};
|
|
21
9
|
};
|
|
22
10
|
|
|
23
11
|
export type AdsConfig = DisabledAdsConfig | AdsenseAdsConfig;
|
|
@@ -31,43 +19,8 @@ export function defineAdsConfig<const TConfig extends AdsConfig>(
|
|
|
31
19
|
): TConfig {
|
|
32
20
|
if (!config.enabled) return config;
|
|
33
21
|
|
|
34
|
-
if (config.allowedHosts.length === 0) {
|
|
35
|
-
throw new Error('Ad configuration requires at least one allowed hostname');
|
|
36
|
-
}
|
|
37
|
-
for (const host of config.allowedHosts) {
|
|
38
|
-
if (!isValidHostname(host)) {
|
|
39
|
-
throw new Error(`Invalid advertising hostname: ${host}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
22
|
if (!/^pub-\d+$/.test(config.publisherId)) {
|
|
43
23
|
throw new Error('Invalid AdSense publisher id');
|
|
44
24
|
}
|
|
45
|
-
if (!/^\d+$/.test(config.units.pageEnd)) {
|
|
46
|
-
throw new Error('Invalid AdSense page-end slot id');
|
|
47
|
-
}
|
|
48
|
-
if (/<\/script/i.test(config.messaging.errorProtectionCode ?? '')) {
|
|
49
|
-
throw new Error(
|
|
50
|
-
'AdSense error protection must contain the script body only',
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
25
|
return config;
|
|
55
26
|
}
|
|
56
|
-
|
|
57
|
-
function isValidHostname(host: string): boolean {
|
|
58
|
-
if (!host || host !== host.trim() || host.includes('/')) return false;
|
|
59
|
-
if (
|
|
60
|
-
host.toLowerCase() === 'localhost' ||
|
|
61
|
-
host.toLowerCase().endsWith('.localhost') ||
|
|
62
|
-
host === '127.0.0.1' ||
|
|
63
|
-
host === '::1'
|
|
64
|
-
) {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
return new URL(`https://${host}`).hostname === host.toLowerCase();
|
|
70
|
-
} catch {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
}
|
package/src/ads/index.ts
CHANGED
package/src/components/index.ts
CHANGED
|
@@ -40,7 +40,6 @@ export { default as Tooltip } from './feedback/Tooltip/Tooltip.astro';
|
|
|
40
40
|
export { default as CanvasFrame } from './media/CanvasFrame/CanvasFrame.astro';
|
|
41
41
|
export { default as EmbedFrame } from './media/EmbedFrame/EmbedFrame.astro';
|
|
42
42
|
export { default as MediaFrame } from './media/MediaFrame/MediaFrame.astro';
|
|
43
|
-
export { default as AdSlot } from './monetization/AdSlot/AdSlot.astro';
|
|
44
43
|
export { default as Breadcrumbs } from './navigation/Breadcrumbs/Breadcrumbs.astro';
|
|
45
44
|
export { default as DesktopNavigation } from './navigation/DesktopNavigation.astro';
|
|
46
45
|
export { default as Header } from './navigation/Header.astro';
|
|
@@ -62,30 +62,32 @@ const resolvedTone = tone ?? dataTone;
|
|
|
62
62
|
<slot name="head" />
|
|
63
63
|
</head>
|
|
64
64
|
<body>
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
65
|
+
<div class="site-shell">
|
|
66
|
+
<slot name="skip-link" />
|
|
67
|
+
<slot name="header" />
|
|
68
|
+
<div class="site-viewport" data-site-viewport>
|
|
69
|
+
<main id="content" class="site-main" tabindex="-1">
|
|
70
|
+
<div class="site-main__content">
|
|
71
|
+
{
|
|
72
|
+
breadcrumbs?.length ? (
|
|
73
|
+
<Breadcrumbs
|
|
74
|
+
{...adAnnotationExclusionAttributes}
|
|
75
|
+
items={breadcrumbs}
|
|
76
|
+
label={breadcrumbLabel}
|
|
77
|
+
tone={breadcrumbTone}
|
|
78
|
+
/>
|
|
79
|
+
) : null
|
|
80
|
+
}
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
<PageSurface {...pageSurfaceAttributes}>
|
|
83
|
+
<slot />
|
|
84
|
+
<slot name="page-end" />
|
|
85
|
+
</PageSurface>
|
|
86
|
+
</div>
|
|
87
|
+
</main>
|
|
88
|
+
<slot name="footer" />
|
|
89
|
+
</div>
|
|
90
|
+
{scrollToTopLabel ? <ScrollToTop label={scrollToTopLabel} /> : null}
|
|
88
91
|
</div>
|
|
89
|
-
{scrollToTopLabel ? <ScrollToTop label={scrollToTopLabel} /> : null}
|
|
90
92
|
</body>
|
|
91
93
|
</html>
|
|
@@ -155,12 +155,12 @@
|
|
|
155
155
|
--map-viewport-cell-surface: color-mix(
|
|
156
156
|
in srgb,
|
|
157
157
|
var(--map-viewport-cell-seed) 38%,
|
|
158
|
-
var(--nox-
|
|
158
|
+
var(--nox-mix-light)
|
|
159
159
|
);
|
|
160
160
|
--map-viewport-cell-surface-hover: color-mix(
|
|
161
161
|
in srgb,
|
|
162
162
|
var(--map-viewport-cell-seed) 52%,
|
|
163
|
-
var(--nox-
|
|
163
|
+
var(--nox-mix-light)
|
|
164
164
|
);
|
|
165
165
|
--map-viewport-cell-background: color-mix(
|
|
166
166
|
in srgb,
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
--map-viewport-cell-border: color-mix(
|
|
176
176
|
in srgb,
|
|
177
177
|
var(--map-viewport-cell-seed) 74%,
|
|
178
|
-
var(--nox-
|
|
178
|
+
var(--nox-mix-dark)
|
|
179
179
|
);
|
|
180
180
|
|
|
181
181
|
fill: var(--map-viewport-cell-background);
|
package/src/styles/base.css
CHANGED
|
@@ -53,17 +53,23 @@ html {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
body {
|
|
56
|
+
margin: 0;
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
background: var(--nox-page-background);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.site-shell {
|
|
56
62
|
display: grid;
|
|
63
|
+
width: 100vw;
|
|
57
64
|
height: 100vh;
|
|
58
65
|
min-height: 100vh;
|
|
59
66
|
grid-template-rows: auto minmax(0, 1fr);
|
|
60
|
-
margin: 0;
|
|
61
67
|
overflow: hidden;
|
|
62
68
|
background: var(--nox-page-background);
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
@supports (height: 100dvh) {
|
|
66
|
-
|
|
72
|
+
.site-shell {
|
|
67
73
|
height: 100dvh;
|
|
68
74
|
min-height: 100dvh;
|
|
69
75
|
}
|
package/src/styles/style.css
CHANGED
|
@@ -49,7 +49,6 @@
|
|
|
49
49
|
@import '../components/media/CanvasFrame/CanvasFrame.css' layer(components);
|
|
50
50
|
@import '../components/media/EmbedFrame/EmbedFrame.css' layer(components);
|
|
51
51
|
@import '../components/media/MediaFrame/MediaFrame.css' layer(components);
|
|
52
|
-
@import '../components/monetization/AdSlot/AdSlot.css' layer(components);
|
|
53
52
|
@import '../components/navigation/Breadcrumbs/Breadcrumbs.css' layer(components);
|
|
54
53
|
@import '../components/navigation/Header.css' layer(components);
|
|
55
54
|
@import '../components/navigation/Footer/Footer.css' layer(components);
|
package/src/styles/tokens.css
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
--nox-content-link-font-weight: var(--nox-font-weight-regular);
|
|
15
15
|
--nox-content-link-emphasis-font-weight: var(--nox-font-weight-medium);
|
|
16
16
|
|
|
17
|
-
/* Public map
|
|
17
|
+
/* Public map palette. It stays stable across UI color themes. */
|
|
18
18
|
--nox-source-map-available: #00b86b;
|
|
19
19
|
--nox-source-map-unavailable: #f43f5e;
|
|
20
20
|
--nox-source-map-field: #f59e0b;
|
|
@@ -48,14 +48,6 @@
|
|
|
48
48
|
--nox-source-tone-warning: #f0ad55;
|
|
49
49
|
--nox-source-tone-danger: #f47777;
|
|
50
50
|
|
|
51
|
-
--nox-source-map-available: #42d392;
|
|
52
|
-
--nox-source-map-unavailable: #fb7185;
|
|
53
|
-
--nox-source-map-field: #fbbf24;
|
|
54
|
-
--nox-source-map-grass: #84cc16;
|
|
55
|
-
--nox-source-map-mountain: #94a3b8;
|
|
56
|
-
--nox-source-map-water: #38bdf8;
|
|
57
|
-
--nox-source-map-range: #60a5fa;
|
|
58
|
-
|
|
59
51
|
--nox-theme-page-base: #0f1218;
|
|
60
52
|
--nox-theme-surface-base: #171b23;
|
|
61
53
|
--nox-theme-text-base: #f8fafc;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import type { HTMLAttributes } from 'astro/types';
|
|
3
|
-
import AdSlot from '../../components/monetization/AdSlot/AdSlot.astro';
|
|
4
|
-
import { toAdsenseClientId } from './provider';
|
|
5
|
-
|
|
6
|
-
type Props = Omit<HTMLAttributes<'aside'>, 'aria-label'> & {
|
|
7
|
-
format: 'display' | 'multiplex';
|
|
8
|
-
label: string;
|
|
9
|
-
publisherId: string;
|
|
10
|
-
size?: 'compact' | 'standard' | 'tall';
|
|
11
|
-
slotId: string;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const {
|
|
15
|
-
format,
|
|
16
|
-
label,
|
|
17
|
-
publisherId,
|
|
18
|
-
size = 'standard',
|
|
19
|
-
slotId,
|
|
20
|
-
...attrs
|
|
21
|
-
}: Props = Astro.props;
|
|
22
|
-
const unitAttributes =
|
|
23
|
-
format === 'multiplex'
|
|
24
|
-
? { 'data-ad-format': 'autorelaxed' }
|
|
25
|
-
: {
|
|
26
|
-
'data-ad-format': 'auto',
|
|
27
|
-
'data-full-width-responsive': 'true',
|
|
28
|
-
};
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
<AdSlot
|
|
32
|
-
{...attrs}
|
|
33
|
-
data-adsense-format={format}
|
|
34
|
-
data-adsense-unit
|
|
35
|
-
label={label}
|
|
36
|
-
size={size}
|
|
37
|
-
state="loading"
|
|
38
|
-
>
|
|
39
|
-
<ins
|
|
40
|
-
{...unitAttributes}
|
|
41
|
-
class="adsbygoogle"
|
|
42
|
-
style="display:block"
|
|
43
|
-
data-ad-client={toAdsenseClientId(publisherId)}
|
|
44
|
-
data-ad-slot={slotId}></ins>
|
|
45
|
-
</AdSlot>
|
package/src/ads/slot.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { AdSlotState } from './contracts';
|
|
2
|
-
|
|
3
|
-
export function setAdSlotState(slot: HTMLElement, state: AdSlotState): void {
|
|
4
|
-
slot.dataset.adState = state;
|
|
5
|
-
slot.setAttribute('aria-busy', state === 'loading' ? 'true' : 'false');
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function getAdSlotState(slot: HTMLElement): AdSlotState {
|
|
9
|
-
const state = slot.dataset.adState;
|
|
10
|
-
return state === 'loading' ||
|
|
11
|
-
state === 'filled' ||
|
|
12
|
-
state === 'blocked' ||
|
|
13
|
-
state === 'unavailable'
|
|
14
|
-
? state
|
|
15
|
-
: 'idle';
|
|
16
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import type { HTMLAttributes } from 'astro/types';
|
|
3
|
-
import type { AdSlotState } from '../../../ads/contracts';
|
|
4
|
-
|
|
5
|
-
type Props = Omit<HTMLAttributes<'aside'>, 'aria-label'> & {
|
|
6
|
-
label: string;
|
|
7
|
-
size?: 'compact' | 'standard' | 'tall';
|
|
8
|
-
state?: AdSlotState;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const {
|
|
12
|
-
class: className,
|
|
13
|
-
label,
|
|
14
|
-
size = 'standard',
|
|
15
|
-
state = 'idle',
|
|
16
|
-
...attrs
|
|
17
|
-
}: Props = Astro.props;
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
<aside
|
|
21
|
-
{...attrs}
|
|
22
|
-
class:list={['ad-slot', className]}
|
|
23
|
-
data-ad-size={size}
|
|
24
|
-
data-ad-state={state}
|
|
25
|
-
data-widget="ad-slot"
|
|
26
|
-
aria-label={label}
|
|
27
|
-
aria-busy={state === 'loading' ? 'true' : 'false'}
|
|
28
|
-
>
|
|
29
|
-
<div class="ad-slot__content"><slot /></div>
|
|
30
|
-
</aside>
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
.ad-slot {
|
|
2
|
-
--ad-slot-min-block-size: 6rem;
|
|
3
|
-
|
|
4
|
-
display: grid;
|
|
5
|
-
inline-size: 100%;
|
|
6
|
-
min-block-size: var(--ad-slot-min-block-size);
|
|
7
|
-
overflow: hidden;
|
|
8
|
-
place-items: center;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.ad-slot[data-ad-size='compact'] {
|
|
12
|
-
--ad-slot-min-block-size: 4rem;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.ad-slot[data-ad-size='tall'] {
|
|
16
|
-
--ad-slot-min-block-size: 15rem;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.ad-slot__content {
|
|
20
|
-
inline-size: 100%;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.ad-slot[data-ad-state='filled'] {
|
|
24
|
-
min-block-size: 0;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.ad-slot[data-ad-state='blocked'],
|
|
28
|
-
.ad-slot[data-ad-state='unavailable'] {
|
|
29
|
-
display: none;
|
|
30
|
-
}
|