CodeIssuesPull RequestsActionsSecurityInsights
✨ AI
More
Settings

feat(web,legal): aggressive legal-compliance release for market launch #4104

Merged⚡ AI-generatedXSccantynz wants to mergeclaude/release-legal-compliance-SevBMmainopened Apr 16, 20263/7 tasks
35 changed files+2382−173
ModifiedCLAUDE.md+11−3View fileUnifiedSplit
553553| 12 | Full rebrand from Vienna/48co/@emailed to AlecRae/alecrae.com/@alecrae | HIGH | 2026-04-12 | DONE 2026-04-12 — all files updated |
554554| 13 | No error boundaries in web app (error.tsx / not-found.tsx) | MEDIUM | 2026-04-12 | FIXED 2026-04-12 — root + dashboard error boundaries + 404 page |
555555| 14 | No sitemap.xml or robots.txt for SEO | LOW | 2026-04-12 | FIXED 2026-04-12 — Next.js route-based sitemap.ts + robots.ts |
556| 15 | No cookie/consent banner — GDPR/ePrivacy/CCPA exposure | CRITICAL | 2026-04-16 | FIXED 2026-04-16 — typed consent lib + banner, GPC/DNT auto-respect, 11 tests |
557| 16 | No /.well-known/security.txt (RFC 9116) or responsible-disclosure policy | HIGH | 2026-04-16 | FIXED 2026-04-16 — security.txt + SECURITY.md + /security page with scope + safe harbour |
558| 17 | Register page had no affirmative age gate (GDPR Art 7, COPPA, UK Children's Code) | HIGH | 2026-04-16 | FIXED 2026-04-16 — ConsentGatedRegistration (13+/16+EEA, terms acceptance, optional marketing) |
559| 18 | No CCPA/CPRA "Do Not Sell or Share" page or GPC signal endpoint | HIGH | 2026-04-16 | FIXED 2026-04-16 — /do-not-sell, /california-notice, /.well-known/gpc.json |
560| 19 | No EU AI Act transparency disclosure (Art 52, model inventory) | HIGH | 2026-04-16 | FIXED 2026-04-16 — /ai-transparency with per-model inventory + Art 22 rights |
561| 20 | No WCAG / EAA / ADA accessibility statement | MEDIUM | 2026-04-16 | FIXED 2026-04-16 — /accessibility page (WCAG 2.2 AA target, EAA, ADA, Section 508) |
562| 21 | Missing Impressum, Children's Privacy, Refund, Corporate Compliance pages | HIGH | 2026-04-16 | FIXED 2026-04-16 — 4 new pages (TMG §5, COPPA, EU 14-day withdrawal, FCPA/Modern Slavery/OFAC) |
563| 22 | Domain inconsistency — 32x `alecrae.dev` emails in legal copy should be `alecrae.com` | MEDIUM | 2026-04-16 | FIXED 2026-04-16 — normalised across terms/privacy/aup/dmca/dpa/sla/cookies/subprocessors/domains |
556564
557565---
558566
650658
651659## 📅 STATUS
652660
653**Date last updated:** 2026-04-12
661**Date last updated:** 2026-04-16
654662**Current phase:** Phase 1 — Ready for Beta Launch
655**Current focus:** Rebrand complete (Vienna/48co → AlecRae/alecrae.com). Production deployment awaiting Craig's infra setup.
656**Build completion:** TIER 1-4 ALL DONE (36/36) + 7 bonus + 31 advanced features (S10/10 + A7/7 + B8/8 + C6/10)
663**Current focus:** Aggressive legal-compliance release shipped (9 new legal pages, typed consent lib + banner honoring GPC/DNT, affirmative age gate on register, RFC 9116 security.txt + SECURITY.md, EU AI Act transparency, CCPA "Do Not Sell", Impressum/Corporate Compliance/Children/Refund, domain normalization). Production deployment awaiting Craig's infra setup.
664**Build completion:** TIER 1-4 ALL DONE (36/36) + 7 bonus + 31 advanced features (S10/10 + A7/7 + B8/8 + C6/10) + aggressive legal-compliance surface
657665
658666**Next review:** Before any major architectural change, before any production deployment, at the start of every session.
659667
AddedSECURITY.md+110−0View fileUnifiedSplit
1# Security Policy
2
3AlecRae is built to protect the most sensitive thing a person owns — their
4email. We take security and privacy seriously, and we welcome responsible
5disclosure from the security research community.
6
7## Reporting a vulnerability
8
9**Preferred channel:** email `security@alecrae.com` with a detailed
10description, proof of concept if available, and your contact information.
11
12**Alternative channels:**
13- Web form: https://alecrae.com/security
14- Encrypted: see `https://alecrae.com/.well-known/security.txt` for the
15 current PGP key / transport guidance.
16
17We will acknowledge your report within **2 business days** and provide a
18status update within **5 business days**. For critical issues we aim to
19ship a fix within **30 days**; lower severity fixes are triaged on our
20public security roadmap.
21
22## Scope
23
24**In scope:**
25- `*.alecrae.com` (all production and public staging subdomains)
26- The AlecRae desktop, web and mobile applications
27- The AlecRae public REST / tRPC API
28- Email authentication surfaces (SPF, DKIM, DMARC, ARC, BIMI)
29- Supporting open-source code in this repository
30
31**Out of scope:**
32- Denial-of-service, volumetric, or resource-exhaustion attacks
33- Social engineering of employees, contractors or users
34- Physical security attacks
35- Issues that require physical access to a victim's device
36- Issues that require a privileged network position (MITM on the
37 victim's local network, compromised CA, etc.)
38- Third-party services we do not operate (Stripe, Cloudflare, Neon,
39 etc.) — please report those directly to the vendor
40- Missing best-practice HTTP headers that do not lead to a concrete
41 exploit
42- Rate-limiting on authenticated endpoints protected by our 6-tier
43 limiter unless bypass is demonstrated
44- Vulnerabilities in third-party libraries without a demonstrated
45 exploit against AlecRae
46
47## Safe harbor
48
49Security research conducted in accordance with this policy is
50authorised. AlecRae will not pursue civil or criminal action under the
51Computer Fraud and Abuse Act (CFAA), the Digital Millennium Copyright
52Act (DMCA), equivalent state laws, or similar laws in other
53jurisdictions, provided you:
54
551. Make a good-faith effort to avoid privacy violations, degradation of
56 user experience, disruption to production systems, and destruction
57 or manipulation of data.
582. Only interact with accounts that you own, or with the explicit
59 permission of the account holder.
603. Do not exfiltrate any data beyond the minimum necessary to
61 demonstrate the vulnerability.
624. Give AlecRae a reasonable time to remediate before publicly
63 disclosing. We target 90 days from acknowledgment; we will work
64 with you on coordinated disclosure if more time is required.
655. Do not engage in extortion, threats, or any other conduct that
66 would cause material harm to AlecRae or its users.
67
68## Rewards
69
70AlecRae runs a bug bounty programme for qualifying reports. Rewards
71scale with severity (CVSS 3.1) and blast radius. See
72`https://alecrae.com/security` for the current reward table,
73Hall of Fame, and canonical rules.
74
75## What to include in a report
76
77To help us triage and fix faster, please include:
78
79- A clear description of the issue and the impact on users
80- Steps to reproduce, including URLs and payloads
81- A proof-of-concept (script, video, or screenshots)
82- Your suggested severity (CVSS vector if possible)
83- Any CVE, advisory or public discussion already linked to the issue
84- Your preferred name / handle for acknowledgment (or "anonymous")
85
86## What we will NOT do
87
88- We will never sue a researcher acting in good faith under this
89 policy.
90- We will never share your identity publicly without your consent.
91- We will never share a report with third parties except our counsel,
92 insurer, or law enforcement where legally required.
93- We will never silently ship a security fix without crediting the
94 reporter (unless requested).
95
96## Incident response
97
98In the event of a security incident affecting user data we commit to:
99
100- Notifying affected users within 72 hours of confirmed breach, as
101 required by GDPR Article 33.
102- Publishing a post-mortem at `docs/postmortems/` within 14 days.
103- Rotating any credentials even tangentially related to the incident.
104- Funding independent review when appropriate.
105
106See `CLAUDE.md` -> "Emergency Protocols" for our internal playbook.
107
108---
109
110Last updated: 2026-04-16.
Modifiedapps/web/app/(auth)/register/page.tsx+99−17View fileUnifiedSplit
2525
2626 <Card>
2727 <CardContent>
28 <Box className="space-y-6">
29 <PasskeyRegistration />
30 <RegistrationDivider />
31 <EmailRegistration />
32 </Box>
28 <ConsentGatedRegistration />
3329 </CardContent>
3430 </Card>
3531
4339 </Text>
4440 </Box>
4541 </Box>
42 </Box>
43 </Box>
44 );
45}
4646
47 <Box className="text-center mt-4">
48 <Text variant="caption" muted>
49 By creating an account, you agree to our{" "}
50 <Box as="a" href="/terms" className="text-brand-600 hover:underline inline">
51 <Text as="span" variant="caption">Terms of Service</Text>
52 </Box>
53 {" "}and{" "}
54 <Box as="a" href="/privacy" className="text-brand-600 hover:underline inline">
55 <Text as="span" variant="caption">Privacy Policy</Text>
56 </Box>
57 .
58 </Text>
47/**
48 * Legal gate shown before the registration form itself. Requires the user
49 * to affirm (i) minimum age, (ii) acceptance of the Terms and Privacy
50 * Policy, before any credential can be created. This is the affirmative
51 * opt-in required by GDPR Article 7 and by the UK ICO Age-Appropriate
52 * Design Code.
53 */
54function ConsentGatedRegistration(): React.ReactElement {
55 const [ageConfirmed, setAgeConfirmed] = useState(false);
56 const [termsAccepted, setTermsAccepted] = useState(false);
57 const [marketingOptIn, setMarketingOptIn] = useState(false);
58
59 const canProceed = ageConfirmed && termsAccepted;
60
61 return (
62 <Box className="space-y-6">
63 <Box className="space-y-3 rounded-lg border border-border bg-surface-subtle p-4">
64 <Text variant="label">Before you create your account</Text>
65 <Box as="label" className="flex items-start gap-3 cursor-pointer">
66 <input
67 type="checkbox"
68 className="mt-1 h-4 w-4 rounded border-neutral-400 text-brand-600 focus:ring-brand-600"
69 checked={ageConfirmed}
70 onChange={(e) => setAgeConfirmed(e.target.checked)}
71 aria-describedby="age-help"
72 />
73 <Box className="flex-1">
74 <Text variant="body-sm" className="text-content">
75 I confirm I am at least 13 years old (or 16 if I live in the
76 European Economic Area or the United Kingdom), and if I am
77 under 18 I have my parent or legal guardian&apos;s consent to
78 use AlecRae.
79 </Text>
80 </Box>
81 </Box>
82 <Box as="label" className="flex items-start gap-3 cursor-pointer">
83 <input
84 type="checkbox"
85 className="mt-1 h-4 w-4 rounded border-neutral-400 text-brand-600 focus:ring-brand-600"
86 checked={termsAccepted}
87 onChange={(e) => setTermsAccepted(e.target.checked)}
88 aria-describedby="terms-help"
89 />
90 <Box className="flex-1">
91 <Text variant="body-sm" className="text-content">
92 I have read and agree to the{" "}
93 <Box as="a" href="/terms" className="text-brand-600 hover:underline inline" target="_blank" rel="noopener noreferrer">
94 <Text as="span" variant="body-sm" className="text-brand-600">Terms of Service</Text>
95 </Box>
96 ,{" "}
97 <Box as="a" href="/privacy" className="text-brand-600 hover:underline inline" target="_blank" rel="noopener noreferrer">
98 <Text as="span" variant="body-sm" className="text-brand-600">Privacy Policy</Text>
99 </Box>
100 ,{" "}
101 <Box as="a" href="/acceptable-use" className="text-brand-600 hover:underline inline" target="_blank" rel="noopener noreferrer">
102 <Text as="span" variant="body-sm" className="text-brand-600">Acceptable Use Policy</Text>
103 </Box>
104 {" "}and{" "}
105 <Box as="a" href="/cookies" className="text-brand-600 hover:underline inline" target="_blank" rel="noopener noreferrer">
106 <Text as="span" variant="body-sm" className="text-brand-600">Cookie Policy</Text>
107 </Box>
108 .
109 </Text>
110 </Box>
111 </Box>
112 <Box as="label" className="flex items-start gap-3 cursor-pointer">
113 <input
114 type="checkbox"
115 className="mt-1 h-4 w-4 rounded border-neutral-400 text-brand-600 focus:ring-brand-600"
116 checked={marketingOptIn}
117 onChange={(e) => setMarketingOptIn(e.target.checked)}
118 />
119 <Box className="flex-1">
120 <Text variant="body-sm" className="text-content">
121 Optional: send me occasional product updates. Unsubscribe any
122 time. We never share your email address with anyone.
123 </Text>
124 </Box>
59125 </Box>
60126 </Box>
127
128 <Box className={canProceed ? "" : "opacity-50 pointer-events-none"} aria-disabled={!canProceed}>
129 <Box className="space-y-6">
130 <PasskeyRegistration />
131 <RegistrationDivider />
132 <EmailRegistration />
133 </Box>
134 </Box>
135
136 {!canProceed ? (
137 <Text variant="caption" muted className="text-center">
138 Tick the required boxes above to continue.
139 </Text>
140 ) : null}
61141 </Box>
62142 );
63143}
64144
145ConsentGatedRegistration.displayName = "ConsentGatedRegistration";
146
65147function PasskeyRegistration(): React.ReactElement {
66148 const [step, setStep] = useState<"initial" | "details">("initial");
67149 const [name, setName] = useState("");
Modifiedapps/web/app/(dashboard)/domains/page.tsx+3−3View fileUnifiedSplit
3030 verificationState: (d.verificationStatus === "verifying" ? "pending" : d.verificationStatus) as "pending" | "verified" | "failed",
3131 dnsRecords: [
3232 { type: "TXT", name: `_alecrae-verify.${d.domain}`, value: `alecrae-verify=${d.id.slice(0, 8)}`, verified: d.spfVerified },
33 { type: "TXT", name: d.domain, value: `v=spf1 include:spf.alecrae.dev ~all`, verified: d.spfVerified },
34 { type: "CNAME", name: `em._domainkey.${d.domain}`, value: "dkim.alecrae.dev", verified: d.dkimVerified },
35 { type: "TXT", name: `_dmarc.${d.domain}`, value: `v=DMARC1; p=reject; rua=mailto:dmarc@alecrae.dev`, verified: d.dmarcVerified },
33 { type: "TXT", name: d.domain, value: `v=spf1 include:spf.alecrae.com ~all`, verified: d.spfVerified },
34 { type: "CNAME", name: `em._domainkey.${d.domain}`, value: "dkim.alecrae.com", verified: d.dkimVerified },
35 { type: "TXT", name: `_dmarc.${d.domain}`, value: `v=DMARC1; p=reject; rua=mailto:dmarc@alecrae.com`, verified: d.dmarcVerified },
3636 ],
3737 spfVerified: d.spfVerified,
3838 dkimVerified: d.dkimVerified,
Modifiedapps/web/app/(legal)/acceptable-use/page.tsx+4−4View fileUnifiedSplit
141141 <Section number="8" title="Network Security">
142142 <Text>You must not use the Service to conduct or facilitate any of the following:</Text>
143143 <Sub label="(a)">Port scanning, network enumeration, or network reconnaissance of AlecRae infrastructure or any third-party systems.</Sub>
144 <Sub label="(b)">Vulnerability testing, penetration testing, or security testing of the Service without prior written authorization from AlecRae. Report vulnerabilities responsibly to security@alecrae.dev.</Sub>
144 <Sub label="(b)">Vulnerability testing, penetration testing, or security testing of the Service without prior written authorization from AlecRae. Report vulnerabilities responsibly to security@alecrae.com.</Sub>
145145 <Sub label="(c)">Distributed denial of service (DDoS) attacks, traffic flooding, or any activity intended to disrupt the availability of the Service or any third-party service.</Sub>
146146 <Sub label="(d)">IP address spoofing, ARP spoofing, or DNS spoofing.</Sub>
147147 <Sub label="(e)">Unauthorized interception, monitoring, or collection of network traffic.</Sub>
148 <Sub label="(f)">Exploitation of any vulnerability in the Service. If you discover a vulnerability, you must report it immediately to security@alecrae.dev and must not exploit or disclose it.</Sub>
148 <Sub label="(f)">Exploitation of any vulnerability in the Service. If you discover a vulnerability, you must report it immediately to security@alecrae.com and must not exploit or disclose it.</Sub>
149149 </Section>
150150
151151 <Section number="9" title="AI System Integrity">
213213 <Section number="12" title="Reporting Abuse">
214214 <Text>To report violations of this AUP or any abuse originating from the AlecRae platform:</Text>
215215 <Box className="ml-6 mt-2 space-y-1">
216 <Text>Email: abuse@alecrae.dev</Text>
216 <Text>Email: abuse@alecrae.com</Text>
217217 <Text>Include: full email headers, message content, timestamps, and any other relevant evidence.</Text>
218218 </Box>
219219 <Text className="mt-3">AlecRae will investigate all reports within 24 hours of receipt. Reporter identity is kept confidential. AlecRae participates in feedback loops (FBLs) with all major ISPs and processes complaints automatically.</Text>
246246
247247 <Box className="mt-12 pt-6 border-t border-border">
248248 <Text className="text-content-tertiary text-sm">
249 Questions about this Acceptable Use Policy should be directed to abuse@alecrae.dev or legal@alecrae.dev. This policy is reviewed and updated regularly to address evolving threats and regulatory requirements.
249 Questions about this Acceptable Use Policy should be directed to abuse@alecrae.com or legal@alecrae.com. This policy is reviewed and updated regularly to address evolving threats and regulatory requirements.
250250 </Text>
251251 </Box>
252252 </Box>
Addedapps/web/app/(legal)/accessibility/page.tsx+158−0View fileUnifiedSplit
1import type { Metadata } from "next";
2import { Box, Text, Card, CardContent } from "@alecrae/ui";
3
4export const metadata: Metadata = {
5 title: "Accessibility Statement | AlecRae",
6 description:
7 "AlecRae's accessibility commitment, conformance target (WCAG 2.2 AA), supported assistive technologies, and how to report accessibility issues.",
8};
9
10export default function AccessibilityPage() {
11 return (
12 <Box className="space-y-10">
13 <Box>
14 <Text as="h1" className="text-3xl font-bold text-content mb-2">
15 Accessibility Statement
16 </Text>
17 <Text className="text-content-tertiary">
18 Effective Date: April 16, 2026 &middot; Last Updated: April 16, 2026
19 </Text>
20 </Box>
21
22 <Text className="text-content-secondary leading-relaxed">
23 AlecRae is committed to making email universally usable. Email is a
24 human right — it cannot be locked behind a design that excludes
25 anyone. This statement describes the conformance targets we have
26 chosen, the standards we test against, and how we handle
27 accessibility feedback.
28 </Text>
29
30 <Box className="space-y-4">
31 <Text as="h2" className="text-xl font-bold text-content">
32 1. Conformance target
33 </Text>
34 <Text className="text-content-secondary leading-relaxed">
35 AlecRae targets <strong>WCAG 2.2 Level AA</strong> (with best-effort
36 conformance to Level AAA where we can do so without degrading the
37 experience). Our target applies to:
38 </Text>
39 <Box className="ml-6 space-y-1 text-content-secondary">
40 <Text>&bull; The marketing site at alecrae.com.</Text>
41 <Text>&bull; The AlecRae web application at mail.alecrae.com.</Text>
42 <Text>&bull; The AlecRae desktop application (Electron).</Text>
43 <Text>&bull; The AlecRae mobile application (iOS + Android).</Text>
44 <Text>&bull; All legal and support content published under alecrae.com.</Text>
45 </Box>
46 </Box>
47
48 <Box className="space-y-4">
49 <Text as="h2" className="text-xl font-bold text-content">
50 2. Standards and laws
51 </Text>
52 <Text className="text-content-secondary leading-relaxed">
53 We aim to meet or exceed the following standards:
54 </Text>
55 <Box className="ml-6 space-y-1 text-content-secondary">
56 <Text>&bull; Web Content Accessibility Guidelines 2.2, Level AA.</Text>
57 <Text>&bull; Americans with Disabilities Act Title III effective communication standard.</Text>
58 <Text>&bull; Section 508 of the U.S. Rehabilitation Act (revised).</Text>
59 <Text>&bull; European Accessibility Act (EAA) effective 28 June 2025 for EU customers.</Text>
60 <Text>&bull; EN 301 549 v3.2.1 (harmonised EU standard).</Text>
61 <Text>&bull; UK Equality Act 2010 reasonable-adjustments obligation.</Text>
62 <Text>&bull; Accessibility for Ontarians with Disabilities Act (AODA), Canada.</Text>
63 </Box>
64 </Box>
65
66 <Box className="space-y-4">
67 <Text as="h2" className="text-xl font-bold text-content">
68 3. What this means in practice
69 </Text>
70 <Box className="ml-6 space-y-1 text-content-secondary">
71 <Text>&bull; Full keyboard navigation. Every action has a shortcut; nothing requires a mouse.</Text>
72 <Text>&bull; Screen-reader first. Semantic HTML, real ARIA, managed focus, live regions announced.</Text>
73 <Text>&bull; High-contrast, reduced-motion, and colour-blind-safe palettes.</Text>
74 <Text>&bull; Adjustable font size (small, medium, large) and density (compact, comfortable, spacious).</Text>
75 <Text>&bull; Minimum 44&times;44 CSS-pixel touch targets on mobile.</Text>
76 <Text>&bull; Dictation and voice control as a first-class input method.</Text>
77 <Text>&bull; Captions on every video; transcripts for every podcast or audio interview.</Text>
78 <Text>&bull; Plain-language copy wherever feasible.</Text>
79 </Box>
80 </Box>
81
82 <Box className="space-y-4">
83 <Text as="h2" className="text-xl font-bold text-content">
84 4. Supported assistive technologies
85 </Text>
86 <Text className="text-content-secondary leading-relaxed">
87 We test on the following assistive technologies on the latest two
88 stable versions of their host operating systems:
89 </Text>
90 <Box className="ml-6 space-y-1 text-content-secondary">
91 <Text>&bull; VoiceOver (macOS, iOS, iPadOS)</Text>
92 <Text>&bull; NVDA and JAWS (Windows)</Text>
93 <Text>&bull; TalkBack (Android)</Text>
94 <Text>&bull; Windows Narrator</Text>
95 <Text>&bull; Dragon NaturallySpeaking / Voice Control</Text>
96 <Text>&bull; Switch Control (iOS / macOS)</Text>
97 <Text>&bull; Browser zoom up to 400% without horizontal scrolling.</Text>
98 </Box>
99 </Box>
100
101 <Box className="space-y-4">
102 <Text as="h2" className="text-xl font-bold text-content">
103 5. Known limitations
104 </Text>
105 <Text className="text-content-secondary leading-relaxed">
106 We maintain a public log of known accessibility issues. The
107 following are known and being actively addressed:
108 </Text>
109 <Box className="ml-6 space-y-1 text-content-secondary">
110 <Text>&bull; The 3D spatial inbox view is an opt-in power-user feature and is not required to use the product; an equivalent 2D list is always available.</Text>
111 <Text>&bull; Some highly-interactive animations default to reduced motion when the OS-level reduced-motion preference is set.</Text>
112 </Box>
113 </Box>
114
115 <Box className="space-y-4">
116 <Text as="h2" className="text-xl font-bold text-content">
117 6. Feedback and escalation
118 </Text>
119 <Text className="text-content-secondary leading-relaxed">
120 If you experience an accessibility barrier in any AlecRae product
121 or content, we want to know. Contact:
122 </Text>
123 <Card className="bg-surface-subtle">
124 <CardContent>
125 <Box className="space-y-1">
126 <Text className="text-content">Email: accessibility@alecrae.com</Text>
127 <Text className="text-content">Subject line: &quot;Accessibility feedback&quot;</Text>
128 <Text className="text-content">
129 Postal: AlecRae, Inc., Attn: Accessibility, 548 Market Street, Suite 45000, San Francisco, CA 94104
130 </Text>
131 </Box>
132 </CardContent>
133 </Card>
134 <Text className="text-content-secondary leading-relaxed">
135 We aim to acknowledge accessibility reports within 2 business days
136 and to resolve or provide a mitigation plan within 30 days. If
137 you are unsatisfied with our response you may escalate to the
138 relevant national enforcement body (for example, the U.S. Access
139 Board, the EU National Enforcement Body, or the UK Equality and
140 Human Rights Commission).
141 </Text>
142 </Box>
143
144 <Box className="space-y-4">
145 <Text as="h2" className="text-xl font-bold text-content">
146 7. Assessment method
147 </Text>
148 <Text className="text-content-secondary leading-relaxed">
149 This statement is supported by automated testing (axe, Lighthouse,
150 Pa11y) integrated into our CI pipeline, plus regular manual
151 testing with assistive technologies and annual third-party audits
152 by a certified accessibility auditor. The current VPAT&reg;/ACR
153 can be requested from accessibility@alecrae.com.
154 </Text>
155 </Box>
156 </Box>
157 );
158}
Addedapps/web/app/(legal)/ai-transparency/page.tsx+151−0View fileUnifiedSplit
1import type { Metadata } from "next";
2import { Box, Text, Card, CardContent } from "@alecrae/ui";
3
4export const metadata: Metadata = {
5 title: "AI Transparency & Responsible AI | AlecRae",
6 description:
7 "AlecRae's responsible-AI practices, EU AI Act obligations, model inventory, human-oversight controls and user rights over automated decision-making.",
8};
9
10export default function AITransparencyPage() {
11 return (
12 <Box className="space-y-10">
13 <Box>
14 <Text as="h1" className="text-3xl font-bold text-content mb-2">
15 AI Transparency &amp; Responsible AI
16 </Text>
17 <Text className="text-content-tertiary">
18 Effective Date: April 16, 2026 &middot; Last Updated: April 16, 2026
19 </Text>
20 </Box>
21
22 <Text className="text-content-secondary leading-relaxed">
23 AlecRae is an AI-native product. This page describes every AI
24 system we use, how we govern it, and the controls you have over
25 automated decisions. It is intended to satisfy the transparency
26 obligations of the EU AI Act (Regulation (EU) 2024/1689), GDPR
27 Article 22, the NIST AI Risk Management Framework and the ISO/IEC
28 42001 AI Management System standard.
29 </Text>
30
31 <Box className="space-y-4">
32 <Text as="h2" className="text-xl font-bold text-content">
33 1. Our responsible-AI principles
34 </Text>
35 <Box className="ml-6 space-y-1 text-content-secondary">
36 <Text>&bull; <strong>Human-in-the-loop by default.</strong> Irreversible or consequential actions (sending, deleting, unsubscribing, paying) always require explicit human confirmation.</Text>
37 <Text>&bull; <strong>Transparency.</strong> Every AI-generated draft, summary, classification or action is labelled.</Text>
38 <Text>&bull; <strong>Opt-out without degradation.</strong> Every non-essential AI feature can be disabled without losing access to core email.</Text>
39 <Text>&bull; <strong>No advertising use.</strong> Your email is never used to train ad-targeting models.</Text>
40 <Text>&bull; <strong>No silent training on customer content.</strong> Individual writing-style models are private to your account and are not used to train other users&apos; models.</Text>
41 <Text>&bull; <strong>Right to object.</strong> Any automated decision with significant effect on you can be contested and reviewed by a human within 5 business days.</Text>
42 </Box>
43 </Box>
44
45 <Box className="space-y-4">
46 <Text as="h2" className="text-xl font-bold text-content">
47 2. AI systems inventory
48 </Text>
49 <Card className="bg-surface-subtle">
50 <CardContent>
51 <Box className="space-y-4 text-sm">
52 <Box>
53 <Text className="text-content font-semibold">Spam &amp; phishing classifier</Text>
54 <Text className="text-content-secondary">Purpose: protect all users. Model: AlecRae-tuned text classifier + signals from SPF/DKIM/DMARC/ARC. Essential — cannot be disabled. EU AI Act risk tier: limited.</Text>
55 </Box>
56 <Box>
57 <Text className="text-content font-semibold">Priority inbox / triage</Text>
58 <Text className="text-content-secondary">Purpose: rank incoming email by importance. Model: Claude Haiku. Optional. Tier: limited risk.</Text>
59 </Box>
60 <Box>
61 <Text className="text-content font-semibold">AI compose &amp; voice profile</Text>
62 <Text className="text-content-secondary">Purpose: generate draft replies in the user&apos;s style. Models: Claude Haiku / Sonnet / Opus (tier-dependent) + local WebGPU inference when available. Optional. Tier: minimal risk.</Text>
63 </Box>
64 <Box>
65 <Text className="text-content font-semibold">Grammar &amp; spell agent</Text>
66 <Text className="text-content-secondary">Purpose: proofread outgoing text. Runs locally on the user device via Transformers.js / WebLLM. Tier: minimal risk.</Text>
67 </Box>
68 <Box>
69 <Text className="text-content font-semibold">Sender trust &amp; phishing explainer</Text>
70 <Text className="text-content-secondary">Purpose: explain why an email is suspicious. Model: Claude Sonnet + DNS + WHOIS signals. Tier: limited risk.</Text>
71 </Box>
72 <Box>
73 <Text className="text-content font-semibold">Dictation &amp; transcription</Text>
74 <Text className="text-content-secondary">Purpose: speech-to-text for voice composition and voice messages. Model: OpenAI Whisper. Optional. Tier: minimal risk.</Text>
75 </Box>
76 <Box>
77 <Text className="text-content font-semibold">Inbox agent (overnight)</Text>
78 <Text className="text-content-secondary">Purpose: draft replies and batch morning briefing. Outputs are proposals only and require human approval. Tier: limited risk.</Text>
79 </Box>
80 <Box>
81 <Text className="text-content font-semibold">Semantic search</Text>
82 <Text className="text-content-secondary">Purpose: natural-language email retrieval. Model: embeddings + Claude Haiku. Optional. Tier: minimal risk.</Text>
83 </Box>
84 </Box>
85 </CardContent>
86 </Card>
87 </Box>
88
89 <Box className="space-y-4">
90 <Text as="h2" className="text-xl font-bold text-content">
91 3. Automated decision-making (GDPR Article 22)
92 </Text>
93 <Text className="text-content-secondary leading-relaxed">
94 Where an AlecRae system makes an automated decision that produces
95 legal or similarly significant effects on you (for example,
96 classifying a message as spam or blocking a sender), you have the
97 right to:
98 </Text>
99 <Box className="ml-6 space-y-1 text-content-secondary">
100 <Text>&bull; Request meaningful information about the logic of the decision.</Text>
101 <Text>&bull; Express your point of view and contest the decision.</Text>
102 <Text>&bull; Obtain human review within 5 business days of a written request.</Text>
103 </Box>
104 <Text className="text-content-secondary leading-relaxed">
105 Exercise these rights by emailing dpo@alecrae.com.
106 </Text>
107 </Box>
108
109 <Box className="space-y-4">
110 <Text as="h2" className="text-xl font-bold text-content">
111 4. Training data and fine-tuning
112 </Text>
113 <Box className="ml-6 space-y-1 text-content-secondary">
114 <Text>&bull; We do <strong>not</strong> use your individual email content as training data for foundation models.</Text>
115 <Text>&bull; We may use aggregated, anonymised patterns (e.g., spam signatures) to improve our own classifiers. You can opt out at Settings &gt; Privacy &gt; AI training.</Text>
116 <Text>&bull; Your personal voice-profile fingerprints are stored encrypted and scoped to your account.</Text>
117 <Text>&bull; Our third-party model providers (Anthropic, OpenAI) are contractually barred from training their models on our production API traffic.</Text>
118 </Box>
119 </Box>
120
121 <Box className="space-y-4">
122 <Text as="h2" className="text-xl font-bold text-content">
123 5. EU AI Act status
124 </Text>
125 <Text className="text-content-secondary leading-relaxed">
126 AlecRae is a <strong>deployer and provider of limited-risk AI
127 systems</strong> under the EU AI Act. We maintain:
128 </Text>
129 <Box className="ml-6 space-y-1 text-content-secondary">
130 <Text>&bull; An internal AI System Register mapping each model to its risk tier.</Text>
131 <Text>&bull; A risk-management process aligned to ISO/IEC 42001 and NIST AI RMF.</Text>
132 <Text>&bull; Incident-response procedures to report serious incidents within the 15-day window under Article 62.</Text>
133 <Text>&bull; Ongoing monitoring for bias, drift and performance degradation.</Text>
134 </Box>
135 </Box>
136
137 <Box className="space-y-4">
138 <Text as="h2" className="text-xl font-bold text-content">
139 6. Reporting an AI harm
140 </Text>
141 <Text className="text-content-secondary leading-relaxed">
142 If you believe an AlecRae AI system has caused you harm — for
143 example, a critical email was mis-classified as spam and caused
144 you loss — please email ai-ethics@alecrae.com. We will
145 investigate, respond within 10 business days, and publish an
146 anonymised summary in our annual Responsible AI report.
147 </Text>
148 </Box>
149 </Box>
150 );
151}
Addedapps/web/app/(legal)/california-notice/page.tsx+141−0View fileUnifiedSplit
1import type { Metadata } from "next";
2import { Box, Text, Card, CardContent } from "@alecrae/ui";
3
4export const metadata: Metadata = {
5 title: "California Notice at Collection | AlecRae",
6 description:
7 "AlecRae's California Notice at Collection describing the categories of personal information we collect, purposes, retention and your CPRA rights.",
8};
9
10export default function CaliforniaNoticePage() {
11 return (
12 <Box className="space-y-10">
13 <Box>
14 <Text as="h1" className="text-3xl font-bold text-content mb-2">
15 California Notice at Collection
16 </Text>
17 <Text className="text-content-tertiary">
18 Effective Date: April 16, 2026 &middot; Last Updated: April 16, 2026
19 </Text>
20 </Box>
21
22 <Text className="text-content-secondary leading-relaxed">
23 This Notice at Collection is provided to California residents under
24 the California Consumer Privacy Act (&quot;CCPA&quot;), as amended
25 by the California Privacy Rights Act (&quot;CPRA&quot;), Cal. Civ.
26 Code &sect;&sect; 1798.100 et seq. It describes the categories of
27 personal information we collect, the purposes, retention, and your
28 rights. For the full description of our privacy practices, see our{" "}
29 <a className="text-brand-600 hover:underline" href="/privacy">Privacy Policy</a>.
30 </Text>
31
32 <Box className="space-y-4">
33 <Text as="h2" className="text-xl font-bold text-content">
34 1. Categories of personal information we collect
35 </Text>
36 <Card className="bg-surface-subtle">
37 <CardContent>
38 <Box className="space-y-2 text-sm">
39 <Text className="text-content"><strong>Identifiers</strong> — name, email address, user ID, IP address, device identifiers.</Text>
40 <Text className="text-content"><strong>Customer records</strong> — billing address, telephone number, employer.</Text>
41 <Text className="text-content"><strong>Commercial information</strong> — subscription tier, invoice history, promo codes redeemed.</Text>
42 <Text className="text-content"><strong>Internet / network activity</strong> — log-in events, pages viewed, API calls, feature usage.</Text>
43 <Text className="text-content"><strong>Geolocation</strong> — approximate city-level location derived from IP address.</Text>
44 <Text className="text-content"><strong>Professional information</strong> — job title, organisation, role (if volunteered).</Text>
45 <Text className="text-content"><strong>Communications content</strong> — email headers, body and attachments transmitted through the service.</Text>
46 <Text className="text-content"><strong>Inferences</strong> — AI-derived priority scores, sentiment, relationship strength, writing-style signals.</Text>
47 <Text className="text-content"><strong>Sensitive PI</strong> — account login credentials, contents of email, recovery keys, passkey public credentials.</Text>
48 </Box>
49 </CardContent>
50 </Card>
51 </Box>
52
53 <Box className="space-y-4">
54 <Text as="h2" className="text-xl font-bold text-content">
55 2. Business or commercial purposes
56 </Text>
57 <Box className="ml-6 space-y-1 text-content-secondary">
58 <Text>&bull; Delivering the AlecRae Service (sending, receiving, storing and organising email).</Text>
59 <Text>&bull; Account authentication and security, including passkey ceremonies and fraud detection.</Text>
60 <Text>&bull; Providing AI-assisted features that you have enabled or that are essential to the service (spam / phishing protection).</Text>
61 <Text>&bull; Billing and subscription management through Stripe.</Text>
62 <Text>&bull; Providing support when you contact us.</Text>
63 <Text>&bull; Complying with legal and regulatory obligations.</Text>
64 <Text>&bull; Protecting the rights, property, or safety of AlecRae, our users, or the public.</Text>
65 </Box>
66 </Box>
67
68 <Box className="space-y-4">
69 <Text as="h2" className="text-xl font-bold text-content">
70 3. Sensitive personal information
71 </Text>
72 <Text className="text-content-secondary leading-relaxed">
73 We use sensitive personal information <strong>only</strong> for
74 the purposes listed in CPRA &sect; 1798.121(a) — to perform the
75 services you requested, to prevent fraud and security incidents,
76 to protect the physical safety of users, and to comply with law.
77 We do <strong>not</strong> use sensitive personal information to
78 infer characteristics about you.
79 </Text>
80 </Box>
81
82 <Box className="space-y-4">
83 <Text as="h2" className="text-xl font-bold text-content">
84 4. Sale, sharing, and cross-context behavioural advertising
85 </Text>
86 <Text className="text-content-secondary leading-relaxed">
87 We do not sell or share personal information and we never target
88 advertising on the basis of your email or product usage. See our{" "}
89 <a className="text-brand-600 hover:underline" href="/do-not-sell">Do Not Sell or Share</a> page for details.
90 </Text>
91 </Box>
92
93 <Box className="space-y-4">
94 <Text as="h2" className="text-xl font-bold text-content">
95 5. Retention
96 </Text>
97 <Text className="text-content-secondary leading-relaxed">
98 We retain personal information for as long as needed to provide
99 the Service plus a limited grace period for account recovery,
100 legal compliance and fraud prevention:
101 </Text>
102 <Box className="ml-6 space-y-1 text-content-secondary">
103 <Text>&bull; Account and billing records: life of subscription + 7 years (tax / financial law).</Text>
104 <Text>&bull; Email content: while the account is active; 30-day soft-delete after termination; then secure deletion.</Text>
105 <Text>&bull; Server and security logs: up to 13 months, then aggregated.</Text>
106 <Text>&bull; Customer-support records: up to 3 years from last contact.</Text>
107 <Text>&bull; AI-derived data: deleted within 30 days of account closure.</Text>
108 </Box>
109 </Box>
110
111 <Box className="space-y-4">
112 <Text as="h2" className="text-xl font-bold text-content">
113 6. California rights
114 </Text>
115 <Text className="text-content-secondary leading-relaxed">
116 California residents have the right to know, delete, correct,
117 opt-out of sale or sharing, limit the use of sensitive PI, and
118 non-discrimination. Submit a request to{" "}
119 <a className="text-brand-600 hover:underline" href="mailto:privacy@alecrae.com">privacy@alecrae.com</a>{" "}
120 or in the app under Settings &gt; Privacy &gt; California rights.
121 A full description of these rights is at{" "}
122 <a className="text-brand-600 hover:underline" href="/do-not-sell">/do-not-sell</a>.
123 </Text>
124 </Box>
125
126 <Box className="space-y-4">
127 <Text as="h2" className="text-xl font-bold text-content">
128 7. Shine the Light (Cal. Civ. Code &sect; 1798.83)
129 </Text>
130 <Text className="text-content-secondary leading-relaxed">
131 California residents may request once per calendar year a list of
132 the categories of personal information disclosed to third parties
133 for direct marketing purposes. AlecRae does not disclose personal
134 information to third parties for direct marketing purposes, but
135 you may still request confirmation by emailing
136 privacy@alecrae.com.
137 </Text>
138 </Box>
139 </Box>
140 );
141}
Addedapps/web/app/(legal)/children/page.tsx+111−0View fileUnifiedSplit
1import type { Metadata } from "next";
2import { Box, Text, Card, CardContent } from "@alecrae/ui";
3
4export const metadata: Metadata = {
5 title: "Children's Privacy | AlecRae",
6 description:
7 "How AlecRae complies with COPPA, UK ICO Age-Appropriate Design Code, and our policy on accounts for minors.",
8};
9
10export default function ChildrenPrivacyPage() {
11 return (
12 <Box className="space-y-10">
13 <Box>
14 <Text as="h1" className="text-3xl font-bold text-content mb-2">
15 Children&apos;s Privacy
16 </Text>
17 <Text className="text-content-tertiary">
18 Effective Date: April 16, 2026 &middot; Last Updated: April 16, 2026
19 </Text>
20 </Box>
21
22 <Card className="border-amber-500/30 bg-amber-500/5">
23 <CardContent>
24 <Box className="p-2 space-y-2">
25 <Text as="h2" className="text-lg font-semibold text-amber-700">
26 AlecRae is not directed at children under 13.
27 </Text>
28 <Text className="text-content-secondary leading-relaxed">
29 We do not knowingly collect personal information from children
30 under 13 (or under 16 in the European Economic Area / United
31 Kingdom). If you believe a child has created an AlecRae
32 account, email{" "}
33 <a className="text-brand-600 hover:underline" href="mailto:privacy@alecrae.com">privacy@alecrae.com</a>{" "}
34 and we will delete the account and associated data promptly.
35 </Text>
36 </Box>
37 </CardContent>
38 </Card>
39
40 <Box className="space-y-4">
41 <Text as="h2" className="text-xl font-bold text-content">
42 1. Minimum age
43 </Text>
44 <Text className="text-content-secondary leading-relaxed">
45 AlecRae&apos;s minimum age is <strong>13</strong> (or <strong>16</strong>
46 {" "}in the European Economic Area, the United Kingdom and any other
47 jurisdiction where a higher digital-consent age applies). Users
48 under 18 must have parental or legal-guardian consent before
49 creating an account.
50 </Text>
51 </Box>
52
53 <Box className="space-y-4">
54 <Text as="h2" className="text-xl font-bold text-content">
55 2. How we comply with COPPA
56 </Text>
57 <Box className="ml-6 space-y-1 text-content-secondary">
58 <Text>&bull; We do not advertise the Service to or in properties directed at children.</Text>
59 <Text>&bull; Our registration flow requires an affirmative age-gate checkbox that presents the 13+ (or 16+) minimum prominently.</Text>
60 <Text>&bull; We do not knowingly collect, use or disclose personal information from children under 13 in violation of COPPA.</Text>
61 <Text>&bull; If we learn that a child under the applicable age has created an account, we will delete the account, notify the parent / guardian and refund any paid subscription.</Text>
62 <Text>&bull; We do not provide third-party trackers that could retarget minors.</Text>
63 </Box>
64 </Box>
65
66 <Box className="space-y-4">
67 <Text as="h2" className="text-xl font-bold text-content">
68 3. UK ICO Age-Appropriate Design Code (&quot;Children&apos;s Code&quot;)
69 </Text>
70 <Text className="text-content-secondary leading-relaxed">
71 For UK users, AlecRae adheres to the ICO&apos;s 15 standards for
72 services likely to be accessed by children, including: high-privacy
73 defaults, no nudging toward lower-privacy options, no
74 geolocation-on-by-default, and restriction of profiling.
75 </Text>
76 </Box>
77
78 <Box className="space-y-4">
79 <Text as="h2" className="text-xl font-bold text-content">
80 4. Parental controls and rights
81 </Text>
82 <Text className="text-content-secondary leading-relaxed">
83 A parent or legal guardian may, at any time:
84 </Text>
85 <Box className="ml-6 space-y-1 text-content-secondary">
86 <Text>&bull; Review the personal information we hold about their child.</Text>
87 <Text>&bull; Request deletion of that information.</Text>
88 <Text>&bull; Refuse further collection or use.</Text>
89 <Text>&bull; Terminate the account and receive a refund of any unused subscription.</Text>
90 </Box>
91 <Text className="text-content-secondary leading-relaxed">
92 Requests should be sent to privacy@alecrae.com with &quot;Parental
93 Request&quot; in the subject and include proof of guardianship.
94 </Text>
95 </Box>
96
97 <Box className="space-y-4">
98 <Text as="h2" className="text-xl font-bold text-content">
99 5. Enterprise and education accounts
100 </Text>
101 <Text className="text-content-secondary leading-relaxed">
102 Accounts provisioned by a school, district or employer are
103 governed by the administrator&apos;s Data Processing Agreement. In
104 the case of K-12 customers the administrator is responsible for
105 obtaining any applicable parental consent under COPPA, FERPA and
106 state student-privacy laws.
107 </Text>
108 </Box>
109 </Box>
110 );
111}
Addedapps/web/app/(legal)/compliance/page.tsx+112−0View fileUnifiedSplit
1import type { Metadata } from "next";
2import { Box, Text, Card, CardContent } from "@alecrae/ui";
3
4export const metadata: Metadata = {
5 title: "Corporate Compliance | AlecRae",
6 description:
7 "AlecRae's modern slavery, anti-bribery, sanctions / export-control and anti-money-laundering compliance statements.",
8};
9
10export default function CompliancePage() {
11 return (
12 <Box className="space-y-10">
13 <Box>
14 <Text as="h1" className="text-3xl font-bold text-content mb-2">
15 Corporate Compliance
16 </Text>
17 <Text className="text-content-tertiary">
18 Effective Date: April 16, 2026 &middot; Last Updated: April 16, 2026
19 </Text>
20 </Box>
21
22 <Text className="text-content-secondary leading-relaxed">
23 AlecRae is committed to operating ethically and lawfully in every
24 jurisdiction we serve. This page brings together our public
25 compliance statements on modern slavery, anti-bribery, sanctions /
26 export control and anti-money-laundering. It is updated at least
27 annually and at any time a material change occurs.
28 </Text>
29
30 <Box className="space-y-4">
31 <Text as="h2" className="text-xl font-bold text-content">
32 1. Modern Slavery &amp; Human Trafficking Statement
33 </Text>
34 <Text className="text-content-secondary leading-relaxed">
35 This statement is made pursuant to section 54(1) of the UK
36 Modern Slavery Act 2015 and the California Transparency in Supply
37 Chains Act of 2010, and is provided on a voluntary basis even
38 though AlecRae does not yet meet the statutory turnover threshold.
39 </Text>
40 <Box className="ml-6 space-y-1 text-content-secondary">
41 <Text>&bull; AlecRae has a zero-tolerance policy to modern slavery, human trafficking, forced labour and child labour.</Text>
42 <Text>&bull; Our supply chain is short and professional: cloud-infrastructure vendors, financial institutions, software libraries and professional-service firms.</Text>
43 <Text>&bull; Every direct vendor engagement includes contractual representations that the vendor complies with applicable labour laws and does not use forced, bonded or child labour.</Text>
44 <Text>&bull; We require vendors to have an equivalent whistleblowing channel for their own staff.</Text>
45 <Text>&bull; All employees and contractors complete annual training on identifying and reporting modern-slavery concerns.</Text>
46 <Text>&bull; Concerns may be reported anonymously via ethics@alecrae.com.</Text>
47 </Box>
48 </Box>
49
50 <Box className="space-y-4">
51 <Text as="h2" className="text-xl font-bold text-content">
52 2. Anti-Bribery and Anti-Corruption
53 </Text>
54 <Text className="text-content-secondary leading-relaxed">
55 AlecRae complies with the US Foreign Corrupt Practices Act
56 (FCPA), the UK Bribery Act 2010 and any equivalent anti-corruption
57 law in each jurisdiction where we do business. We:
58 </Text>
59 <Box className="ml-6 space-y-1 text-content-secondary">
60 <Text>&bull; Prohibit offering, giving, accepting or receiving bribes or &quot;facilitation payments&quot; of any kind.</Text>
61 <Text>&bull; Forbid political contributions on behalf of AlecRae.</Text>
62 <Text>&bull; Cap gifts and hospitality to reasonable business-courtesy levels and require manager approval above a de-minimis threshold.</Text>
63 <Text>&bull; Require vendors, agents and partners to adhere to equivalent anti-corruption standards.</Text>
64 </Box>
65 </Box>
66
67 <Box className="space-y-4">
68 <Text as="h2" className="text-xl font-bold text-content">
69 3. Export Control, Sanctions and Restricted Parties
70 </Text>
71 <Text className="text-content-secondary leading-relaxed">
72 AlecRae is subject to US export-control laws, including the
73 Export Administration Regulations (EAR) and the regulations
74 administered by the Office of Foreign Assets Control (OFAC), as
75 well as equivalent UK, EU and UN sanctions regimes.
76 </Text>
77 <Box className="ml-6 space-y-1 text-content-secondary">
78 <Text>&bull; The AlecRae Service is not available in, or for users who are ordinarily resident in, jurisdictions subject to comprehensive US sanctions (currently: Cuba, Iran, North Korea, Syria, and the Crimea, Donetsk and Luhansk regions of Ukraine).</Text>
79 <Text>&bull; We will not knowingly contract with individuals or entities on the OFAC Specially Designated Nationals (SDN) list, the UK HMT Consolidated List, or the EU Consolidated Financial Sanctions list.</Text>
80 <Text>&bull; Encryption technology in our products is classified as eligible for the Mass Market exception (ENC) under 15 CFR &sect; 742.15(b); a BIS notification has been filed.</Text>
81 <Text>&bull; Users are responsible for complying with applicable export-control laws of their own country, including re-export restrictions.</Text>
82 </Box>
83 </Box>
84
85 <Box className="space-y-4">
86 <Text as="h2" className="text-xl font-bold text-content">
87 4. Anti-Money-Laundering (AML) &amp; KYC
88 </Text>
89 <Text className="text-content-secondary leading-relaxed">
90 Although AlecRae is not itself a regulated financial institution,
91 we operate consistently with good AML practice: we collect
92 minimum necessary KYC data through Stripe, monitor for unusual
93 payment patterns, and cooperate with law-enforcement requests
94 supported by valid legal process.
95 </Text>
96 </Box>
97
98 <Box className="space-y-4">
99 <Text as="h2" className="text-xl font-bold text-content">
100 5. Whistleblower Protection
101 </Text>
102 <Text className="text-content-secondary leading-relaxed">
103 Any employee, contractor, vendor or user may raise a compliance
104 concern confidentially by emailing ethics@alecrae.com or via an
105 independent third-party hotline listed in our employee handbook.
106 Retaliation against a good-faith reporter is a dismissable
107 offence.
108 </Text>
109 </Box>
110 </Box>
111 );
112}
Modifiedapps/web/app/(legal)/cookies/page.tsx+2−2View fileUnifiedSplit
2222 <Text variant="body-md" className="text-content-secondary leading-relaxed">
2323 This Cookie Policy explains how AlecRae, Inc. (&quot;AlecRae,&quot;
2424 &quot;we,&quot; &quot;us,&quot; &quot;our&quot;) uses cookies and similar
25 tracking technologies when you visit or use our platform at alecrae.dev (the
25 tracking technologies when you visit or use our platform at alecrae.com (the
2626 &quot;Service&quot;). This policy should be read alongside our{" "}
2727 <Text as="span" className="text-brand-600 font-medium">
2828 Privacy Policy
472472
473473 <Box className="border-t border-border pt-6">
474474 <Text variant="body-sm" muted>
475 Questions about our use of cookies? Contact us at privacy@alecrae.dev.
475 Questions about our use of cookies? Contact us at privacy@alecrae.com.
476476 </Text>
477477 </Box>
478478 </Box>
Modifiedapps/web/app/(legal)/dmca/page.tsx+3−3View fileUnifiedSplit
6666 Attn: Copyright Agent / Legal Department
6767 </Text>
6868 <Text variant="body-md" className="text-content-secondary">
69 Email: dmca@alecrae.dev
69 Email: dmca@alecrae.com
7070 </Text>
7171 <Text variant="body-md" className="text-content-secondary">
7272 Mailing Address: 548 Market Street, Suite 45000, San Francisco, CA 94104
277277
278278 <Box className="border-t border-border pt-6">
279279 <Text variant="body-sm" muted>
280 To report copyright infringement, contact our DMCA agent at dmca@alecrae.dev.
281 For general legal inquiries, contact legal@alecrae.dev.
280 To report copyright infringement, contact our DMCA agent at dmca@alecrae.com.
281 For general legal inquiries, contact legal@alecrae.com.
282282 </Text>
283283 </Box>
284284 </Box>
Addedapps/web/app/(legal)/do-not-sell/page.tsx+169−0View fileUnifiedSplit
1import type { Metadata } from "next";
2import { Box, Text, Card, CardContent } from "@alecrae/ui";
3
4export const metadata: Metadata = {
5 title: "Do Not Sell or Share My Personal Information | AlecRae",
6 description:
7 "Your California Consumer Privacy Act (CCPA/CPRA) right to opt out of the sale or sharing of personal information and how AlecRae honours that right.",
8};
9
10export default function DoNotSellPage() {
11 return (
12 <Box className="space-y-10">
13 <Box>
14 <Text as="h1" className="text-3xl font-bold text-content mb-2">
15 Do Not Sell or Share My Personal Information
16 </Text>
17 <Text className="text-content-tertiary">
18 Effective Date: April 16, 2026 &middot; Last Updated: April 16, 2026
19 </Text>
20 </Box>
21
22 <Card className="border-green-500/30 bg-green-500/5">
23 <CardContent>
24 <Box className="p-2">
25 <Text as="h2" className="text-lg font-semibold text-green-700 mb-2">
26 Summary: AlecRae does not sell or share your personal information.
27 </Text>
28 <Text className="text-content-secondary leading-relaxed">
29 We are structurally a zero-data-broker business. We do not sell,
30 rent, trade, or share personal information for cross-context
31 behavioural advertising. This page documents your rights under
32 California law and how to exercise them — even though, in our
33 case, there is nothing to opt out of.
34 </Text>
35 </Box>
36 </CardContent>
37 </Card>
38
39 <Box className="space-y-4">
40 <Text as="h2" className="text-xl font-bold text-content">
41 1. Your rights under the CCPA and CPRA
42 </Text>
43 <Text className="text-content-secondary leading-relaxed">
44 If you are a California resident, the California Consumer Privacy
45 Act (&quot;CCPA&quot;), as amended by the California Privacy Rights
46 Act (&quot;CPRA&quot;), gives you the following rights in relation
47 to your personal information:
48 </Text>
49 <Box className="ml-6 space-y-1 text-content-secondary">
50 <Text>&bull; The right to know what personal information we collect, use, disclose and retain.</Text>
51 <Text>&bull; The right to delete personal information we have collected about you.</Text>
52 <Text>&bull; The right to correct inaccurate personal information.</Text>
53 <Text>&bull; The right to opt out of the &quot;sale&quot; or &quot;sharing&quot; of your personal information.</Text>
54 <Text>&bull; The right to limit the use and disclosure of sensitive personal information.</Text>
55 <Text>&bull; The right to non-discrimination for exercising any of these rights.</Text>
56 <Text>&bull; The right to appoint an authorised agent to submit requests on your behalf.</Text>
57 </Box>
58 </Box>
59
60 <Box className="space-y-4">
61 <Text as="h2" className="text-xl font-bold text-content">
62 2. What we do (and do not) do with your personal information
63 </Text>
64 <Text className="text-content-secondary leading-relaxed">
65 AlecRae does <strong>not</strong> and has never:
66 </Text>
67 <Box className="ml-6 space-y-1 text-content-secondary">
68 <Text>&bull; Sold personal information for monetary or other valuable consideration.</Text>
69 <Text>&bull; Shared personal information for cross-context behavioural advertising.</Text>
70 <Text>&bull; Allowed third-party advertising networks or data brokers inside the product.</Text>
71 <Text>&bull; Used the content of your email to build advertising profiles.</Text>
72 <Text>&bull; Trained external advertising or ad-tech AI models on your email content.</Text>
73 </Box>
74 <Text className="text-content-secondary leading-relaxed">
75 AlecRae <strong>does</strong>:
76 </Text>
77 <Box className="ml-6 space-y-1 text-content-secondary">
78 <Text>&bull; Use subprocessors to deliver the service (see our Subprocessor list).</Text>
79 <Text>&bull; Process your data inside our own AI pipelines so we can deliver the product you asked for.</Text>
80 <Text>&bull; Respond to valid legal process when required by law.</Text>
81 </Box>
82 <Text className="text-content-secondary leading-relaxed">
83 Because of this architecture, there is nothing to &quot;opt out&quot;
84 of — the default position for every user is already &quot;do not
85 sell, do not share&quot;. However, you may still file a request to
86 have that position recorded against your account.
87 </Text>
88 </Box>
89
90 <Box className="space-y-4">
91 <Text as="h2" className="text-xl font-bold text-content">
92 3. Global Privacy Control (GPC)
93 </Text>
94 <Text className="text-content-secondary leading-relaxed">
95 AlecRae honours the Global Privacy Control signal as a valid
96 opt-out of &quot;sale&quot; and &quot;sharing&quot; under the CPRA.
97 If your browser transmits a GPC signal, we will automatically
98 record your opt-out preference the first time you load the site.
99 You can verify GPC is active at{" "}
100 <a className="text-brand-600 hover:underline" href="https://globalprivacycontrol.org/" rel="noopener noreferrer">
101 globalprivacycontrol.org
102 </a>
103 .
104 </Text>
105 </Box>
106
107 <Box className="space-y-4">
108 <Text as="h2" className="text-xl font-bold text-content">
109 4. How to submit a request
110 </Text>
111 <Text className="text-content-secondary leading-relaxed">
112 To submit a &quot;Do Not Sell or Share&quot; request, a deletion
113 request, a correction request or any other California rights
114 request, email{" "}
115 <a className="text-brand-600 hover:underline" href="mailto:privacy@alecrae.com">
116 privacy@alecrae.com
117 </a>{" "}
118 with &quot;CCPA Request&quot; in the subject line, or use the
119 in-product Privacy &gt; California rights screen. We will:
120 </Text>
121 <Box className="ml-6 space-y-1 text-content-secondary">
122 <Text>&bull; Confirm receipt within 10 business days.</Text>
123 <Text>&bull; Verify your identity in a way that matches the sensitivity of the request.</Text>
124 <Text>&bull; Respond within 45 calendar days (extendable once by 45 days with notice).</Text>
125 <Text>&bull; Never charge a fee, unless the request is manifestly unfounded or excessive.</Text>
126 </Box>
127 </Box>
128
129 <Box className="space-y-4">
130 <Text as="h2" className="text-xl font-bold text-content">
131 5. Authorised agents
132 </Text>
133 <Text className="text-content-secondary leading-relaxed">
134 You may designate an authorised agent to submit a request on your
135 behalf. We will require written proof of the agent&apos;s authority
136 (e.g., a signed permission or a power of attorney), plus identity
137 verification of the consumer making the request.
138 </Text>
139 </Box>
140
141 <Box className="space-y-4">
142 <Text as="h2" className="text-xl font-bold text-content">
143 6. Non-discrimination
144 </Text>
145 <Text className="text-content-secondary leading-relaxed">
146 We will never deny service, charge a different price, or provide a
147 different level of quality because you exercised a privacy right.
148 </Text>
149 </Box>
150
151 <Box className="space-y-4">
152 <Text as="h2" className="text-xl font-bold text-content">
153 7. Contact
154 </Text>
155 <Card className="bg-surface-subtle">
156 <CardContent>
157 <Box className="space-y-1">
158 <Text className="text-content">AlecRae, Inc.</Text>
159 <Text className="text-content">Attn: Privacy Team</Text>
160 <Text className="text-content">548 Market Street, Suite 45000, San Francisco, CA 94104</Text>
161 <Text className="text-content">privacy@alecrae.com &middot; dpo@alecrae.com</Text>
162 <Text className="text-content">Toll-free: 1-888-ALECRAE (available once activated)</Text>
163 </Box>
164 </CardContent>
165 </Card>
166 </Box>
167 </Box>
168 );
169}
Modifiedapps/web/app/(legal)/dpa/page.tsx+1−1View fileUnifiedSplit
213213
214214 <Box className="mt-12 pt-6 border-t border-border">
215215 <Text className="text-content-tertiary text-sm mb-4">
216 This Data Processing Agreement is effective as of the date the Controller begins using the Service. For questions about this DPA or to request a signed copy, contact legal@alecrae.dev or dpo@alecrae.dev.
216 This Data Processing Agreement is effective as of the date the Controller begins using the Service. For questions about this DPA or to request a signed copy, contact legal@alecrae.com or dpo@alecrae.com.
217217 </Text>
218218 </Box>
219219 </Box>
Addedapps/web/app/(legal)/impressum/page.tsx+146−0View fileUnifiedSplit
1import type { Metadata } from "next";
2import { Box, Text, Card, CardContent } from "@alecrae/ui";
3
4export const metadata: Metadata = {
5 title: "Impressum / Legal Notice | AlecRae",
6 description:
7 "Legal notice and company disclosures for AlecRae under the German TMG §5 and similar EU transparency laws.",
8};
9
10export default function ImpressumPage() {
11 return (
12 <Box className="space-y-10">
13 <Box>
14 <Text as="h1" className="text-3xl font-bold text-content mb-2">
15 Impressum / Legal Notice
16 </Text>
17 <Text className="text-content-tertiary">
18 Last Updated: April 16, 2026
19 </Text>
20 </Box>
21
22 <Text className="text-content-secondary leading-relaxed">
23 This page fulfils the information obligations of &sect; 5 TMG
24 (Germany), Art. 14 Directive 2006/123/EC and equivalent commercial
25 transparency laws in the United Kingdom, Austria, Switzerland and
26 the wider European Economic Area.
27 </Text>
28
29 <Box className="space-y-4">
30 <Text as="h2" className="text-xl font-bold text-content">
31 1. Operator
32 </Text>
33 <Card className="bg-surface-subtle">
34 <CardContent>
35 <Box className="space-y-1">
36 <Text className="text-content"><strong>AlecRae, Inc.</strong></Text>
37 <Text className="text-content">A Delaware C-Corporation (USA)</Text>
38 <Text className="text-content">548 Market Street, Suite 45000</Text>
39 <Text className="text-content">San Francisco, CA 94104</Text>
40 <Text className="text-content">United States of America</Text>
41 <Text className="text-content">Delaware File Number: (pending issuance)</Text>
42 <Text className="text-content">IRS EIN: (pending issuance)</Text>
43 </Box>
44 </CardContent>
45 </Card>
46 </Box>
47
48 <Box className="space-y-4">
49 <Text as="h2" className="text-xl font-bold text-content">
50 2. Contact
51 </Text>
52 <Card className="bg-surface-subtle">
53 <CardContent>
54 <Box className="space-y-1">
55 <Text className="text-content">General: hello@alecrae.com</Text>
56 <Text className="text-content">Legal: legal@alecrae.com</Text>
57 <Text className="text-content">Privacy / DPO: dpo@alecrae.com</Text>
58 <Text className="text-content">Security: security@alecrae.com</Text>
59 <Text className="text-content">Press: press@alecrae.com</Text>
60 </Box>
61 </CardContent>
62 </Card>
63 </Box>
64
65 <Box className="space-y-4">
66 <Text as="h2" className="text-xl font-bold text-content">
67 3. Authorised representative
68 </Text>
69 <Text className="text-content-secondary leading-relaxed">
70 Responsible for content under &sect; 18 Abs. 2 MStV:
71 Craig Kanty-Nehls, CEO, at the address above.
72 </Text>
73 </Box>
74
75 <Box className="space-y-4">
76 <Text as="h2" className="text-xl font-bold text-content">
77 4. EU GDPR representative (Art. 27)
78 </Text>
79 <Text className="text-content-secondary leading-relaxed">
80 For data subjects in the European Economic Area, our Article 27
81 representative is appointed and the name, address and contact
82 details will be published here and in our Privacy Policy as soon
83 as the appointment is finalised. In the interim, please contact
84 dpo@alecrae.com for any GDPR-related request.
85 </Text>
86 </Box>
87
88 <Box className="space-y-4">
89 <Text as="h2" className="text-xl font-bold text-content">
90 5. UK GDPR representative (Art. 27 UK GDPR)
91 </Text>
92 <Text className="text-content-secondary leading-relaxed">
93 For data subjects in the United Kingdom, our Article 27 UK GDPR
94 representative is appointed and the name, address and contact
95 details will be published here and in our Privacy Policy as soon
96 as the appointment is finalised. In the interim, please contact
97 dpo@alecrae.com for any UK GDPR-related request.
98 </Text>
99 </Box>
100
101 <Box className="space-y-4">
102 <Text as="h2" className="text-xl font-bold text-content">
103 6. Online Dispute Resolution (EU Regulation 524/2013)
104 </Text>
105 <Text className="text-content-secondary leading-relaxed">
106 The European Commission provides a platform for online dispute
107 resolution at{" "}
108 <a className="text-brand-600 hover:underline" href="https://ec.europa.eu/consumers/odr/" rel="noopener noreferrer">
109 ec.europa.eu/consumers/odr
110 </a>
111 . AlecRae is not obliged, and is not willing, to participate in
112 alternative dispute-resolution proceedings before a consumer
113 arbitration board, but consumers may still use the ODR platform
114 to contact us. Our contact address is legal@alecrae.com.
115 </Text>
116 </Box>
117
118 <Box className="space-y-4">
119 <Text as="h2" className="text-xl font-bold text-content">
120 7. Liability for content and links
121 </Text>
122 <Text className="text-content-secondary leading-relaxed">
123 As a service provider, we are responsible for our own content on
124 these pages in accordance with general laws (&sect; 7 Abs. 1
125 TMG). According to &sect;&sect; 8 to 10 TMG, we are not obliged
126 to monitor transmitted or stored third-party information, or to
127 investigate circumstances that indicate unlawful activity. Upon
128 notification of specific infringements, we will remove such
129 content immediately.
130 </Text>
131 </Box>
132
133 <Box className="space-y-4">
134 <Text as="h2" className="text-xl font-bold text-content">
135 8. Trademark notice
136 </Text>
137 <Text className="text-content-secondary leading-relaxed">
138 &quot;AlecRae&quot;, the AlecRae signature wordmark and the
139 tagline &quot;Email, considered.&quot; are trademarks of AlecRae,
140 Inc. Other product names are the property of their respective
141 owners.
142 </Text>
143 </Box>
144 </Box>
145 );
146}
Modifiedapps/web/app/(legal)/layout.tsx+11−2View fileUnifiedSplit
66const legalPages = [
77 { href: "/terms", label: "Terms of Service" },
88 { href: "/privacy", label: "Privacy Policy" },
9 { href: "/aup", label: "Acceptable Use Policy" },
9 { href: "/cookies", label: "Cookie Policy" },
10 { href: "/do-not-sell", label: "Do Not Sell or Share" },
11 { href: "/california-notice", label: "California Notice" },
12 { href: "/children", label: "Children's Privacy" },
13 { href: "/acceptable-use", label: "Acceptable Use Policy" },
1014 { href: "/dpa", label: "Data Processing Agreement" },
1115 { href: "/sla", label: "Service Level Agreement" },
1216 { href: "/dmca", label: "DMCA / Copyright Policy" },
13 { href: "/cookies", label: "Cookie Policy" },
1417 { href: "/subprocessors", label: "Subprocessors" },
18 { href: "/refund", label: "Refund Policy" },
19 { href: "/ai-transparency", label: "AI Transparency" },
20 { href: "/accessibility", label: "Accessibility" },
21 { href: "/security", label: "Security & Disclosure" },
22 { href: "/compliance", label: "Corporate Compliance" },
23 { href: "/impressum", label: "Impressum / Legal Notice" },
1524] as const;
1625
1726export default function LegalLayout({
Modifiedapps/web/app/(legal)/privacy/page.tsx+30−8View fileUnifiedSplit
9494
9595 <Sub label="(e) No Human Review.">Your email content is processed exclusively by automated systems. No AlecRae employee, contractor, or agent reads your emails. The only exceptions are: (i) when you voluntarily share email content with our support team for troubleshooting, (ii) when we are compelled by valid legal process (subpoena, court order, national security letter), or (iii) when investigating confirmed abuse reports, in which case review is limited to the specific content at issue.</Sub>
9696
97 <Sub label="(f) Automated Decision-Making.">Our AI systems make automated decisions that affect your experience, including: which emails appear in your inbox vs. spam folder, the priority order of your inbox, and whether outbound emails are flagged for compliance review. Under GDPR Article 22, you have the right to object to decisions based solely on automated processing that significantly affect you. To exercise this right, contact dpo@alecrae.dev. We will review any contested automated decision with human oversight within 5 business days.</Sub>
97 <Sub label="(f) Automated Decision-Making.">Our AI systems make automated decisions that affect your experience, including: which emails appear in your inbox vs. spam folder, the priority order of your inbox, and whether outbound emails are flagged for compliance review. Under GDPR Article 22, you have the right to object to decisions based solely on automated processing that significantly affect you. To exercise this right, contact dpo@alecrae.com. We will review any contested automated decision with human oversight within 5 business days.</Sub>
9898
9999 <Sub label="(g) Profiling.">Our AI creates profiles of your communication patterns, relationships, and writing style. These profiles are used solely to personalize your experience within the Service. We do not use these profiles for advertising, do not share them with third parties, and do not use them for automated decision-making that produces legal effects or similarly significant effects on you.</Sub>
100100 </Section>
169169 <Sub label="(g) Rights Related to Automated Decision-Making (Article 22).">You have the right not to be subject to decisions based solely on automated processing that significantly affect you, and to obtain human intervention, express your point of view, and contest the decision.</Sub>
170170 <Sub label="(h) Right to Withdraw Consent.">Where processing is based on consent, you may withdraw consent at any time without affecting the lawfulness of processing performed before withdrawal.</Sub>
171171 <Sub label="(i) Right to Lodge a Complaint.">You have the right to lodge a complaint with your local data protection supervisory authority.</Sub>
172 <Text className="mt-3">To exercise any of these rights, contact our Data Protection Officer at dpo@alecrae.dev. We will respond within 30 days. We may require identity verification before processing your request. Requests are fulfilled free of charge unless they are manifestly unfounded or excessive.</Text>
172 <Text className="mt-3">To exercise any of these rights, contact our Data Protection Officer at dpo@alecrae.com. We will respond within 30 days. We may require identity verification before processing your request. Requests are fulfilled free of charge unless they are manifestly unfounded or excessive.</Text>
173173 </Section>
174174
175175 <Section number="8" title="Your Rights Under CCPA (California Users)">
179179 <Sub label="(c) Right to Opt-Out of Sale.">We do not sell personal information. We have not sold personal information in the preceding 12 months.</Sub>
180180 <Sub label="(d) Right to Non-Discrimination.">We will not discriminate against you for exercising your CCPA rights. You will not receive different pricing, quality, or service levels.</Sub>
181181 <Sub label="(e) Authorized Agents.">You may designate an authorized agent to submit requests on your behalf with proper written authorization.</Sub>
182 <Text className="mt-3">To exercise your CCPA rights, email privacy@alecrae.dev or use the privacy controls in your account settings. We will respond within 45 days.</Text>
182 <Text className="mt-3">To exercise your CCPA rights, email privacy@alecrae.com or use the privacy controls in your account settings. We will respond within 45 days.</Text>
183183 </Section>
184184
185185 <Section number="9" title="International Data Transfers">
203203
204204 <Section number="11" title="Children&apos;s Privacy">
205205 <Text>The Service is not directed at children under the age of 13 (or 16 in the EEA). We do not knowingly collect personal information from children under these ages. If we discover that we have inadvertently collected personal information from a child under the applicable age, we will promptly delete it.</Text>
206 <Text>If you believe a child under the applicable age has provided personal information to us, please contact us immediately at privacy@alecrae.dev.</Text>
206 <Text>If you believe a child under the applicable age has provided personal information to us, please contact us immediately at privacy@alecrae.com.</Text>
207207 </Section>
208208
209209 <Section number="12" title="Cookies and Tracking">
220220 <Text>For privacy-related inquiries:</Text>
221221 <Box className="ml-6 mt-2 space-y-1">
222222 <Text className="font-semibold">Data Protection Officer</Text>
223 <Text>Email: dpo@alecrae.dev</Text>
223 <Text>Email: dpo@alecrae.com</Text>
224224 </Box>
225225 <Box className="ml-6 mt-3 space-y-1">
226226 <Text className="font-semibold">Privacy Team</Text>
227 <Text>Email: privacy@alecrae.dev</Text>
227 <Text>Email: privacy@alecrae.com</Text>
228228 </Box>
229229 <Box className="ml-6 mt-3 space-y-1">
230230 <Text className="font-semibold">Mailing Address</Text>
235235 <Text>United States</Text>
236236 </Box>
237237 <Box className="ml-6 mt-3 space-y-1">
238 <Text className="font-semibold">EU Representative</Text>
239 <Text>To be appointed. Contact dpo@alecrae.dev in the interim.</Text>
238 <Text className="font-semibold">EU Representative (GDPR Article 27)</Text>
239 <Text>
240 For data subjects located in the European Economic Area, our Article 27 representative is in the process of being appointed with a qualified pan-EU representation service. The representative&apos;s name, address and contact details will be published in this Privacy Policy and in our Impressum as soon as the appointment is finalised. In the interim, data subjects may exercise their rights by contacting <strong>dpo@alecrae.com</strong>, and we will treat that contact as constructive notice to the representative for the purposes of Article 27(4) GDPR.
241 </Text>
242 </Box>
243 <Box className="ml-6 mt-3 space-y-1">
244 <Text className="font-semibold">UK Representative (UK GDPR Article 27)</Text>
245 <Text>
246 For data subjects located in the United Kingdom, our Article 27 UK GDPR representative is in the process of being appointed. The representative&apos;s name, address and contact details will be published in this Privacy Policy and in our Impressum as soon as the appointment is finalised. In the interim, UK data subjects may exercise their rights by contacting <strong>dpo@alecrae.com</strong>, which will be treated as constructive notice to the representative.
247 </Text>
248 </Box>
249 <Box className="ml-6 mt-3 space-y-1">
250 <Text className="font-semibold">Right to lodge a complaint</Text>
251 <Text>
252 You always have the right to lodge a complaint with a supervisory authority, in particular in the Member State of your habitual residence, place of work or place of the alleged infringement:
253 </Text>
254 <Box className="ml-4 mt-2 space-y-1">
255 <Text>&bull; EU: see the list of national Data Protection Authorities at <Text as="span" className="font-semibold">edpb.europa.eu</Text>.</Text>
256 <Text>&bull; UK: Information Commissioner&apos;s Office (ICO) at <Text as="span" className="font-semibold">ico.org.uk</Text>, helpline 0303 123 1113.</Text>
257 <Text>&bull; California: California Privacy Protection Agency (CPPA) at <Text as="span" className="font-semibold">cppa.ca.gov</Text> or the Office of the Attorney General at <Text as="span" className="font-semibold">oag.ca.gov/privacy</Text>.</Text>
258 <Text>&bull; Canada: Office of the Privacy Commissioner of Canada (OPC) at <Text as="span" className="font-semibold">priv.gc.ca</Text>.</Text>
259 <Text>&bull; Australia: Office of the Australian Information Commissioner (OAIC) at <Text as="span" className="font-semibold">oaic.gov.au</Text>.</Text>
260 <Text>&bull; Brazil: Autoridade Nacional de Proteção de Dados (ANPD) at <Text as="span" className="font-semibold">www.gov.br/anpd</Text>.</Text>
261 </Box>
240262 </Box>
241263 </Section>
242264 </Box>
Addedapps/web/app/(legal)/refund/page.tsx+110−0View fileUnifiedSplit
1import type { Metadata } from "next";
2import { Box, Text, Card, CardContent } from "@alecrae/ui";
3
4export const metadata: Metadata = {
5 title: "Refund Policy | AlecRae",
6 description:
7 "AlecRae's refund policy, including 14-day money-back, EU / UK statutory withdrawal rights and the process for requesting a refund.",
8};
9
10export default function RefundPage() {
11 return (
12 <Box className="space-y-10">
13 <Box>
14 <Text as="h1" className="text-3xl font-bold text-content mb-2">
15 Refund Policy
16 </Text>
17 <Text className="text-content-tertiary">
18 Effective Date: April 16, 2026 &middot; Last Updated: April 16, 2026
19 </Text>
20 </Box>
21
22 <Text className="text-content-secondary leading-relaxed">
23 AlecRae wants every customer to feel confident trying the product.
24 This page sets out our refund commitments, which are in addition to
25 any statutory rights you may have (for example, the EU / UK 14-day
26 right of withdrawal for distance contracts).
27 </Text>
28
29 <Box className="space-y-4">
30 <Text as="h2" className="text-xl font-bold text-content">
31 1. 14-day money-back guarantee (monthly and annual)
32 </Text>
33 <Text className="text-content-secondary leading-relaxed">
34 New paid AlecRae subscriptions (Personal, Pro and Team) can be
35 refunded in full for any reason within <strong>14 days</strong> of
36 the first charge. There is no form to fill in — email
37 billing@alecrae.com or cancel from Settings &gt; Billing &gt;
38 Request refund. Refunds are processed to the original payment
39 method within 5-10 business days.
40 </Text>
41 </Box>
42
43 <Box className="space-y-4">
44 <Text as="h2" className="text-xl font-bold text-content">
45 2. Pro-rata refunds for annual plans
46 </Text>
47 <Text className="text-content-secondary leading-relaxed">
48 If you cancel an annual plan after the 14-day window, we will
49 refund the unused months pro-rata, less any free months that came
50 with the annual discount and any applicable third-party fees
51 already incurred on your behalf.
52 </Text>
53 </Box>
54
55 <Box className="space-y-4">
56 <Text as="h2" className="text-xl font-bold text-content">
57 3. EU / UK consumer rights
58 </Text>
59 <Text className="text-content-secondary leading-relaxed">
60 If you are a consumer resident in the European Economic Area,
61 Switzerland or the United Kingdom, you have a statutory right to
62 withdraw from your subscription within 14 days of purchase
63 without giving any reason. To exercise this right, make a clear
64 statement to billing@alecrae.com within 14 days. These rights
65 operate in parallel with, not instead of, our 14-day money-back
66 guarantee.
67 </Text>
68 </Box>
69
70 <Box className="space-y-4">
71 <Text as="h2" className="text-xl font-bold text-content">
72 4. When refunds are not available
73 </Text>
74 <Box className="ml-6 space-y-1 text-content-secondary">
75 <Text>&bull; Refunds beyond 14 days for monthly subscriptions where no specific statutory right applies.</Text>
76 <Text>&bull; Accounts suspended or terminated for violation of the Acceptable Use Policy.</Text>
77 <Text>&bull; Usage-based add-ons that have already been consumed (for example, API calls, sent email volume).</Text>
78 <Text>&bull; Enterprise contracts governed by a signed master service agreement — refunds follow that contract.</Text>
79 </Box>
80 </Box>
81
82 <Box className="space-y-4">
83 <Text as="h2" className="text-xl font-bold text-content">
84 5. Apple App Store / Google Play purchases
85 </Text>
86 <Text className="text-content-secondary leading-relaxed">
87 Subscriptions purchased through the Apple App Store or Google
88 Play must be refunded by the applicable store under their refund
89 policies (we cannot issue refunds for those transactions
90 ourselves). We will gladly help you navigate the process.
91 </Text>
92 </Box>
93
94 <Box className="space-y-4">
95 <Text as="h2" className="text-xl font-bold text-content">
96 6. How to request a refund
97 </Text>
98 <Card className="bg-surface-subtle">
99 <CardContent>
100 <Box className="space-y-2">
101 <Text className="text-content">Email: billing@alecrae.com</Text>
102 <Text className="text-content">In-product: Settings &gt; Billing &gt; Request refund</Text>
103 <Text className="text-content">Include: the email address on the account, and the date of the charge.</Text>
104 </Box>
105 </CardContent>
106 </Card>
107 </Box>
108 </Box>
109 );
110}
Addedapps/web/app/(legal)/security/page.tsx+155−0View fileUnifiedSplit
1import type { Metadata } from "next";
2import { Box, Text, Card, CardContent } from "@alecrae/ui";
3
4export const metadata: Metadata = {
5 title: "Security & Responsible Disclosure | AlecRae",
6 description:
7 "AlecRae's security program, vulnerability disclosure policy, safe harbour terms, bug bounty rewards, and in-scope assets.",
8};
9
10export default function SecurityPage() {
11 return (
12 <Box className="space-y-10">
13 <Box>
14 <Text as="h1" className="text-3xl font-bold text-content mb-2">
15 Security &amp; Responsible Disclosure
16 </Text>
17 <Text className="text-content-tertiary">
18 Effective Date: April 16, 2026 &middot; Last Updated: April 16, 2026
19 </Text>
20 </Box>
21
22 <Card className="border-blue-500/30 bg-blue-500/5">
23 <CardContent>
24 <Box className="p-2 space-y-2">
25 <Text as="h2" className="text-lg font-semibold text-blue-700">
26 Found something? Tell us.
27 </Text>
28 <Text className="text-content-secondary leading-relaxed">
29 Email <a className="text-brand-600 hover:underline" href="mailto:security@alecrae.com">security@alecrae.com</a>.
30 We acknowledge within 2 business days. Good-faith research is
31 welcome and protected by the safe-harbour terms below.
32 </Text>
33 </Box>
34 </CardContent>
35 </Card>
36
37 <Box className="space-y-4">
38 <Text as="h2" className="text-xl font-bold text-content">
39 1. Scope
40 </Text>
41 <Text className="text-content-secondary leading-relaxed">
42 The following assets are in scope for our bug bounty and
43 responsible disclosure program:
44 </Text>
45 <Box className="ml-6 space-y-1 text-content-secondary">
46 <Text>&bull; alecrae.com and all production and public-staging subdomains.</Text>
47 <Text>&bull; mail.alecrae.com (web application).</Text>
48 <Text>&bull; api.alecrae.com (public API).</Text>
49 <Text>&bull; admin.alecrae.com (admin console and SSO).</Text>
50 <Text>&bull; mx1 / mx2 / smtp.alecrae.com (email transfer agents).</Text>
51 <Text>&bull; The AlecRae mobile app (iOS + Android store builds).</Text>
52 <Text>&bull; The AlecRae desktop app (signed release builds).</Text>
53 </Box>
54 </Box>
55
56 <Box className="space-y-4">
57 <Text as="h2" className="text-xl font-bold text-content">
58 2. Out of scope
59 </Text>
60 <Box className="ml-6 space-y-1 text-content-secondary">
61 <Text>&bull; Denial-of-service and volumetric attacks.</Text>
62 <Text>&bull; Social engineering of employees, contractors or users.</Text>
63 <Text>&bull; Physical attacks against our premises or staff.</Text>
64 <Text>&bull; Issues that require a privileged network position.</Text>
65 <Text>&bull; Third-party services we do not operate (Stripe, Neon, Cloudflare, etc.).</Text>
66 <Text>&bull; Missing best-practice headers without a demonstrated exploit.</Text>
67 <Text>&bull; Self-XSS or clickjacking without security impact.</Text>
68 <Text>&bull; Vulnerabilities in third-party libraries without a demonstrated exploit against AlecRae.</Text>
69 </Box>
70 </Box>
71
72 <Box className="space-y-4">
73 <Text as="h2" className="text-xl font-bold text-content">
74 3. Safe harbour
75 </Text>
76 <Text className="text-content-secondary leading-relaxed">
77 AlecRae authorises security research conducted in accordance with
78 this policy. We will not pursue civil or criminal action under the
79 Computer Fraud and Abuse Act (CFAA), the Digital Millennium
80 Copyright Act (DMCA), the UK Computer Misuse Act, the EU
81 Cybersecurity Directive, or any equivalent law, provided you:
82 </Text>
83 <Box className="ml-6 space-y-1 text-content-secondary">
84 <Text>&bull; Make a good-faith effort to avoid privacy violations, data destruction and service degradation.</Text>
85 <Text>&bull; Only interact with accounts you own or have explicit permission to access.</Text>
86 <Text>&bull; Do not exfiltrate data beyond the minimum needed to demonstrate impact.</Text>
87 <Text>&bull; Give us a reasonable time to fix before public disclosure (target: 90 days).</Text>
88 <Text>&bull; Do not extort, threaten, or demand payment outside this programme.</Text>
89 </Box>
90 </Box>
91
92 <Box className="space-y-4">
93 <Text as="h2" className="text-xl font-bold text-content">
94 4. Reward scale
95 </Text>
96 <Text className="text-content-secondary leading-relaxed">
97 Rewards are determined by CVSS 3.1 severity, exploitability, and
98 user-blast radius. Indicative ranges (USD):
99 </Text>
100 <Card className="bg-surface-subtle">
101 <CardContent>
102 <Box className="space-y-2">
103 <Text className="text-content font-semibold">Critical (CVSS 9.0-10.0): $5,000 - $25,000</Text>
104 <Text className="text-content font-semibold">High (CVSS 7.0-8.9): $1,500 - $5,000</Text>
105 <Text className="text-content font-semibold">Medium (CVSS 4.0-6.9): $300 - $1,500</Text>
106 <Text className="text-content font-semibold">Low (CVSS 0.1-3.9): $100 - $300</Text>
107 <Text className="text-content text-sm mt-3">
108 Exceptional reports (novel classes of bug, RCE on production,
109 full account takeover without user interaction) may be
110 rewarded beyond the top of this range.
111 </Text>
112 </Box>
113 </CardContent>
114 </Card>
115 </Box>
116
117 <Box className="space-y-4">
118 <Text as="h2" className="text-xl font-bold text-content">
119 5. Disclosure timeline
120 </Text>
121 <Box className="ml-6 space-y-1 text-content-secondary">
122 <Text>&bull; Acknowledge within 2 business days.</Text>
123 <Text>&bull; Triage result within 5 business days.</Text>
124 <Text>&bull; Critical fix target: 30 days. High: 60 days. Medium / Low: 90 days.</Text>
125 <Text>&bull; Coordinated public disclosure upon remediation or, at latest, 90 days after acknowledgment unless mutually agreed otherwise.</Text>
126 </Box>
127 </Box>
128
129 <Box className="space-y-4">
130 <Text as="h2" className="text-xl font-bold text-content">
131 6. Hall of fame
132 </Text>
133 <Text className="text-content-secondary leading-relaxed">
134 With your permission we will credit you on our Hall of Fame at
135 alecrae.com/security/hall-of-fame. You may request to remain
136 anonymous.
137 </Text>
138 </Box>
139
140 <Box className="space-y-4">
141 <Text as="h2" className="text-xl font-bold text-content">
142 7. PGP / encrypted reporting
143 </Text>
144 <Text className="text-content-secondary leading-relaxed">
145 Our active PGP fingerprint is published at{" "}
146 <a className="text-brand-600 hover:underline" href="/.well-known/security.txt">
147 /.well-known/security.txt
148 </a>
149 . We also accept encrypted reports via Signal; request a session
150 key by emailing security@alecrae.com.
151 </Text>
152 </Box>
153 </Box>
154 );
155}
Modifiedapps/web/app/(legal)/sla/page.tsx+3−3View fileUnifiedSplit
404404 <Text variant="body-md" className="text-brand-600 font-bold">a.</Text>
405405 <Text variant="body-md" className="text-content-secondary leading-relaxed">
406406 <Text as="span" className="font-semibold text-content">Real-Time Status Page.</Text>{" "}
407 A publicly accessible status page at status.alecrae.dev displaying
407 A publicly accessible status page at status.alecrae.com displaying
408408 current operational status of all services, updated in real time.
409409 </Text>
410410 </Box>
466466 </Box>
467467 </Box>
468468 <Text variant="body-md" className="text-content-secondary leading-relaxed">
469 Requests may be submitted by emailing sla@alecrae.dev or through the support
469 Requests may be submitted by emailing sla@alecrae.com or through the support
470470 portal. AlecRae will review and respond to credit requests within 10 business
471471 days. If we confirm the SLA failure, credits will be applied to your next
472472 billing cycle.
506506
507507 <Box className="border-t border-border pt-6">
508508 <Text variant="body-sm" muted>
509 Questions about this SLA? Contact us at legal@alecrae.dev or visit our
509 Questions about this SLA? Contact us at legal@alecrae.com or visit our
510510 support portal.
511511 </Text>
512512 </Box>
Modifiedapps/web/app/(legal)/subprocessors/page.tsx+2−2View fileUnifiedSplit
232232 <Text as="span" className="font-semibold text-content">Objection Window.</Text>{" "}
233233 You must submit your objection in writing within 30 calendar days of
234234 receiving the change notification. Objections must be sent to
235 dpa@alecrae.dev and must include a detailed explanation of the specific,
235 dpa@alecrae.com and must include a detailed explanation of the specific,
236236 reasonable grounds for the objection based on data protection concerns.
237237 </Text>
238238 </Box>
329329 </Text>
330330 <Text variant="body-sm" muted>
331331 For questions about our Subprocessors or data processing practices,
332 contact our Data Protection Officer at dpo@alecrae.dev.
332 contact our Data Protection Officer at dpo@alecrae.com.
333333 </Text>
334334 </Box>
335335 </Box>
Modifiedapps/web/app/(legal)/terms/page.tsx+7−7View fileUnifiedSplit
5959
6060 <Section number="3" title="Account Registration and Security">
6161 <Text>You must provide accurate, current, and complete information during registration and maintain the accuracy of such information. Each account is for a single individual or entity; credential sharing is prohibited.</Text>
62 <Text>You are solely responsible for maintaining the confidentiality of your account credentials, including passwords, API keys, and passkeys. You must immediately notify AlecRae at security@alecrae.dev of any unauthorized access to or use of your account.</Text>
62 <Text>You are solely responsible for maintaining the confidentiality of your account credentials, including passwords, API keys, and passkeys. You must immediately notify AlecRae at security@alecrae.com of any unauthorized access to or use of your account.</Text>
6363 <Text>AlecRae reserves the right to suspend or terminate accounts that provide false information, are used for fraudulent purposes, or violate these Terms.</Text>
6464 </Section>
6565
180180 <Text className="text-sm text-amber-700">This section contains a binding arbitration clause and class action waiver.</Text>
181181 </CardContent>
182182 </Card>
183 <Sub label="(a) Informal Resolution.">Before filing any formal dispute, you must contact AlecRae at legal@alecrae.dev and attempt to resolve the dispute informally for at least 30 days. Most disputes can be resolved without formal proceedings.</Sub>
183 <Sub label="(a) Informal Resolution.">Before filing any formal dispute, you must contact AlecRae at legal@alecrae.com and attempt to resolve the dispute informally for at least 30 days. Most disputes can be resolved without formal proceedings.</Sub>
184184 <Sub label="(b) Binding Arbitration.">Any dispute, controversy, or claim arising out of or relating to these Terms or the Service that cannot be resolved informally shall be resolved by binding arbitration administered by the American Arbitration Association (&quot;AAA&quot;) under its Commercial Arbitration Rules. The arbitration shall be conducted in San Francisco, California, by a single arbitrator selected in accordance with AAA rules.</Sub>
185185 <Sub label="(c) Class Action Waiver.">YOU AND EMAILED AGREE THAT EACH PARTY MAY BRING CLAIMS AGAINST THE OTHER ONLY IN YOUR OR ITS INDIVIDUAL CAPACITY AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS, CONSOLIDATED, OR REPRESENTATIVE ACTION. The arbitrator may not consolidate more than one person&apos;s claims and may not preside over any form of representative or class proceeding.</Sub>
186186 <Sub label="(d) Exceptions.">Notwithstanding the above, either party may (i) bring an individual action in small claims court for disputes within that court&apos;s jurisdiction, or (ii) seek injunctive or other equitable relief in any court of competent jurisdiction to prevent the actual or threatened infringement of intellectual property rights.</Sub>
187187 <Sub label="(e) Costs.">Each party shall bear its own costs and attorneys&apos; fees in arbitration. If the arbitrator finds that the substance of your claim or the relief sought is frivolous or brought for an improper purpose, the arbitrator may award costs and fees to AlecRae.</Sub>
188 <Sub label="(f) Opt-Out.">You may opt out of this arbitration agreement by sending written notice to legal@alecrae.dev within 30 days of first accepting these Terms. Your notice must include your name, account email, and a clear statement that you wish to opt out of arbitration.</Sub>
188 <Sub label="(f) Opt-Out.">You may opt out of this arbitration agreement by sending written notice to legal@alecrae.com within 30 days of first accepting these Terms. Your notice must include your name, account email, and a clear statement that you wish to opt out of arbitration.</Sub>
189189 </Section>
190190
191191 <Section number="16" title="Governing Law">
193193 </Section>
194194
195195 <Section number="17" title="Termination">
196 <Sub label="(a) By You.">You may terminate your account at any time through your account settings or by contacting support@alecrae.dev. Upon termination, you will have a 30-day window to export your data.</Sub>
196 <Sub label="(a) By You.">You may terminate your account at any time through your account settings or by contacting support@alecrae.com. Upon termination, you will have a 30-day window to export your data.</Sub>
197197 <Sub label="(b) By AlecRae for Cause.">AlecRae may suspend or terminate your account immediately upon notice if you breach these Terms, violate the Acceptable Use Policy, fail to pay fees when due, or engage in activity that threatens the security or reputation of the platform.</Sub>
198198 <Sub label="(c) By AlecRae Without Cause.">AlecRae may terminate your account without cause by providing 90 days&apos; advance written notice. In such case, you will receive a pro-rata refund of any prepaid fees for the unused portion of your subscription.</Sub>
199199 <Sub label="(d) Effect of Termination.">Upon termination, your right to use the Service immediately ceases (subject to any data export window). AlecRae will delete your Content within 90 days of termination, except as required for backup retention, legal compliance, or dispute resolution. Sections 6(b), 11, 12, 13, 14, 15, 16, and 25 survive termination.</Sub>
241241 <Text>AlecRae, Inc.</Text>
242242 <Text>548 Market Street, Suite 45000</Text>
243243 <Text>San Francisco, CA 94104</Text>
244 <Text>Legal inquiries: legal@alecrae.dev</Text>
245 <Text>General support: support@alecrae.dev</Text>
246 <Text>Security issues: security@alecrae.dev</Text>
244 <Text>Legal inquiries: legal@alecrae.com</Text>
245 <Text>General support: support@alecrae.com</Text>
246 <Text>Security issues: security@alecrae.com</Text>
247247 </Box>
248248 </Section>
249249 </Box>
Modifiedapps/web/app/layout.tsx+2−0View fileUnifiedSplit
11import type { Metadata, Viewport } from "next";
22import { Italianno, Inter } from "next/font/google";
33import "./globals.css";
4import { ConsentBanner } from "../components/ConsentBanner";
45
56/**
67 * Italianno — the signature-style handwritten script used for the AlecRae wordmark.
7374 >
7475 <body className="h-full bg-[#f5f4ef] text-neutral-900 font-sans">
7576 {children}
77 <ConsentBanner />
7678 </body>
7779 </html>
7880 );
Modifiedapps/web/app/page.tsx+32−35View fileUnifiedSplit
22 * AlecRaeComing Soon Landing Page
33 *
44 * Apple-minimal. Ivory background. Handwritten signature wordmark.
5 * One signature, one hairline rule, one tagline. Nothing else.
6 * The brand promise: people using AlecRae look sophisticated.
7 * Every design decision serves that feeling.
5 * One signature, one hairline rule, one tagline. Nothing else above
6 * the fold. Legal footer is rendered below the viewport so it's there
7 * for regulators and curious visitors, but doesn't break the brand feel.
88 */
99
10import { LegalFooter } from "../components/LegalFooter";
11
1012export default function ComingSoonPage() {
1113 return (
12 <main className="min-h-screen flex flex-col items-center justify-center bg-[#f5f4ef] text-neutral-900 px-6 relative">
13 {/* Signature wordmark — handwritten script */}
14 <div className="flex flex-col items-center text-center">
15 <h1
16 className="text-[6rem] sm:text-[9rem] md:text-[13rem] lg:text-[15rem] leading-[0.85] text-neutral-900 select-none"
17 style={{
18 fontFamily: "var(--font-italianno), 'Snell Roundhand', 'Apple Chancery', cursive",
19 fontWeight: 400,
20 letterSpacing: "-0.01em",
21 }}
22 >
23 AlecRae
24 </h1>
25
26 {/* Hairline flourish */}
27 <div className="mt-2 mb-10 w-48 md:w-64 h-px bg-neutral-400/50" aria-hidden="true" />
14 <>
15 <main className="min-h-screen flex flex-col items-center justify-center bg-[#f5f4ef] text-neutral-900 px-6 relative">
16 {/* Signature wordmark — handwritten script */}
17 <div className="flex flex-col items-center text-center">
18 <h1
19 className="text-[6rem] sm:text-[9rem] md:text-[13rem] lg:text-[15rem] leading-[0.85] text-neutral-900 select-none"
20 style={{
21 fontFamily: "var(--font-italianno), 'Snell Roundhand', 'Apple Chancery', cursive",
22 fontWeight: 400,
23 letterSpacing: "-0.01em",
24 }}
25 >
26 AlecRae
27 </h1>
2828
29 {/* Tagline — clean sans */}
30 <p
31 className="text-sm md:text-base text-neutral-600 font-light tracking-[0.2em]"
32 style={{ fontFamily: "var(--font-inter), system-ui, sans-serif" }}
33 >
34 Email, considered.
35 </p>
36 </div>
29 {/* Hairline flourish */}
30 <div className="mt-2 mb-10 w-48 md:w-64 h-px bg-neutral-400/50" aria-hidden="true" />
3731
38 {/* Ultra-minimal footer */}
39 <footer
40 className="absolute bottom-6 left-0 right-0 text-center text-[10px] text-neutral-500/70 tracking-[0.25em] uppercase"
41 style={{ fontFamily: "var(--font-inter), system-ui, sans-serif" }}
42 >
43 © 2026 AlecRae
44 </footer>
45 </main>
32 {/* Tagline — clean sans */}
33 <p
34 className="text-sm md:text-base text-neutral-600 font-light tracking-[0.2em]"
35 style={{ fontFamily: "var(--font-inter), system-ui, sans-serif" }}
36 >
37 Email, considered.
38 </p>
39 </div>
40 </main>
41 <LegalFooter />
42 </>
4643 );
4744}
Modifiedapps/web/app/robots.ts+12−2View fileUnifiedSplit
55 rules: [
66 {
77 userAgent: "*",
8 allow: "/",
9 disallow: ["/inbox", "/compose", "/settings", "/analytics", "/domains", "/login", "/register", "/api/"],
8 allow: ["/", "/.well-known/"],
9 disallow: [
10 "/inbox",
11 "/compose",
12 "/settings",
13 "/analytics",
14 "/domains",
15 "/login",
16 "/register",
17 "/api/",
18 ],
1019 },
1120 ],
1221 sitemap: "https://alecrae.com/sitemap.xml",
22 host: "https://alecrae.com",
1323 };
1424}
Modifiedapps/web/app/sitemap.ts+28−48View fileUnifiedSplit
11import type { MetadataRoute } from "next";
22
3const LEGAL_LAST_UPDATED = new Date("2026-04-16");
4
5const legalRoutes: ReadonlyArray<string> = [
6 "/terms",
7 "/privacy",
8 "/cookies",
9 "/do-not-sell",
10 "/california-notice",
11 "/children",
12 "/acceptable-use",
13 "/dpa",
14 "/sla",
15 "/dmca",
16 "/subprocessors",
17 "/refund",
18 "/ai-transparency",
19 "/accessibility",
20 "/security",
21 "/compliance",
22 "/impressum",
23];
24
325export default function sitemap(): MetadataRoute.Sitemap {
426 const baseUrl = "https://alecrae.com";
527
1032 changeFrequency: "weekly",
1133 priority: 1,
1234 },
13 {
14 url: `${baseUrl}/terms`,
15 lastModified: new Date("2026-04-12"),
16 changeFrequency: "monthly",
17 priority: 0.5,
18 },
19 {
20 url: `${baseUrl}/privacy`,
21 lastModified: new Date("2026-04-12"),
22 changeFrequency: "monthly",
23 priority: 0.5,
24 },
25 {
26 url: `${baseUrl}/acceptable-use`,
27 lastModified: new Date("2026-04-12"),
28 changeFrequency: "monthly",
29 priority: 0.3,
30 },
31 {
32 url: `${baseUrl}/dpa`,
33 lastModified: new Date("2026-04-12"),
34 changeFrequency: "monthly",
35 priority: 0.3,
36 },
37 {
38 url: `${baseUrl}/sla`,
39 lastModified: new Date("2026-04-12"),
40 changeFrequency: "monthly",
41 priority: 0.3,
42 },
43 {
44 url: `${baseUrl}/dmca`,
45 lastModified: new Date("2026-04-12"),
46 changeFrequency: "monthly",
47 priority: 0.3,
48 },
49 {
50 url: `${baseUrl}/cookies`,
51 lastModified: new Date("2026-04-12"),
52 changeFrequency: "monthly",
53 priority: 0.3,
54 },
55 {
56 url: `${baseUrl}/subprocessors`,
57 lastModified: new Date("2026-04-12"),
58 changeFrequency: "monthly",
59 priority: 0.3,
60 },
35 ...legalRoutes.map((path) => ({
36 url: `${baseUrl}${path}`,
37 lastModified: LEGAL_LAST_UPDATED,
38 changeFrequency: "monthly" as const,
39 priority: 0.4,
40 })),
6141 ];
6242}
Addedapps/web/components/ConsentBanner.tsx+306−0View fileUnifiedSplit
1"use client";
2
3/**
4 * AlecRae Cookie / Tracking-Technology Consent Banner.
5 *
6 * Requirements met:
7 * - GDPR Article 7 affirmative, granular, revocable consent
8 * - ePrivacy Directive — no non-essential cookies fired before consent
9 * - CPRA (California) — "Do Not Sell or Share" link always present
10 * - Respects Global Privacy Control (GPC) and legacy DNT signals
11 * - Keyboard accessible, screen-reader friendly, WCAG 2.2 AA colour contrast
12 * - No dark patterns — "Reject all" is equally prominent as "Accept all"
13 */
14
15import { useCallback, useEffect, useState } from "react";
16import {
17 buildConsentRecord,
18 CONSENT_CHANGED_EVENT,
19 type ConsentCategory,
20 type ConsentRecord,
21 detectDNTSignal,
22 detectGPCSignal,
23 loadConsent,
24 saveConsent,
25} from "../lib/consent";
26
27interface ToggleRowProps {
28 readonly id: ConsentCategory;
29 readonly label: string;
30 readonly description: string;
31 readonly required?: boolean;
32 readonly checked: boolean;
33 readonly onToggle: (next: boolean) => void;
34}
35
36function ToggleRow({ id, label, description, required, checked, onToggle }: ToggleRowProps) {
37 const switchId = `consent-toggle-${id}`;
38 const descId = `${switchId}-desc`;
39 return (
40 <div className="flex items-start justify-between gap-4 py-4 border-b border-neutral-200 last:border-b-0">
41 <div className="flex-1 min-w-0">
42 <label htmlFor={switchId} className="block text-sm font-semibold text-neutral-900">
43 {label}
44 {required ? (
45 <span className="ml-2 text-[10px] uppercase tracking-wider text-neutral-500">
46 Always active
47 </span>
48 ) : null}
49 </label>
50 <p id={descId} className="mt-1 text-xs text-neutral-600 leading-relaxed">
51 {description}
52 </p>
53 </div>
54 <button
55 id={switchId}
56 type="button"
57 role="switch"
58 aria-checked={checked}
59 aria-describedby={descId}
60 aria-disabled={required ? true : undefined}
61 disabled={required}
62 onClick={() => !required && onToggle(!checked)}
63 className={[
64 "relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors",
65 "focus:outline-none focus-visible:ring-2 focus-visible:ring-neutral-900 focus-visible:ring-offset-2",
66 checked ? "bg-neutral-900" : "bg-neutral-300",
67 required ? "cursor-not-allowed opacity-70" : "cursor-pointer",
68 ].join(" ")}
69 >
70 <span
71 className={[
72 "inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform",
73 checked ? "translate-x-5" : "translate-x-0.5",
74 ].join(" ")}
75 />
76 </button>
77 </div>
78 );
79}
80
81export function ConsentBanner(): React.ReactElement | null {
82 const [mounted, setMounted] = useState(false);
83 const [visible, setVisible] = useState(false);
84 const [showPreferences, setShowPreferences] = useState(false);
85 const [record, setRecord] = useState<ConsentRecord | null>(null);
86 const [selections, setSelections] = useState<Record<ConsentCategory, boolean>>({
87 necessary: true,
88 functional: false,
89 analytics: false,
90 performance: false,
91 });
92
93 // Initial hydration: decide whether to show the banner.
94 useEffect(() => {
95 setMounted(true);
96
97 const existing = loadConsent();
98 if (existing) {
99 setRecord(existing);
100 setSelections(existing.categories);
101 setVisible(false);
102 return;
103 }
104
105 // Respect GPC / DNT as automatic "reject non-essential" signals.
106 if (detectGPCSignal() || detectDNTSignal()) {
107 const auto = buildConsentRecord(
108 { functional: false, analytics: false, performance: false },
109 true,
110 );
111 saveConsent(auto);
112 setRecord(auto);
113 setSelections(auto.categories);
114 setVisible(false);
115 return;
116 }
117
118 setVisible(true);
119 }, []);
120
121 // Respond to "Manage preferences" links from anywhere else on the site.
122 useEffect(() => {
123 function onOpen(): void {
124 setShowPreferences(true);
125 setVisible(true);
126 }
127 if (typeof window !== "undefined") {
128 window.addEventListener("alecrae:open-consent", onOpen);
129 return () => window.removeEventListener("alecrae:open-consent", onOpen);
130 }
131 return undefined;
132 }, []);
133
134 const persist = useCallback((categories: Record<ConsentCategory, boolean>) => {
135 const next = buildConsentRecord(categories, false);
136 saveConsent(next);
137 setRecord(next);
138 setSelections(next.categories);
139 setVisible(false);
140 setShowPreferences(false);
141 }, []);
142
143 const acceptAll = useCallback(() => {
144 persist({ necessary: true, functional: true, analytics: true, performance: true });
145 }, [persist]);
146
147 const rejectAll = useCallback(() => {
148 persist({ necessary: true, functional: false, analytics: false, performance: false });
149 }, [persist]);
150
151 const saveSelections = useCallback(() => {
152 persist(selections);
153 }, [persist, selections]);
154
155 if (!mounted || !visible) return null;
156
157 return (
158 <div
159 role="dialog"
160 aria-modal="false"
161 aria-labelledby="consent-banner-title"
162 aria-describedby="consent-banner-desc"
163 className="fixed inset-x-4 bottom-4 z-[100] mx-auto max-w-3xl rounded-xl border border-neutral-200 bg-white shadow-2xl"
164 >
165 <div className="p-6">
166 <div className="flex items-start justify-between gap-4 mb-4">
167 <div>
168 <h2 id="consent-banner-title" className="text-base font-semibold text-neutral-900">
169 Your privacy, your choice.
170 </h2>
171 <p id="consent-banner-desc" className="mt-2 text-sm text-neutral-700 leading-relaxed">
172 AlecRae uses strictly necessary cookies to keep the site secure and working.
173 With your permission we may also use a small number of functional, analytics,
174 and performance cookies. We never use advertising cookies, we never share
175 your data with brokers, and we never sell personal information. You can
176 change your choices at any time from the footer link.
177 </p>
178 </div>
179 </div>
180
181 {showPreferences ? (
182 <div className="mt-2 border-t border-neutral-200 pt-2">
183 <ToggleRow
184 id="necessary"
185 label="Strictly necessary"
186 description="Session cookies, CSRF protection, authentication. Required for the site to function. Cannot be disabled."
187 required
188 checked
189 onToggle={() => undefined}
190 />
191 <ToggleRow
192 id="functional"
193 label="Functional"
194 description="Remember language, theme, density and other preferences you explicitly set in the UI."
195 checked={selections.functional}
196 onToggle={(v) => setSelections((s) => ({ ...s, functional: v }))}
197 />
198 <ToggleRow
199 id="analytics"
200 label="Analytics"
201 description="Aggregate, privacy-preserving measurement of page views and feature usage. No third-party trackers. No cross-site identifiers."
202 checked={selections.analytics}
203 onToggle={(v) => setSelections((s) => ({ ...s, analytics: v }))}
204 />
205 <ToggleRow
206 id="performance"
207 label="Performance"
208 description="Real-user performance monitoring (Web Vitals) to catch regressions. Never includes personal content."
209 checked={selections.performance}
210 onToggle={(v) => setSelections((s) => ({ ...s, performance: v }))}
211 />
212 </div>
213 ) : null}
214
215 <div className="mt-5 flex flex-col sm:flex-row gap-2 sm:gap-3 sm:items-center sm:justify-between">
216 <div className="flex flex-wrap gap-2">
217 <a
218 href="/cookies"
219 className="text-xs font-medium text-neutral-700 underline underline-offset-2 hover:text-neutral-900"
220 >
221 Cookie policy
222 </a>
223 <span aria-hidden="true" className="text-neutral-400 text-xs">·</span>
224 <a
225 href="/privacy"
226 className="text-xs font-medium text-neutral-700 underline underline-offset-2 hover:text-neutral-900"
227 >
228 Privacy policy
229 </a>
230 <span aria-hidden="true" className="text-neutral-400 text-xs">·</span>
231 <a
232 href="/do-not-sell"
233 className="text-xs font-medium text-neutral-700 underline underline-offset-2 hover:text-neutral-900"
234 >
235 Do Not Sell or Share
236 </a>
237 </div>
238
239 <div className="flex flex-wrap gap-2 justify-end">
240 {!showPreferences ? (
241 <button
242 type="button"
243 onClick={() => setShowPreferences(true)}
244 className="inline-flex items-center justify-center rounded-md border border-neutral-300 bg-white px-4 py-2 text-sm font-medium text-neutral-900 hover:bg-neutral-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-neutral-900 focus-visible:ring-offset-2"
245 >
246 Manage preferences
247 </button>
248 ) : (
249 <button
250 type="button"
251 onClick={saveSelections}
252 className="inline-flex items-center justify-center rounded-md border border-neutral-300 bg-white px-4 py-2 text-sm font-medium text-neutral-900 hover:bg-neutral-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-neutral-900 focus-visible:ring-offset-2"
253 >
254 Save my choices
255 </button>
256 )}
257 <button
258 type="button"
259 onClick={rejectAll}
260 className="inline-flex items-center justify-center rounded-md border border-neutral-300 bg-white px-4 py-2 text-sm font-medium text-neutral-900 hover:bg-neutral-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-neutral-900 focus-visible:ring-offset-2"
261 >
262 Reject non-essential
263 </button>
264 <button
265 type="button"
266 onClick={acceptAll}
267 className="inline-flex items-center justify-center rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-white hover:bg-neutral-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-neutral-900 focus-visible:ring-offset-2"
268 >
269 Accept all
270 </button>
271 </div>
272 </div>
273
274 {record?.autoAppliedFromSignal ? (
275 <p className="mt-4 text-[11px] text-neutral-500 leading-relaxed">
276 We detected a Global Privacy Control or Do Not Track signal and applied it automatically.
277 </p>
278 ) : null}
279 </div>
280 </div>
281 );
282}
283
284ConsentBanner.displayName = "ConsentBanner";
285
286/**
287 * Re-open the consent preferences from any link / button with
288 * `data-open-consent` or by dispatching `alecrae:open-consent`.
289 */
290export function openConsentPreferences(): void {
291 if (typeof window === "undefined") return;
292 window.dispatchEvent(new CustomEvent("alecrae:open-consent"));
293}
294
295/** Hook for other client components that need to react to consent changes. */
296export function subscribeToConsentChanges(
297 cb: (record: ConsentRecord | null) => void,
298): () => void {
299 if (typeof window === "undefined") return () => undefined;
300 const listener = (e: Event): void => {
301 const detail = (e as CustomEvent<ConsentRecord | null>).detail ?? null;
302 cb(detail);
303 };
304 window.addEventListener(CONSENT_CHANGED_EVENT, listener);
305 return () => window.removeEventListener(CONSENT_CHANGED_EVENT, listener);
306}
Addedapps/web/components/LegalFooter.tsx+90−0View fileUnifiedSplit
1"use client";
2
3/**
4 * AlecRae — Global legal footer.
5 *
6 * Renders on every public page. Links to every legal surface we publish
7 * so a visitor is never more than one click from our terms, privacy
8 * policy, security policy, or CPRA opt-out. Also exposes a button to
9 * re-open the cookie consent preferences at any time, which is a
10 * regulatory requirement under the ePrivacy Directive and CPRA.
11 */
12
13import { openConsentPreferences } from "./ConsentBanner";
14
15const primaryLinks: ReadonlyArray<{ href: string; label: string }> = [
16 { href: "/terms", label: "Terms" },
17 { href: "/privacy", label: "Privacy" },
18 { href: "/cookies", label: "Cookies" },
19 { href: "/do-not-sell", label: "Do Not Sell or Share" },
20 { href: "/accessibility", label: "Accessibility" },
21 { href: "/security", label: "Security" },
22 { href: "/dmca", label: "DMCA" },
23 { href: "/acceptable-use", label: "Acceptable Use" },
24 { href: "/dpa", label: "DPA" },
25 { href: "/sla", label: "SLA" },
26 { href: "/subprocessors", label: "Subprocessors" },
27 { href: "/refund", label: "Refunds" },
28 { href: "/ai-transparency", label: "AI Transparency" },
29 { href: "/children", label: "Children's Privacy" },
30 { href: "/california-notice", label: "California Notice" },
31 { href: "/compliance", label: "Compliance" },
32 { href: "/impressum", label: "Impressum" },
33];
34
35export function LegalFooter(): React.ReactElement {
36 return (
37 <footer
38 role="contentinfo"
39 className="border-t border-neutral-200 bg-[#f5f4ef] text-neutral-700"
40 style={{ fontFamily: "var(--font-inter), system-ui, sans-serif" }}
41 >
42 <div className="max-w-6xl mx-auto px-6 py-10">
43 <div className="flex flex-col gap-6">
44 <div className="flex flex-wrap gap-x-5 gap-y-2 text-xs">
45 {primaryLinks.map((link) => (
46 <a
47 key={link.href}
48 href={link.href}
49 className="text-neutral-600 hover:text-neutral-900 transition-colors"
50 >
51 {link.label}
52 </a>
53 ))}
54 <button
55 type="button"
56 onClick={openConsentPreferences}
57 className="text-neutral-600 hover:text-neutral-900 underline underline-offset-2 transition-colors"
58 >
59 Cookie preferences
60 </button>
61 </div>
62
63 <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 text-[11px] text-neutral-500 tracking-wide">
64 <div className="space-y-1">
65 <p>
66 &copy; 2026 AlecRae, Inc. All rights reserved.
67 AlecRae&trade; is a trademark of AlecRae, Inc.
68 </p>
69 <p>
70 548 Market Street, Suite 45000, San Francisco, CA 94104, USA &middot;
71 <a className="ml-1 hover:text-neutral-900 underline underline-offset-2" href="mailto:privacy@alecrae.com">
72 privacy@alecrae.com
73 </a>
74 </p>
75 </div>
76 <div className="flex flex-wrap gap-3">
77 <a href="/status" className="hover:text-neutral-900">System status</a>
78 <a href="/changelog" className="hover:text-neutral-900">Changelog</a>
79 <a href="https://docs.alecrae.com" className="hover:text-neutral-900" rel="noopener noreferrer">
80 Docs
81 </a>
82 </div>
83 </div>
84 </div>
85 </div>
86 </footer>
87 );
88}
89
90LegalFooter.displayName = "LegalFooter";
Addedapps/web/lib/consent.test.ts+116−0View fileUnifiedSplit
1/**
2 * Tests for the consent library. These are regulatory-critical — they
3 * prove that AlecRae will not treat a missing consent record, a stale
4 * version, or a browser privacy signal as permission to set
5 * non-essential cookies.
6 *
7 * @vitest-environment jsdom
8 */
9
10import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
11
12import {
13 buildConsentRecord,
14 CONSENT_STORAGE_KEY,
15 CONSENT_VERSION,
16 DEFAULT_CONSENT,
17 detectDNTSignal,
18 detectGPCSignal,
19 hasConsent,
20 loadConsent,
21 resetConsent,
22 saveConsent,
23} from "./consent";
24
25describe("consent", () => {
26 beforeEach(() => {
27 window.localStorage.clear();
28 });
29
30 afterEach(() => {
31 vi.restoreAllMocks();
32 });
33
34 it("defaults deny everything except necessary", () => {
35 expect(DEFAULT_CONSENT.categories.necessary).toBe(true);
36 expect(DEFAULT_CONSENT.categories.functional).toBe(false);
37 expect(DEFAULT_CONSENT.categories.analytics).toBe(false);
38 expect(DEFAULT_CONSENT.categories.performance).toBe(false);
39 });
40
41 it("returns null when no consent record has been saved", () => {
42 expect(loadConsent()).toBeNull();
43 });
44
45 it("hasConsent('necessary') is always true, even with no record", () => {
46 expect(hasConsent("necessary")).toBe(true);
47 });
48
49 it("hasConsent returns false for any non-necessary category without consent", () => {
50 expect(hasConsent("functional")).toBe(false);
51 expect(hasConsent("analytics")).toBe(false);
52 expect(hasConsent("performance")).toBe(false);
53 });
54
55 it("saves and loads a consent record round-trip", () => {
56 const record = buildConsentRecord({
57 functional: true,
58 analytics: false,
59 performance: true,
60 });
61 saveConsent(record);
62 const loaded = loadConsent();
63 expect(loaded).not.toBeNull();
64 expect(loaded?.categories.functional).toBe(true);
65 expect(loaded?.categories.analytics).toBe(false);
66 expect(loaded?.categories.performance).toBe(true);
67 expect(loaded?.version).toBe(CONSENT_VERSION);
68 });
69
70 it("rejects a stored record whose version does not match", () => {
71 const stale = JSON.stringify({
72 version: CONSENT_VERSION - 1,
73 decidedAt: new Date().toISOString(),
74 categories: {
75 necessary: true,
76 functional: true,
77 analytics: true,
78 performance: true,
79 },
80 autoAppliedFromSignal: false,
81 });
82 window.localStorage.setItem(CONSENT_STORAGE_KEY, stale);
83 expect(loadConsent()).toBeNull();
84 });
85
86 it("resetConsent removes the stored record", () => {
87 saveConsent(buildConsentRecord({ functional: true }));
88 expect(loadConsent()).not.toBeNull();
89 resetConsent();
90 expect(loadConsent()).toBeNull();
91 });
92
93 it("detectGPCSignal picks up navigator.globalPrivacyControl", () => {
94 vi.stubGlobal("navigator", { globalPrivacyControl: true });
95 expect(detectGPCSignal()).toBe(true);
96 });
97
98 it("detectGPCSignal ignores a missing signal", () => {
99 vi.stubGlobal("navigator", {});
100 expect(detectGPCSignal()).toBe(false);
101 });
102
103 it("detectDNTSignal recognises legacy DNT=1", () => {
104 vi.stubGlobal("navigator", { doNotTrack: "1" });
105 expect(detectDNTSignal()).toBe(true);
106 });
107
108 it("buildConsentRecord stamps version and ISO timestamp", () => {
109 const record = buildConsentRecord({ analytics: true });
110 expect(record.version).toBe(CONSENT_VERSION);
111 expect(() => new Date(record.decidedAt).toISOString()).not.toThrow();
112 expect(record.categories.analytics).toBe(true);
113 expect(record.categories.functional).toBe(false);
114 expect(record.categories.performance).toBe(false);
115 });
116});
Addedapps/web/lib/consent.ts+138−0View fileUnifiedSplit
1/**
2 * AlecRae — Consent management (GDPR / ePrivacy / CCPA / CPRA / LGPD).
3 *
4 * This is the single source of truth for cookie and tracking-technology
5 * consent. Every client surface that plans to set a non-essential cookie,
6 * write to localStorage outside a session, or fire any analytic event
7 * MUST first call `hasConsent(category)` — no exceptions.
8 *
9 * We treat "consent" as a first-class privacy control:
10 * - Strictly-necessary cookies always run (no consent required by law).
11 * - All other categories default to OFF (GDPR "opt-in by default").
12 * - Global Privacy Control (GPC) and Do Not Track (DNT) signals are
13 * respected automatically — if the browser sets either, we treat
14 * the non-essential categories as explicitly declined and suppress
15 * the banner.
16 * - Consent records include timestamp + version so we can prove
17 * affirmative, informed, revocable consent under Article 7 GDPR.
18 */
19
20export type ConsentCategory =
21 | "necessary"
22 | "functional"
23 | "analytics"
24 | "performance";
25
26export interface ConsentRecord {
27 /** Incremented whenever the cookie policy materially changes. */
28 version: number;
29 /** ISO-8601 timestamp of when the user recorded the decision. */
30 decidedAt: string;
31 /** Per-category state. `necessary` is always true. */
32 categories: Record<ConsentCategory, boolean>;
33 /** True if the decision was auto-applied because GPC/DNT was set. */
34 autoAppliedFromSignal: boolean;
35}
36
37export const CONSENT_VERSION = 1;
38export const CONSENT_STORAGE_KEY = "alecrae.consent.v1";
39export const CONSENT_CHANGED_EVENT = "alecrae:consent-changed";
40
41export const DEFAULT_CONSENT: ConsentRecord = {
42 version: CONSENT_VERSION,
43 decidedAt: "",
44 categories: {
45 necessary: true,
46 functional: false,
47 analytics: false,
48 performance: false,
49 },
50 autoAppliedFromSignal: false,
51};
52
53/**
54 * Detect a Global Privacy Control signal.
55 * See https://globalprivacycontrol.org/. Respected by CPRA regulations
56 * and a growing number of US state laws.
57 */
58export function detectGPCSignal(): boolean {
59 if (typeof navigator === "undefined") return false;
60 const nav = navigator as Navigator & { globalPrivacyControl?: boolean };
61 return nav.globalPrivacyControl === true;
62}
63
64/**
65 * Detect a legacy Do Not Track signal. We honour it for compatibility
66 * even though GPC is now the canonical mechanism.
67 */
68export function detectDNTSignal(): boolean {
69 if (typeof navigator === "undefined") return false;
70 const nav = navigator as Navigator & {
71 doNotTrack?: string;
72 msDoNotTrack?: string;
73 };
74 const values = [nav.doNotTrack, nav.msDoNotTrack];
75 if (typeof window !== "undefined") {
76 const w = window as Window & { doNotTrack?: string };
77 values.push(w.doNotTrack);
78 }
79 return values.some((v) => v === "1" || v === "yes");
80}
81
82export function loadConsent(): ConsentRecord | null {
83 if (typeof window === "undefined") return null;
84 try {
85 const raw = window.localStorage.getItem(CONSENT_STORAGE_KEY);
86 if (!raw) return null;
87 const parsed = JSON.parse(raw) as ConsentRecord;
88 if (typeof parsed !== "object" || parsed === null) return null;
89 if (parsed.version !== CONSENT_VERSION) return null;
90 return parsed;
91 } catch {
92 return null;
93 }
94}
95
96export function saveConsent(record: ConsentRecord): void {
97 if (typeof window === "undefined") return;
98 try {
99 window.localStorage.setItem(CONSENT_STORAGE_KEY, JSON.stringify(record));
100 window.dispatchEvent(new CustomEvent(CONSENT_CHANGED_EVENT, { detail: record }));
101 } catch {
102 // Intentionally swallow — storage full / disabled must not break the site.
103 }
104}
105
106export function resetConsent(): void {
107 if (typeof window === "undefined") return;
108 try {
109 window.localStorage.removeItem(CONSENT_STORAGE_KEY);
110 window.dispatchEvent(new CustomEvent(CONSENT_CHANGED_EVENT, { detail: null }));
111 } catch {
112 // Intentionally swallow.
113 }
114}
115
116export function hasConsent(category: ConsentCategory): boolean {
117 if (category === "necessary") return true;
118 const record = loadConsent();
119 if (!record) return false;
120 return record.categories[category] === true;
121}
122
123export function buildConsentRecord(
124 categories: Partial<Record<ConsentCategory, boolean>>,
125 autoAppliedFromSignal = false,
126): ConsentRecord {
127 return {
128 version: CONSENT_VERSION,
129 decidedAt: new Date().toISOString(),
130 autoAppliedFromSignal,
131 categories: {
132 necessary: true,
133 functional: categories.functional ?? false,
134 analytics: categories.analytics ?? false,
135 performance: categories.performance ?? false,
136 },
137 };
138}
Modifiedapps/web/package.json+3−2View fileUnifiedSplit
3333 "@types/react": "^19.0.0",
3434 "@types/react-dom": "^19.0.0",
3535 "@types/three": "^0.170.0",
36 "tailwindcss": "^3.4.0",
37 "postcss": "^8.4.0",
3836 "autoprefixer": "^10.4.0",
37 "jsdom": "25",
38 "postcss": "^8.4.0",
39 "tailwindcss": "^3.4.0",
3940 "typescript": "^5.7.0",
4041 "vitest": "^2.1.0"
4142 }
Addedapps/web/public/.well-known/gpc.json+5−0View fileUnifiedSplit
1{
2 "gpc": true,
3 "lastUpdate": "2026-04-16",
4 "version": 1
5}
Addedapps/web/public/.well-known/pgp-key.txt+10−0View fileUnifiedSplit
1AlecRae Security Team PGP Key
2-----------------------------
3
4Our active PGP key is published at https://alecrae.com/security.
5Until a long-lived key is registered, please use encrypted transport
6(TLS 1.3) and send reports to security@alecrae.com. We will respond
7with a session-specific public key if you require encrypted reply.
8
9Fingerprint: (provisional — see https://alecrae.com/security for the
10current published fingerprint before sending sensitive material).
Addedapps/web/public/.well-known/security.txt+27−0View fileUnifiedSplit
1# AlecRae Security Policy
2# RFC 9116: https://www.rfc-editor.org/rfc/rfc9116
3#
4# We take security seriously. If you believe you have discovered a
5# vulnerability, please report it responsibly using the contacts below.
6# We commit to acknowledging reports within 2 business days and to
7# coordinated disclosure with all researchers acting in good faith.
8
9Contact: mailto:security@alecrae.com
10Contact: https://alecrae.com/security
11Expires: 2027-04-16T00:00:00.000Z
12Encryption: https://alecrae.com/.well-known/pgp-key.txt
13Preferred-Languages: en
14Canonical: https://alecrae.com/.well-known/security.txt
15Policy: https://alecrae.com/security
16Hiring: https://alecrae.com/careers
17Acknowledgments: https://alecrae.com/security/hall-of-fame
18
19# Scope: *.alecrae.com, the AlecRae mobile and desktop apps, and the
20# AlecRae public API. Out of scope: social engineering, physical security,
21# denial-of-service, issues requiring privileged network position,
22# third-party services we do not operate.
23#
24# Safe harbor: Good-faith security research that follows this policy
25# will not be pursued under the Computer Fraud and Abuse Act, the
26# Digital Millennium Copyright Act, or similar laws. See
27# https://alecrae.com/security for full safe-harbor terms.
Modifiedbun.lock+74−31View fileUnifiedSplit
206206 "@types/react-dom": "^19.0.0",
207207 "@types/three": "^0.170.0",
208208 "autoprefixer": "^10.4.0",
209 "jsdom": "25",
209210 "postcss": "^8.4.0",
210211 "tailwindcss": "^3.4.0",
211212 "typescript": "^5.7.0",
569570
570571 "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.30.1", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-nuKvp7wOIz6BFei8WrTdhmSsx5mwnArYyJgh4+vYu3V4J0Ltb8Xm3odPm51n1aSI0XxNCrDl7O88cxCtUdAkaw=="],
571572
573 "@asamuzakjp/css-color": ["@asamuzakjp/css-color@3.2.0", "", { "dependencies": { "@csstools/css-calc": "^2.1.3", "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "lru-cache": "^10.4.3" } }, "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw=="],
574
572575 "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
573576
574577 "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="],
887890
888891 "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="],
889892
893 "@csstools/color-helpers": ["@csstools/color-helpers@5.1.0", "", {}, "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA=="],
894
895 "@csstools/css-calc": ["@csstools/css-calc@2.1.4", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ=="],
896
897 "@csstools/css-color-parser": ["@csstools/css-color-parser@3.1.0", "", { "dependencies": { "@csstools/color-helpers": "^5.1.0", "@csstools/css-calc": "^2.1.4" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA=="],
898
899 "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@3.0.5", "", { "peerDependencies": { "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ=="],
900
901 "@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.4", "", {}, "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="],
902
890903 "@develar/schema-utils": ["@develar/schema-utils@2.6.5", "", { "dependencies": { "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" } }, "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig=="],
891904
892905 "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
20992112
21002113 "cssom": ["cssom@0.5.0", "", {}, "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="],
21012114
2102 "cssstyle": ["cssstyle@2.3.0", "", { "dependencies": { "cssom": "~0.3.6" } }, "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A=="],
2115 "cssstyle": ["cssstyle@4.6.0", "", { "dependencies": { "@asamuzakjp/css-color": "^3.2.0", "rrweb-cssom": "^0.8.0" } }, "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg=="],
21032116
21042117 "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
21052118
2106 "data-urls": ["data-urls@3.0.2", "", { "dependencies": { "abab": "^2.0.6", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0" } }, "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ=="],
2119 "data-urls": ["data-urls@5.0.0", "", { "dependencies": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0" } }, "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg=="],
21072120
21082121 "debounce-fn": ["debounce-fn@6.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ=="],
21092122
25352548
25362549 "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="],
25372550
2538 "html-encoding-sniffer": ["html-encoding-sniffer@3.0.0", "", { "dependencies": { "whatwg-encoding": "^2.0.0" } }, "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA=="],
2551 "html-encoding-sniffer": ["html-encoding-sniffer@4.0.0", "", { "dependencies": { "whatwg-encoding": "^3.1.1" } }, "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ=="],
25392552
25402553 "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
25412554
27732786
27742787 "jscodeshift": ["jscodeshift@0.14.0", "", { "dependencies": { "@babel/core": "^7.13.16", "@babel/parser": "^7.13.16", "@babel/plugin-proposal-class-properties": "^7.13.0", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", "@babel/plugin-proposal-optional-chaining": "^7.13.12", "@babel/plugin-transform-modules-commonjs": "^7.13.8", "@babel/preset-flow": "^7.13.13", "@babel/preset-typescript": "^7.13.0", "@babel/register": "^7.13.16", "babel-core": "^7.0.0-bridge.0", "chalk": "^4.1.2", "flow-parser": "0.*", "graceful-fs": "^4.2.4", "micromatch": "^4.0.4", "neo-async": "^2.5.0", "node-dir": "^0.1.17", "recast": "^0.21.0", "temp": "^0.8.4", "write-file-atomic": "^2.3.0" }, "peerDependencies": { "@babel/preset-env": "^7.1.6" }, "bin": { "jscodeshift": "bin/jscodeshift.js" } }, "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA=="],
27752788
2776 "jsdom": ["jsdom@20.0.3", "", { "dependencies": { "abab": "^2.0.6", "acorn": "^8.8.1", "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", "data-urls": "^3.0.2", "decimal.js": "^10.4.2", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", "html-encoding-sniffer": "^3.0.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.2", "parse5": "^7.1.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^4.1.2", "w3c-xmlserializer": "^4.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0", "ws": "^8.11.0", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "canvas": "^2.5.0" }, "optionalPeers": ["canvas"] }, "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ=="],
2789 "jsdom": ["jsdom@25.0.1", "", { "dependencies": { "cssstyle": "^4.1.0", "data-urls": "^5.0.0", "decimal.js": "^10.4.3", "form-data": "^4.0.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.5", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.12", "parse5": "^7.1.2", "rrweb-cssom": "^0.7.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^5.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0", "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^2.11.2" }, "optionalPeers": ["canvas"] }, "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw=="],
27772790
27782791 "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
27792792
35313544
35323545 "rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="],
35333546
3547 "rrweb-cssom": ["rrweb-cssom@0.7.1", "", {}, "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg=="],
3548
35343549 "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
35353550
35363551 "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
37853800
37863801 "tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="],
37873802
3803 "tldts": ["tldts@6.1.86", "", { "dependencies": { "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ=="],
3804
3805 "tldts-core": ["tldts-core@6.1.86", "", {}, "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA=="],
3806
37883807 "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="],
37893808
37903809 "tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="],
37953814
37963815 "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
37973816
3798 "tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="],
3817 "tough-cookie": ["tough-cookie@5.1.2", "", { "dependencies": { "tldts": "^6.1.32" } }, "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A=="],
37993818
3800 "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
3819 "tr46": ["tr46@5.1.1", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="],
38013820
38023821 "treeverse": ["treeverse@3.0.0", "", {}, "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ=="],
38033822
39173936
39183937 "vlq": ["vlq@1.0.1", "", {}, "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w=="],
39193938
3920 "w3c-xmlserializer": ["w3c-xmlserializer@4.0.0", "", { "dependencies": { "xml-name-validator": "^4.0.0" } }, "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw=="],
3939 "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
39213940
39223941 "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="],
39233942
39373956
39383957 "webgl-sdf-generator": ["webgl-sdf-generator@1.1.1", "", {}, "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA=="],
39393958
3940 "webidl-conversions": ["webidl-conversions@5.0.0", "", {}, "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA=="],
3959 "webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
39413960
39423961 "webpack": ["webpack@5.106.1", "", { "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.20.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.17", "watchpack": "^2.5.1", "webpack-sources": "^3.3.4" }, "bin": { "webpack": "bin/webpack.js" } }, "sha512-EW8af29ak8Oaf4T8k8YsajjrDBDYgnKZ5er6ljWFJsXABfTNowQfvHLftwcepVgdz+IoLSdEAbBiM9DFXoll9w=="],
39433962
39443963 "webpack-sources": ["webpack-sources@3.3.4", "", {}, "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q=="],
39453964
3946 "whatwg-encoding": ["whatwg-encoding@2.0.0", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg=="],
3965 "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="],
39473966
39483967 "whatwg-fetch": ["whatwg-fetch@3.6.20", "", {}, "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="],
39493968
3950 "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
3969 "whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
39513970
3952 "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
3971 "whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="],
39533972
39543973 "whatwg-url-without-unicode": ["whatwg-url-without-unicode@8.0.0-3", "", { "dependencies": { "buffer": "^5.4.3", "punycode": "^2.1.1", "webidl-conversions": "^5.0.0" } }, "sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig=="],
39553974
39793998
39803999 "xcode": ["xcode@3.0.1", "", { "dependencies": { "simple-plist": "^1.1.0", "uuid": "^7.0.3" } }, "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA=="],
39814000
3982 "xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="],
4001 "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
39834002
39844003 "xml2js": ["xml2js@0.6.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w=="],
39854004
40334052
40344053 "@anthropic-ai/sdk/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
40354054
4055 "@asamuzakjp/css-color/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
4056
40364057 "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
40374058
40384059 "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
42854306
42864307 "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
42874308
4288 "cssstyle/cssom": ["cssom@0.3.8", "", {}, "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="],
4289
4290 "data-urls/whatwg-url": ["whatwg-url@11.0.0", "", { "dependencies": { "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" } }, "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ=="],
4309 "cssstyle/rrweb-cssom": ["rrweb-cssom@0.8.0", "", {}, "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw=="],
42914310
42924311 "default-gateway/execa": ["execa@1.0.0", "", { "dependencies": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" } }, "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA=="],
42934312
42954314
42964315 "docker-classic/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="],
42974316
4298 "domexception/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
4299
43004317 "electron/@types/node": ["@types/node@20.19.39", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw=="],
43014318
43024319 "electron-updater/builder-util-runtime": ["builder-util-runtime@9.5.1", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ=="],
43534370
43544371 "its-fine/@types/react-reconciler": ["@types/react-reconciler@0.28.9", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg=="],
43554372
4373 "jest-environment-jsdom/jsdom": ["jsdom@20.0.3", "", { "dependencies": { "abab": "^2.0.6", "acorn": "^8.8.1", "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", "data-urls": "^3.0.2", "decimal.js": "^10.4.2", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", "html-encoding-sniffer": "^3.0.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.2", "parse5": "^7.1.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^4.1.2", "w3c-xmlserializer": "^4.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0", "ws": "^8.11.0", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "canvas": "^2.5.0" }, "optionalPeers": ["canvas"] }, "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ=="],
4374
43564375 "jest-haste-map/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
43574376
43584377 "jest-runner/source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="],
43734392
43744393 "jscodeshift/write-file-atomic": ["write-file-atomic@2.4.3", "", { "dependencies": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", "signal-exit": "^3.0.2" } }, "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ=="],
43754394
4376 "jsdom/http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="],
4377
4378 "jsdom/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
4379
4380 "jsdom/whatwg-url": ["whatwg-url@11.0.0", "", { "dependencies": { "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" } }, "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ=="],
4395 "jsdom/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
43814396
43824397 "jsdom/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="],
43834398
44394454
44404455 "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
44414456
4457 "node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
4458
44424459 "node-gyp/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
44434460
44444461 "node-gyp/make-fetch-happen": ["make-fetch-happen@10.2.1", "", { "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^16.1.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-fetch": "^2.0.3", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "promise-retry": "^2.0.1", "socks-proxy-agent": "^7.0.0", "ssri": "^9.0.0" } }, "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w=="],
45634580
45644581 "three-stdlib/fflate": ["fflate@0.6.10", "", {}, "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg=="],
45654582
4566 "tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="],
4567
45684583 "tunnel-rat/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="],
45694584
45704585 "validate-npm-package-license/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="],
45834598
45844599 "webpack/json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
45854600
4586 "whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
4587
45884601 "whatwg-url-without-unicode/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
45894602
4603 "whatwg-url-without-unicode/webidl-conversions": ["webidl-conversions@5.0.0", "", {}, "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA=="],
4604
45904605 "write-file-atomic/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
45914606
45924607 "xcode/uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="],
48154830
48164831 "cosmiconfig/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
48174832
4818 "data-urls/whatwg-url/tr46": ["tr46@3.0.0", "", { "dependencies": { "punycode": "^2.1.1" } }, "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA=="],
4819
4820 "data-urls/whatwg-url/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
4821
48224833 "default-gateway/execa/cross-spawn": ["cross-spawn@6.0.6", "", { "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } }, "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw=="],
48234834
48244835 "default-gateway/execa/get-stream": ["get-stream@4.1.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w=="],
48934904
48944905 "ignore-walk/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
48954906
4907 "jest-environment-jsdom/jsdom/cssstyle": ["cssstyle@2.3.0", "", { "dependencies": { "cssom": "~0.3.6" } }, "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A=="],
4908
4909 "jest-environment-jsdom/jsdom/data-urls": ["data-urls@3.0.2", "", { "dependencies": { "abab": "^2.0.6", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0" } }, "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ=="],
4910
4911 "jest-environment-jsdom/jsdom/html-encoding-sniffer": ["html-encoding-sniffer@3.0.0", "", { "dependencies": { "whatwg-encoding": "^2.0.0" } }, "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA=="],
4912
4913 "jest-environment-jsdom/jsdom/http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="],
4914
4915 "jest-environment-jsdom/jsdom/tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="],
4916
4917 "jest-environment-jsdom/jsdom/w3c-xmlserializer": ["w3c-xmlserializer@4.0.0", "", { "dependencies": { "xml-name-validator": "^4.0.0" } }, "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw=="],
4918
4919 "jest-environment-jsdom/jsdom/whatwg-encoding": ["whatwg-encoding@2.0.0", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg=="],
4920
4921 "jest-environment-jsdom/jsdom/whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
4922
4923 "jest-environment-jsdom/jsdom/whatwg-url": ["whatwg-url@11.0.0", "", { "dependencies": { "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" } }, "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ=="],
4924
4925 "jest-environment-jsdom/jsdom/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="],
4926
4927 "jest-environment-jsdom/jsdom/xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="],
4928
48964929 "jest-watch-typeahead/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
48974930
48984931 "jest-watcher/string-length/char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="],
48994932
4900 "jsdom/whatwg-url/tr46": ["tr46@3.0.0", "", { "dependencies": { "punycode": "^2.1.1" } }, "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA=="],
4933 "jsdom/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
49014934
49024935 "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
49034936
49194952
49204953 "metro/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
49214954
4955 "node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
4956
4957 "node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
4958
49224959 "node-gyp/make-fetch-happen/cacache": ["cacache@16.1.3", "", { "dependencies": { "@npmcli/fs": "^2.1.0", "@npmcli/move-file": "^2.0.0", "chownr": "^2.0.0", "fs-minipass": "^2.1.0", "glob": "^8.0.1", "infer-owner": "^1.0.4", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "mkdirp": "^1.0.4", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^9.0.0", "tar": "^6.1.11", "unique-filename": "^2.0.0" } }, "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ=="],
49234960
49244961 "node-gyp/make-fetch-happen/http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="],
51115148
51125149 "ignore-walk/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
51135150
5151 "jest-environment-jsdom/jsdom/cssstyle/cssom": ["cssom@0.3.8", "", {}, "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="],
5152
5153 "jest-environment-jsdom/jsdom/tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="],
5154
5155 "jest-environment-jsdom/jsdom/whatwg-url/tr46": ["tr46@3.0.0", "", { "dependencies": { "punycode": "^2.1.1" } }, "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA=="],
5156
51145157 "log-symbols/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
51155158
51165159 "log-symbols/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
51175160
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts