Initial commit of Descrybe v2 without local scratch artifacts.

Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
2026-08-09 22:47:43 +02:00
commit 8580c996c3
1285 changed files with 325780 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
/**
* Pure Consent Mode v2 helpers (no $app / $env) — safe for node:test.
*
* Categories:
* - Necessary: always on (session, CSRF, theme, locale, consent preference)
* - Analytics → analytics_storage
* - Marketing → ad_storage, ad_user_data, ad_personalization
*/
export const CONSENT_STORAGE_KEY = "descrybe-cookie-consent";
export const CONSENT_VERSION = 1;
export const CONSENT_WAIT_FOR_UPDATE_MS = 500;
export type ConsentPreferences = {
analytics: boolean;
marketing: boolean;
};
export type StoredConsent = ConsentPreferences & {
v: number;
updatedAt: string;
};
/** Google Consent Mode v2 signal map (string literals for gtag). */
export type ConsentModeSignals = {
ad_storage: "granted" | "denied";
ad_user_data: "granted" | "denied";
ad_personalization: "granted" | "denied";
analytics_storage: "granted" | "denied";
functionality_storage: "granted" | "denied";
personalization_storage: "granted" | "denied";
security_storage: "granted" | "denied";
};
export const DENIED_CONSENT_DEFAULTS: ConsentModeSignals = {
ad_storage: "denied",
ad_user_data: "denied",
ad_personalization: "denied",
analytics_storage: "denied",
functionality_storage: "granted",
personalization_storage: "denied",
security_storage: "granted"
};
export function preferencesToConsentSignals(
prefs: ConsentPreferences
): ConsentModeSignals {
const analytics = prefs.analytics ? "granted" : "denied";
const marketing = prefs.marketing ? "granted" : "denied";
return {
ad_storage: marketing,
ad_user_data: marketing,
ad_personalization: marketing,
analytics_storage: analytics,
functionality_storage: "granted",
personalization_storage: analytics,
security_storage: "granted"
};
}
export function parseStoredConsent(raw: string | null | undefined): StoredConsent | null {
if (!raw?.trim()) return null;
try {
const parsed: unknown = JSON.parse(raw);
if (!parsed || typeof parsed !== "object") return null;
const obj = parsed as Record<string, unknown>;
if (obj.v !== CONSENT_VERSION) return null;
if (typeof obj.analytics !== "boolean" || typeof obj.marketing !== "boolean") {
return null;
}
const updatedAt =
typeof obj.updatedAt === "string" && obj.updatedAt.trim()
? obj.updatedAt
: new Date(0).toISOString();
return {
v: CONSENT_VERSION,
analytics: obj.analytics,
marketing: obj.marketing,
updatedAt
};
} catch {
return null;
}
}
export function serializeConsent(prefs: ConsentPreferences, updatedAt = new Date().toISOString()): string {
const stored: StoredConsent = {
v: CONSENT_VERSION,
analytics: prefs.analytics,
marketing: prefs.marketing,
updatedAt
};
return JSON.stringify(stored);
}
/** Accept all non-necessary categories. */
export function acceptAllPreferences(): ConsentPreferences {
return { analytics: true, marketing: true };
}
/** Reject analytics and marketing (necessary remains on). */
export function rejectNonEssentialPreferences(): ConsentPreferences {
return { analytics: false, marketing: false };
}
/** Coarse SKU-volume bucket for lead events — never send the raw count as PII-adjacent precision. */
export function estimatedSkusBucket(n: number | null | undefined): string {
if (n == null || !Number.isFinite(n) || n < 0) return "unknown";
if (n < 1_000) return "0_999";
if (n < 5_000) return "1000_4999";
if (n < 20_000) return "5000_19999";
if (n < 100_000) return "20000_99999";
return "100000_plus";
}