Add GDPR compliant cookie banner and update footer/privacy policy to include GA and Clarity; added e2e and unit tests for cookie handling; updated snapshots
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful

This commit is contained in:
2026-03-12 15:04:49 -03:00
parent 4ad45d5625
commit a5989b03b1
28 changed files with 607 additions and 9 deletions

View File

@@ -32,6 +32,7 @@
--font-family-heading: 'Fraunces', ui-serif, Georgia, 'Times New Roman', serif;
--font-size-base: 18px;
--font-size-xs: 14px;
--font-size-small: 15px;
--font-size-medium: 16px;
--font-size-large: 20px;
@@ -382,6 +383,14 @@ a {
&:active {
transform: translateY(0);
}
&.small {
border-radius: var(--border-radius-small);
font-size: var(--font-size-xs);
min-height: 36px;
min-width: fit-content;
padding: 0.5rem 1rem;
}
}
.icon-button {

View File

@@ -53,6 +53,13 @@
Terms of Service
</a>
</nav>
<p class="legal-notice">
We improve our products and advertising by using Google Analytics and
Microsoft Clarity to see how you use our website. By using our site, you agree
that we and Microsoft can collect and use this data. Our <a
href="/privacy-policy">privacy policy</a
> has more details.
</p>
</div>
</footer>
@@ -82,4 +89,22 @@
justify-content: center;
gap: var(--space-xs);
}
.legal-notice {
margin-top: var(--space-md);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-normal);
color: var(--color-text-tertiary);
max-width: 100%;
}
.legal-notice a {
color: var(--color-primary);
text-decoration: underline;
text-underline-offset: 0.2em;
}
.legal-notice a:hover {
color: var(--color-primary-hover);
}
</style>

View File

@@ -32,7 +32,10 @@
<span
class={['mobile nav-header-logo', { 'page-home': bodyClass === 'page-home' }]}
>
<Wordmark />
<a href="/" class="logo-link">
<Wordmark />
<span class="sr-only">mifi Ventures home page</span>
</a>
</span>
<button
type="button"
@@ -64,7 +67,7 @@
]}
>
{#if page !== 'home'}
<a href="/">
<a href="/" class="logo-link">
<Wordmark />
<span class="sr-only">mifi Ventures home page</span>
</a>
@@ -155,6 +158,15 @@
display: inline-block;
}
}
& .logo-link {
text-decoration: none;
&:hover {
text-decoration: none;
border-bottom: none;
}
}
}
/* Hamburger toggle: mobile only, animates to X when open */

View File

@@ -1,20 +1,27 @@
/**
* Privacy Policy content for mifi Ventures. Used by /privacy-policy.
* Last updated: March 5, 2026. Includes Messaging Policy (SMS) for OpenPhone / A2P compliance.
* Last updated: March 12, 2026. Includes Messaging Policy (SMS) for OpenPhone / A2P compliance; Microsoft Clarity and Google Analytics.
*/
export interface LegalSectionLink {
href: string;
label: string;
}
export interface LegalSection {
id: string;
heading: string;
body: string[];
list?: string[];
/** Optional links to inject into body paragraphs (URLs in text become <a> with this label) */
links?: LegalSectionLink[];
/** Numbered sub-sections (e.g. Messaging Policy 16) */
subsections?: { title: string; body: string[]; list?: string[] }[];
}
export const privacyPolicy = {
title: 'Privacy Policy',
lastUpdated: 'March 5, 2026',
lastUpdated: 'March 12, 2026',
intro: [
'mifi Ventures LLC respects your privacy and is committed to protecting personal information shared through this website and related communications.',
'This policy explains what information we collect, how it is used, and how it is protected.',
@@ -56,6 +63,31 @@ export const privacyPolicy = {
'This information is used only to maintain the website, improve performance, and monitor security.',
],
},
{
id: 'analytics-and-tracking',
heading: 'Analytics and Tracking Technologies',
body: [
'We use third-party analytics and advertising tools to understand how visitors use this website and to improve our services.',
'We partner with Microsoft Clarity and Microsoft Advertising to capture how you use and interact with our website through behavioral metrics, heatmaps, and session replay to improve and market our products and services. Website usage data is captured using first- and third-party cookies and similar tracking technologies to determine the popularity of content and online activity. We also use this information for site optimization, security and fraud detection, and advertising.',
'For more information about how Microsoft collects and uses your data, see the Microsoft Privacy Statement: https://www.microsoft.com/privacy/privacystatement.',
'We also use Google Analytics to collect information about website usage, such as pages visited, time on site, and browser and device information. Google Analytics uses cookies and similar technologies to help us analyze how visitors use the site and to compile aggregated statistics.',
'You can learn more about how Google handles data in Google Analytics at: https://policies.google.com/privacy and https://policies.google.com/technologies/partner-sites.',
],
links: [
{
href: 'https://www.microsoft.com/privacy/privacystatement',
label: 'Microsoft Privacy Statement',
},
{
href: 'https://policies.google.com/privacy',
label: 'Google Privacy Policy',
},
{
href: 'https://policies.google.com/technologies/partner-sites',
label: 'How Google uses data from sites and apps',
},
],
},
{
id: 'how-we-use',
heading: 'How We Use Information',

View File

@@ -29,17 +29,12 @@
</script>
<svelte:head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-36F29PDKRT"></script>
<script defer src="/assets/js/ga-init.js"></script>
<script
defer
src="https://analytics.mifi.holdings/script.js"
data-website-id="72ac01ce-e7fc-4582-8593-703f15add8d5"
></script>
<script defer src="/assets/js/umami-helper.js"></script>
<!-- Microsoft Clarity -->
<script defer src="https://www.clarity.ms/tag/vuo5q3yf79?ref=bwt"></script>
<title>{merged.title}</title>
<meta name="description" content={merged.description ?? ''} />
@@ -151,6 +146,7 @@
<script src="/assets/js/copyright-year.js" defer></script>
<script src="/assets/js/mobile-menu-helper.js" defer></script>
<script src="/assets/js/cookie-consent.js" defer></script>
</svelte:head>
<a href="#main" class="skip-link" data-umami-event="skip to main content"
@@ -158,6 +154,24 @@
>
{@render children()}
<Footer />
<div id="cookie-banner" class="cookie-banner" role="region" aria-label="Cookie consent">
<div class="cookie-banner-content">
<p class="cookie-notification-text">
We use first-party analytics and, if you accept, third-party tools (e.g.
Google, Microsoft) to understand usage and improve this site. You can accept
all or reject non-essential analytics.
<a href="/privacy-policy#analytics-and-tracking">Learn more</a>.
</p>
<div class="cookie-banner-actions">
<button type="button" class="btn btn-primary small" data-consent="accept"
>Accept all</button
>
<button type="button" class="btn btn-secondary small" data-consent="reject"
>Reject non-essential</button
>
</div>
</div>
</div>
<img
src="https://analytics.mifi.holdings/p/wQ9GYnLIg"
alt=""
@@ -166,3 +180,105 @@
role="presentation"
loading="eager"
/>
<style>
.cookie-banner {
position: fixed;
inset-inline: 0;
bottom: 0;
z-index: 1000;
display: none;
background-color: var(--color-bg-elevated, var(--color-bg));
color: var(--color-text);
border-top: 1px solid var(--color-border);
box-shadow: 0 -8px 24px rgba(15, 23, 42, 0.16);
&:global(.is-visible) {
display: block;
}
}
.cookie-banner-content {
container: cookie-banner / inline-size;
max-width: var(--max-narrow-width);
margin: 0 auto;
padding: var(--space-md) var(--space-md);
display: flex;
flex-direction: column;
gap: var(--space-sm);
@media (min-width: 768px) {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
}
.cookie-notification-text {
margin: 0;
font-size: var(--font-size-small);
line-height: var(--line-height-base);
@container cookie-banner (width >= 644px) {
flex: 1 1 auto;
}
@container cookie-banner (width < 644px) {
flex: 0 0 100%;
text-align: center;
width: 100%;
}
& a {
color: var(--color-primary);
text-decoration: underline;
text-underline-offset: 0.16em;
&:hover {
color: var(--color-primary-hover);
}
}
}
.cookie-banner-actions {
display: flex;
flex-flow: column nowrap;
justify-content: flex-start;
@container cookie-banner (width >= 644px) {
flex: 0 0 auto;
gap: var(--space-xs);
}
@container cookie-banner (width < 644px) {
flex: 0 0 100%;
gap: var(--space-sm);
justify-content: center;
width: 100%;
& .btn {
max-width: 100%;
width: 100%;
}
}
& [data-consent='accept'] {
background-color: var(--color-primary);
color: var(--color-bg);
&:hover {
background-color: var(--color-primary-hover);
}
}
& [data-consent='reject'] {
background-color: transparent;
color: var(--color-text);
border-color: var(--color-primary);
&:hover {
background-color: var(--color-surface-subtle, rgba(148, 163, 184, 0.16));
}
}
}
</style>

View File

@@ -1,8 +1,30 @@
<script lang="ts">
import Navigation from '$lib/components/Navigation.svelte';
import { privacyPolicy } from '$lib/data/privacy-policy';
import type { LegalSectionLink } from '$lib/data/privacy-policy';
const navItems = [{ label: 'Home', href: '/', umamiEventLabel: 'home' }];
type BodySegment = string | { type: 'link'; href: string; label: string };
/** Splits a paragraph by link URLs and returns text/link segments for rendering. */
function linkify(para: string, links: LegalSectionLink[]): BodySegment[] {
const matches: { index: number; link: LegalSectionLink }[] = [];
for (const link of links) {
const idx = para.indexOf(link.href);
if (idx !== -1) matches.push({ index: idx, link });
}
matches.sort((a, b) => a.index - b.index);
const result: BodySegment[] = [];
let last = 0;
for (const { index, link } of matches) {
if (index > last) result.push(para.slice(last, index));
result.push({ type: 'link', href: link.href, label: link.label });
last = index + link.href.length;
}
if (last < para.length) result.push(para.slice(last));
return result.length ? result : [para];
}
</script>
<Navigation items={navItems} page="privacy-policy" />
@@ -36,6 +58,16 @@
<a href="https://mifi.ventures" rel="noopener noreferrer"
>https://mifi.ventures</a
>
{:else if section.links}
{#each linkify(para, section.links) as segment}
{#if typeof segment === 'string'}
{segment}
{:else}
<a href={segment.href} rel="noopener noreferrer"
>{segment.label}</a
>
{/if}
{/each}
{:else}
{para}
{/if}
@@ -116,6 +148,16 @@
margin: 0 0 var(--space-md) 0;
}
.legal-content a {
color: var(--color-primary);
text-decoration: underline;
text-underline-offset: 0.2em;
}
.legal-content a:hover {
color: var(--color-primary-hover);
}
.legal-section {
margin-bottom: var(--space-xl);
}