Rewrite page copy across the site, fix nav/blog polish, resize SDK explorer
Some checks are pending
CI / build (push) Waiting to run
Some checks are pending
CI / build (push) Waiting to run
Restores and rewrites content on Home, Mobile SDK, DNS Protect, Halo CPE, For Operators, Financial Services, Why BlackDice, Investors and Contact to match the approved source copy, removing stray dashes site-wide in favour of plain punctuation. Removes an unreachable duplicate Contact page block that left a stray unclosed <div> in the markup. Shrinks and repads the Guardian Mobile SDK explorer widget so it fits without excess scrolling. Also includes in-progress nav, blog seed and demo scenario tweaks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -70,11 +70,16 @@ function sanitiseBody(html) {
|
||||
}
|
||||
s = s.replace(/(<br\s*\/?>\s*){3,}/gi, '<br /><br />')
|
||||
s = s.replace(/[ \t]+/g, ' ').replace(/\s*\n\s*/g, '\n').trim()
|
||||
return s
|
||||
return normaliseDashes(s)
|
||||
}
|
||||
|
||||
// House style reads as British English — em dashes read as American, so every
|
||||
// import normalises them to a spaced en dash rather than relying on the studio
|
||||
// draft (or a future re-import) to get it right.
|
||||
const normaliseDashes = (text) => String(text || '').replace(/\s*—\s*/g, ' – ')
|
||||
|
||||
const excerptOf = (post) =>
|
||||
(post.excerpt || sanitiseBody(post.body).replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 260)).trim()
|
||||
normaliseDashes((post.excerpt || sanitiseBody(post.body).replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 260)).trim())
|
||||
|
||||
// ── Hero extraction ────────────────────────────────────────────────────────────
|
||||
const EXT = { 'image/jpeg': 'jpg', 'image/png': 'png', 'image/webp': 'webp', 'image/gif': 'gif', 'image/svg+xml': 'svg' }
|
||||
@@ -119,7 +124,7 @@ const extraPosts = fs.existsSync(EXTRA) ? JSON.parse(fs.readFileSync(EXTRA, 'utf
|
||||
const posts = (draft.posts || []).map((p) => ({
|
||||
id: p.slug,
|
||||
slug: p.slug,
|
||||
title: p.title || 'Untitled',
|
||||
title: normaliseDashes(p.title || 'Untitled'),
|
||||
category: ['insights', 'news', 'press', 'events'].includes(p.category) ? p.category : 'insights',
|
||||
date: (p.date || '').slice(0, 10),
|
||||
author: p.author || 'BlackDice Cyber',
|
||||
@@ -138,11 +143,15 @@ for (const extra of extraPosts) {
|
||||
}
|
||||
posts.sort((a, b) => (a.date < b.date ? 1 : -1))
|
||||
|
||||
const normalisedContent = Object.fromEntries(
|
||||
Object.entries(draft.content || {}).map(([id, text]) => [id, normaliseDashes(text)]),
|
||||
)
|
||||
|
||||
const seed = {
|
||||
format: 'blackdice-react-content',
|
||||
version: 1,
|
||||
savedAt: draft.savedAt || new Date().toISOString(),
|
||||
content: draft.content || {},
|
||||
content: normalisedContent,
|
||||
images: draft.images || {},
|
||||
numbers: {},
|
||||
posts,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -515,7 +515,7 @@ function SceneIsolateModal() {
|
||||
</PrimaryBtn>
|
||||
<motion.div whileTap={{ scale: .97 }}
|
||||
style={{ textAlign: 'center', fontSize: 11, color: T.muted, cursor: 'pointer', padding: '3px 0' }}>
|
||||
Cancel — stay connected
|
||||
Cancel – stay connected
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
@@ -658,7 +658,7 @@ function SceneNotifPanel() {
|
||||
{/* Critical group */}
|
||||
<div style={{ fontSize: 9, fontWeight: 700, color: T.danger, letterSpacing: '.5px', textTransform: 'uppercase', padding: '0 2px' }}>Critical</div>
|
||||
{([
|
||||
{ Icon: IconAlert, title: "Suspicious login — Emma's account", body: 'Instagram · Moscow 🇷🇺 · 02:31 AM', t: '8m ago', variant: 'danger' as const },
|
||||
{ Icon: IconAlert, title: "Suspicious login – Emma's account", body: 'Instagram · Moscow 🇷🇺 · 02:31 AM', t: '8m ago', variant: 'danger' as const },
|
||||
{ Icon: IconShield, title: 'Malware blocked · PS5', body: 'Trojan.GenericKD.71234 · Stopped', t: '6m ago', variant: 'danger' as const },
|
||||
] as const).map((n, i) => {
|
||||
const c = T.danger
|
||||
@@ -682,7 +682,7 @@ function SceneNotifPanel() {
|
||||
<div style={{ fontSize: 9, fontWeight: 700, color: T.muted, letterSpacing: '.5px', textTransform: 'uppercase', padding: '0 2px', marginTop: 4 }}>Earlier</div>
|
||||
{([
|
||||
{ Icon: IconPin, title: 'Emma arrived at School', body: '8:42 AM · geofence enter', t: '2h ago', variant: 'ok' as const },
|
||||
{ Icon: IconLock, title: 'Unsafe WiFi — VPN on', body: "Starbucks_Free · Sarah's iPhone", t: '4h ago', variant: 'warn' as const },
|
||||
{ Icon: IconLock, title: 'Unsafe WiFi – VPN on', body: "Starbucks_Free · Sarah's iPhone", t: '4h ago', variant: 'warn' as const },
|
||||
] as const).map((n, i) => {
|
||||
const vc = { ok: T.success, warn: T.warn }
|
||||
const c = vc[n.variant]
|
||||
@@ -861,7 +861,7 @@ function SceneIPDetail() {
|
||||
|
||||
<motion.div style={{ ...cardStyle, fontSize: 11, color: T.text2, lineHeight: 1.6 }}
|
||||
initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ delay: .35 }}>
|
||||
<span style={{ color: T.danger, fontWeight: 600 }}>Linked to:</span> Instagram credential stuffing campaign — 3,400+ victims globally, ongoing since Nov 2024.
|
||||
<span style={{ color: T.danger, fontWeight: 600 }}>Linked to:</span> Instagram credential stuffing campaign – 3,400+ victims globally, ongoing since Nov 2024.
|
||||
</motion.div>
|
||||
|
||||
<PrimaryBtn color={T.danger}>Block this IP across all devices →</PrimaryBtn>
|
||||
@@ -1448,7 +1448,7 @@ type VideoItem = { id: string; scene: string | null; ms: number; caption: string
|
||||
// Sequence: title + 7 Flow-A scenes + end card ≈ 52s
|
||||
const VIDEO_SEQUENCE: VideoItem[] = [
|
||||
{ id: 'title', scene: null, ms: 4000, caption: 'Welcome', vo: 'Your family’s digital life. One screen.', tilt: { rotateY: -4, rotateX: 2, scale: 0.97 } },
|
||||
{ id: 'home', scene: 'home', ms: 5400, caption: 'Home Dashboard', vo: 'Everything you need to protect your family — in one place.', tilt: { rotateY: -2, rotateX: 2, scale: 1.00 } },
|
||||
{ id: 'home', scene: 'home', ms: 5400, caption: 'Home Dashboard', vo: 'Everything you need to protect your family – in one place.', tilt: { rotateY: -2, rotateX: 2, scale: 1.00 } },
|
||||
{ id: 'toast', scene: 'alert-toast', ms: 6000, caption: 'Real-time Alert', vo: 'Real-time threats, delivered exactly when they happen.', tilt: { rotateY: 3, rotateX: 1, scale: 1.01 } },
|
||||
{ id: 'detail', scene: 'alert-detail', ms: 6400, caption: 'Threat Intelligence', vo: 'We don’t just say something’s wrong. We show you what it is.', tilt: { rotateY: 0, rotateX: 0, scale: 1.00 } },
|
||||
{ id: 'ps5', scene: 'device-ps5', ms: 6000, caption: 'Live Traffic', vo: 'Watch threats in real time. See what’s leaving your home.', tilt: { rotateY: -3, rotateX: 2, scale: 1.01 } },
|
||||
|
||||
@@ -35,7 +35,7 @@ const FLOWS: Record<FlowId, DemoFlow> = {
|
||||
A: {
|
||||
id: 'A',
|
||||
title: 'Critical Alert → Resolution',
|
||||
subtitle: 'Malware detected — PS5 isolated in one tap',
|
||||
subtitle: 'Malware detected – PS5 isolated in one tap',
|
||||
durationSec: 60,
|
||||
color: '#ef4444',
|
||||
steps: [
|
||||
@@ -65,7 +65,7 @@ const FLOWS: Record<FlowId, DemoFlow> = {
|
||||
},
|
||||
{
|
||||
id: 'A4',
|
||||
label: 'Device Detail — PS5',
|
||||
label: 'Device Detail – PS5',
|
||||
screen: 'device-ps5',
|
||||
caption: 'PlayStation 5 · THREAT DETECTED · Unusual traffic spike',
|
||||
camera: 'Slide right. Zoom on threat badge.',
|
||||
@@ -100,7 +100,7 @@ const FLOWS: Record<FlowId, DemoFlow> = {
|
||||
B: {
|
||||
id: 'B',
|
||||
title: 'Incident Investigation',
|
||||
subtitle: 'Suspicious login from Moscow — traced and blocked',
|
||||
subtitle: 'Suspicious login from Moscow – traced and blocked',
|
||||
durationSec: 45,
|
||||
color: '#f59e0b',
|
||||
steps: [
|
||||
@@ -124,7 +124,7 @@ const FLOWS: Record<FlowId, DemoFlow> = {
|
||||
id: 'B3',
|
||||
label: 'Event Timeline',
|
||||
screen: 'timeline',
|
||||
caption: '02:31 — Login from Moscow 🇷🇺 · 02:32 — Password change blocked',
|
||||
caption: '02:31 – Login from Moscow 🇷🇺 · 02:32 – Password change blocked',
|
||||
camera: 'Scroll timeline top-to-bottom.',
|
||||
durationMs: 12000,
|
||||
},
|
||||
@@ -173,7 +173,7 @@ const FLOWS: Record<FlowId, DemoFlow> = {
|
||||
id: 'C3',
|
||||
label: 'Full Network Scan',
|
||||
screen: 'scan-running',
|
||||
caption: 'Scanning 12 devices… ✅ × 11 · ⚠️ Printer — 1 issue',
|
||||
caption: 'Scanning 12 devices… ✅ × 11 · ⚠️ Printer – 1 issue',
|
||||
camera: 'Static. Devices check one by one.',
|
||||
durationMs: 10000,
|
||||
},
|
||||
@@ -187,7 +187,7 @@ const FLOWS: Record<FlowId, DemoFlow> = {
|
||||
},
|
||||
{
|
||||
id: 'C5',
|
||||
label: 'Verified — All Clear',
|
||||
label: 'Verified – All Clear',
|
||||
screen: 'verified',
|
||||
caption: '✅ HP Printer · Patched · All 12 devices secure · Score 95 ▲',
|
||||
camera: 'Zoom out. Green sweep.',
|
||||
|
||||
@@ -479,7 +479,7 @@ export const NOTIFICATIONS: Notification[] = [
|
||||
{
|
||||
id: 'n2',
|
||||
icon: 'alert',
|
||||
title: '⚠️ Suspicious login — Emma',
|
||||
title: '⚠️ Suspicious login – Emma',
|
||||
body: 'Instagram login from Moscow 🇷🇺 at 02:31. Access blocked.',
|
||||
time: '7 hr ago',
|
||||
read: false,
|
||||
@@ -498,7 +498,7 @@ export const NOTIFICATIONS: Notification[] = [
|
||||
id: 'n4',
|
||||
icon: 'shield',
|
||||
title: 'Unsafe WiFi auto-secured',
|
||||
body: 'Emma joined Starbucks_Free_WiFi — VPN activated automatically.',
|
||||
body: 'Emma joined Starbucks_Free_WiFi – VPN activated automatically.',
|
||||
time: 'Yesterday',
|
||||
read: true,
|
||||
severity: 'warn',
|
||||
@@ -582,7 +582,7 @@ export const SECURITY_ALERTS: SecurityAlert[] = [
|
||||
id: 'alert-002',
|
||||
severity: 'critical',
|
||||
type: 'suspicious_login',
|
||||
title: "Suspicious Login — Emma's Account",
|
||||
title: "Suspicious Login – Emma's Account",
|
||||
body: "Login attempt on Emma's Instagram from an unrecognised device in Moscow. Password change was blocked.",
|
||||
deviceId: 'd3',
|
||||
deviceName: "Emma's iPhone 14",
|
||||
@@ -637,7 +637,7 @@ export const SECURITY_ALERTS: SecurityAlert[] = [
|
||||
id: 'alert-006',
|
||||
severity: 'high',
|
||||
type: 'vulnerability',
|
||||
title: 'Firmware Vulnerability — HP Printer',
|
||||
title: 'Firmware Vulnerability – HP Printer',
|
||||
body: 'CVE-2024-8471 (CVSS 7.8): Remote code execution via port 9100. Firmware update available.',
|
||||
deviceId: 'd10',
|
||||
deviceName: 'HP LaserJet Pro',
|
||||
@@ -655,7 +655,7 @@ export const EMMA_INCIDENT_TIMELINE: TimelineEvent[] = [
|
||||
{
|
||||
id: 't1',
|
||||
time: '21:04',
|
||||
label: 'Normal login — Instagram',
|
||||
label: 'Normal login – Instagram',
|
||||
detail: 'Emma signed in from her iPhone 14 · London 🇬🇧 · known device',
|
||||
kind: 'normal',
|
||||
},
|
||||
|
||||
@@ -60,10 +60,10 @@ const PHASE_LABEL = ['', 'Live Map', 'Safe Zone', 'Zone Saved', 'Leaving Zone',
|
||||
|
||||
const VO_CUES = [
|
||||
{ at: 300, text: 'Parents can see exactly where their family is, right from the app.' },
|
||||
{ at: 3800, text: 'Creating a safe zone takes seconds — drop a pin, and draw the boundary.' },
|
||||
{ at: 3800, text: 'Creating a safe zone takes seconds – drop a pin, and draw the boundary.' },
|
||||
{ at: 8800, text: 'Once it’s saved, we keep watch over that boundary, day and night.' },
|
||||
{ at: 11800, text: 'The moment Jack leaves the Park zone, we notify the parent immediately.' },
|
||||
{ at: 23800, text: 'One tap shows exactly where he is — peace of mind, one message away. Book a demo today.' },
|
||||
{ at: 23800, text: 'One tap shows exactly where he is – peace of mind, one message away. Book a demo today.' },
|
||||
]
|
||||
|
||||
const TABS = [
|
||||
|
||||
@@ -173,7 +173,7 @@ export const THREAT_SCENARIOS: ThreatScenario[] = [
|
||||
tag: 'dns',
|
||||
tagLabel: 'DNS · BLOCK',
|
||||
title: 'Malicious DNS Block',
|
||||
blurb: 'Request to a malicious domain blocked. BlackDice Angel risk stays low at 45 — prevention in action.',
|
||||
blurb: 'Request to a malicious domain blocked. BlackDice Angel risk stays low at 45 – prevention in action.',
|
||||
icon: '🌐',
|
||||
context: 'DNS Shield active',
|
||||
initialScore: 35,
|
||||
@@ -183,7 +183,7 @@ export const THREAT_SCENARIOS: ThreatScenario[] = [
|
||||
threatBody: 'Blocked request to phishing-domain.com',
|
||||
detailHeadline: 'Threat blocked before it could load',
|
||||
detailLines: [
|
||||
'phishing-domain.com — known malicious domain',
|
||||
'phishing-domain.com – known malicious domain',
|
||||
'Request intercepted at the DNS layer',
|
||||
'12 malicious domains blocked this week',
|
||||
],
|
||||
@@ -197,7 +197,7 @@ export const THREAT_SCENARIOS: ThreatScenario[] = [
|
||||
voCues: [
|
||||
{ at: 300, text: 'Our DNS Shield filters every domain request.' },
|
||||
{ at: 4300, text: 'A device tries to reach a known malicious domain.' },
|
||||
{ at: 9400, text: 'We block it instantly — our risk score stays low at 45.' },
|
||||
{ at: 9400, text: 'We block it instantly – our risk score stays low at 45.' },
|
||||
{ at: 15500, text: 'The threat is stopped before any damage, and logged in the dashboard.' },
|
||||
{ at: 30300, text: 'We deliver prevention that just works. Book a demo today.' },
|
||||
],
|
||||
@@ -233,7 +233,7 @@ export const THREAT_SCENARIOS: ThreatScenario[] = [
|
||||
statusChange: 'PhotoEditor · request reviewed',
|
||||
voCues: [
|
||||
{ at: 300, text: 'We review every app your child wants to install.' },
|
||||
{ at: 4300, text: 'PhotoEditor asks for more than it needs — contacts and location.' },
|
||||
{ at: 4300, text: 'PhotoEditor asks for more than it needs – contacts and location.' },
|
||||
{ at: 9400, text: 'We flag the risk and raise our BlackDice Angel score to 66.' },
|
||||
{ at: 15500, text: 'The parent can review the permissions and decline the install.' },
|
||||
{ at: 30300, text: 'We make apps safer for younger users. Book a demo today.' },
|
||||
|
||||
@@ -27,13 +27,13 @@ const SCRIPTS: Record<string, Spec> = {
|
||||
score: {
|
||||
dur: 7200,
|
||||
steps: [
|
||||
{ t: 0, s: 0, cap: 'Guardian opens with the score from the last check — <b>81</b>.' },
|
||||
{ t: 0, s: 0, cap: 'Guardian opens with the score from the last check – <b>81</b>.' },
|
||||
{ t: 800, s: 1, cap: 'Re-scanning all nine protection modules on device.', add: [['.js-ring', 'scanning']] },
|
||||
{ t: 2200, s: 2, cap: '<b>Device trust</b> — verified, patch level current.' },
|
||||
{ t: 3200, s: 3, cap: '<b>Apps & permissions</b> — no risky grants detected.' },
|
||||
{ t: 4200, s: 4, cap: '<b>Wi-Fi security</b> — BT-T5F97X assessed as low risk.' },
|
||||
{ t: 2200, s: 2, cap: '<b>Device trust</b> – verified, patch level current.' },
|
||||
{ t: 3200, s: 3, cap: '<b>Apps & permissions</b> – no risky grants detected.' },
|
||||
{ t: 4200, s: 4, cap: '<b>Wi-Fi security</b> – BT-T5F97X assessed as low risk.' },
|
||||
{ t: 5000, s: 5, cap: 'Score recomputed and the delta shown: <b>81 → 93</b>.', rm: [['.js-ring', 'scanning']], num: [['.js-score', 81, 93, 900]], ring: [['.js-arc', 81, 93, 900]] },
|
||||
{ t: 6300, cap: 'Ranked remediation sits behind one tap — no dead ends.', flash: [['.js-improve', 'glow', 900]] },
|
||||
{ t: 6300, cap: 'Ranked remediation sits behind one tap – no dead ends.', flash: [['.js-improve', 'glow', 900]] },
|
||||
],
|
||||
},
|
||||
protection: {
|
||||
@@ -41,8 +41,8 @@ const SCRIPTS: Record<string, Spec> = {
|
||||
steps: [
|
||||
{ t: 0, s: 0, cap: 'The Protection tab covers the connection itself.' },
|
||||
{ t: 700, s: 1, cap: "DNS filter reports the day's blocks.", num: [['.js-n', 0, 40, 1100]] },
|
||||
{ t: 2500, s: 2, cap: 'Call screening — <b>no scam calls today</b>.' },
|
||||
{ t: 3700, s: 3, cap: 'SIM integrity — no carrier or slot change detected.' },
|
||||
{ t: 2500, s: 2, cap: 'Call screening – <b>no scam calls today</b>.' },
|
||||
{ t: 3700, s: 3, cap: 'SIM integrity – no carrier or slot change detected.' },
|
||||
{ t: 5000, cap: 'Any card taps through to its full timestamped event log.', flash: [['.js-sb', 'tap', 1300]] },
|
||||
],
|
||||
},
|
||||
@@ -51,7 +51,7 @@ const SCRIPTS: Record<string, Spec> = {
|
||||
steps: [
|
||||
{ t: 0, s: 0, cap: '285 blocked of 1,116 lookups so far today.' },
|
||||
{ t: 900, s: 1, cap: 'User taps a link in a WhatsApp group message.', set: [['.js-chip', 'Resolving…']], add: [['.js-chip', 'wait'], ['.js-chip', 'pulse']] },
|
||||
{ t: 2300, s: 2, cap: '<b>whtaspp-group.pages.dev</b> — typosquat, matched in 40 ms.', set: [['.js-chip', 'Blocked']], rm: [['.js-chip', 'wait'], ['.js-chip', 'pulse']] },
|
||||
{ t: 2300, s: 2, cap: '<b>whtaspp-group.pages.dev</b> – typosquat, matched in 40 ms.', set: [['.js-chip', 'Blocked']], rm: [['.js-chip', 'wait'], ['.js-chip', 'pulse']] },
|
||||
{ t: 3100, s: 3, cap: 'Connection never opens. The block is logged and shown to the user.', add: [['.js-newrow', 'in'], ['.js-newrow', 'hot']], num: [['.js-blocked', 285, 288, 800], ['.js-req', 1116, 1119, 800]] },
|
||||
{ t: 5000, s: 4, cap: 'Counts refresh on demand.', flash: [['.js-refresh', 'press', 300]], rm: [['.js-newrow', 'hot']] },
|
||||
{ t: 6400, cap: 'Every verdict is also available to your fraud platform by webhook.' },
|
||||
@@ -61,7 +61,7 @@ const SCRIPTS: Record<string, Spec> = {
|
||||
dur: 9200,
|
||||
steps: [
|
||||
{ t: 0, s: 0, cap: 'Home screen. An unknown number starts ringing.' },
|
||||
{ t: 700, s: 1, cap: 'Inbound call from <b>07813996004</b> — reputation lookup fires.', add: [['.js-verdict', 'pulse']] },
|
||||
{ t: 700, s: 1, cap: 'Inbound call from <b>07813996004</b> – reputation lookup fires.', add: [['.js-verdict', 'pulse']] },
|
||||
{ t: 2100, s: 2, cap: 'Verdict returned <i>before answer</i>: likely scam, confidence 91.', set: [['.js-verdict', 'Likely scam · 91']], add: [['.js-verdict', 'bad']], rm: [['.js-verdict', 'pulse']] },
|
||||
{ t: 3700, s: 3, cap: 'Call ends. Guardian asks how it went.' },
|
||||
{ t: 5300, s: 4, cap: 'One tap: the user labels it <b>Scam</b>.', flash: [['.js-scam', 'hit', 1400]] },
|
||||
@@ -73,11 +73,11 @@ const SCRIPTS: Record<string, Spec> = {
|
||||
dur: 6600,
|
||||
steps: [
|
||||
{ t: 0, s: 0, cap: 'Screen opens and reads phone state.', add: [['.js-simh', 'pulse']] },
|
||||
{ t: 1500, s: 1, cap: 'Read completes — <b>no SIM changes detected</b>.', set: [['.js-simh', 'No SIM changes detected'], ['.js-simp', 'No SIM changes detected.']], rm: [['.js-simh', 'pulse']], flash: [['.js-simh', 'swap', 600]] },
|
||||
{ t: 1500, s: 1, cap: 'Read completes – <b>no SIM changes detected</b>.', set: [['.js-simh', 'No SIM changes detected'], ['.js-simp', 'No SIM changes detected.']], rm: [['.js-simh', 'pulse']], flash: [['.js-simh', 'swap', 600]] },
|
||||
{ t: 2500, s: 2, cap: 'Carrier recorded: <b>EE</b>.' },
|
||||
{ t: 3300, s: 3, cap: 'Slot configuration: single SIM.' },
|
||||
{ t: 4100, s: 4, cap: 'Change history: none on record.' },
|
||||
{ t: 5200, cap: 'Had this changed, the alert would go to the recovery email — not this screen.' },
|
||||
{ t: 5200, cap: 'Had this changed, the alert would go to the recovery email – not this screen.' },
|
||||
],
|
||||
},
|
||||
perms: {
|
||||
@@ -85,10 +85,10 @@ const SCRIPTS: Record<string, Spec> = {
|
||||
steps: [
|
||||
{ t: 0, s: 0, cap: 'User taps <b>Scan again</b>.', flash: [['.js-scan', 'press', 300]] },
|
||||
{ t: 900, s: 1, cap: 'Scanning installed apps for the grants fraudsters abuse.' },
|
||||
{ t: 2400, s: 2, cap: '<b>Microphone</b> — high risk. 1 app holds it, 1 to review.' },
|
||||
{ t: 3400, s: 3, cap: '<b>Phone state</b> — medium. Can read your number and call state.' },
|
||||
{ t: 4400, s: 4, cap: '<b>Contacts</b> — medium, and 1 app still to review.' },
|
||||
{ t: 5400, s: 5, cap: '<b>Post notifications</b> — low risk, already accepted.' },
|
||||
{ t: 2400, s: 2, cap: '<b>Microphone</b> – high risk. 1 app holds it, 1 to review.' },
|
||||
{ t: 3400, s: 3, cap: '<b>Phone state</b> – medium. Can read your number and call state.' },
|
||||
{ t: 4400, s: 4, cap: '<b>Contacts</b> – medium, and 1 app still to review.' },
|
||||
{ t: 5400, s: 5, cap: '<b>Post notifications</b> – low risk, already accepted.' },
|
||||
{ t: 6600, cap: 'Ranked by how fraud actually works, not alphabetically.' },
|
||||
],
|
||||
},
|
||||
@@ -96,10 +96,10 @@ const SCRIPTS: Record<string, Spec> = {
|
||||
dur: 11200,
|
||||
steps: [
|
||||
{ t: 0, s: 0, cap: 'Empty map. No zones configured yet.' },
|
||||
{ t: 800, s: 1, cap: "User drops a pin and draws the boundary — <b>Jakarta Town Hall</b>." },
|
||||
{ t: 800, s: 1, cap: "User drops a pin and draws the boundary – <b>Jakarta Town Hall</b>." },
|
||||
{ t: 2000, s: 2, cap: 'Radius set to <b>200 m</b>. The geofence is armed.', num: [['.js-rad', 0, 200, 700]] },
|
||||
{ t: 3300, s: 3, cap: "Zone saved. It now appears in the user's list." },
|
||||
{ t: 4700, s: 4, cap: 'They arrive — an enter event is logged, timestamped.', add: [['.js-noev', 'gone']] },
|
||||
{ t: 4700, s: 4, cap: 'They arrive – an enter event is logged, timestamped.', add: [['.js-noev', 'gone']] },
|
||||
{ t: 6800, s: 5, cap: 'Later, they cross back out of the boundary.' },
|
||||
{ t: 8300, s: 6, cap: 'Guardian notifies the parent the moment they leave the zone.' },
|
||||
{ t: 10100, cap: 'The same signal drives impossible-travel checks for your fraud engine.' },
|
||||
@@ -109,20 +109,20 @@ const SCRIPTS: Record<string, Spec> = {
|
||||
dur: 7400,
|
||||
steps: [
|
||||
{ t: 0, s: 0, cap: 'Both identity checks are long-running and start on open.', add: [['.js-locb', 'pulse'], ['.js-idb', 'pulse']] },
|
||||
{ t: 1100, s: 1, cap: 'Location analysis resolves — <b>no unusual travel detected</b>.', set: [['.js-locb', 'Good'], ['.js-locp', 'No unusual travel detected.']], add: [['.js-locb', 'good']], rm: [['.js-locb', 'pulse'], ['.js-locb', 'wait']], flash: [['.js-locb', 'swap', 600]] },
|
||||
{ t: 3000, s: 2, cap: 'Breach monitoring resolves — <b>1 monitored identity, no breaches</b>.', set: [['.js-idb', 'Good'], ['.js-idp', '1 monitored identity — no breaches found.']], add: [['.js-idb', 'good']], rm: [['.js-idb', 'pulse'], ['.js-idb', 'wait']], flash: [['.js-idb', 'swap', 600]] },
|
||||
{ t: 1100, s: 1, cap: 'Location analysis resolves – <b>no unusual travel detected</b>.', set: [['.js-locb', 'Good'], ['.js-locp', 'No unusual travel detected.']], add: [['.js-locb', 'good']], rm: [['.js-locb', 'pulse'], ['.js-locb', 'wait']], flash: [['.js-locb', 'swap', 600]] },
|
||||
{ t: 3000, s: 2, cap: 'Breach monitoring resolves – <b>1 monitored identity, no breaches</b>.', set: [['.js-idb', 'Good'], ['.js-idp', '1 monitored identity – no breaches found.']], add: [['.js-idb', 'good']], rm: [['.js-idb', 'pulse'], ['.js-idb', 'wait']], flash: [['.js-idb', 'swap', 600]] },
|
||||
{ t: 4700, cap: 'Re-checked continuously as new breach corpora land.' },
|
||||
{ t: 6000, cap: 'A positive finding escalates by webhook — not just an on-device toast.' },
|
||||
{ t: 6000, cap: 'A positive finding escalates by webhook – not just an on-device toast.' },
|
||||
],
|
||||
},
|
||||
settings: {
|
||||
dur: 9400,
|
||||
steps: [
|
||||
{ t: 0, s: 0, cap: 'Every module is opt-in and starts off.' },
|
||||
{ t: 800, s: 1, cap: '<b>Safe browsing</b> on — DNS-level phishing protection.', add: [['.js-sw1', 'on']] },
|
||||
{ t: 800, s: 1, cap: '<b>Safe browsing</b> on – DNS-level phishing protection.', add: [['.js-sw1', 'on']] },
|
||||
{ t: 2000, s: 2, cap: '<b>Scam call warnings</b> on.', add: [['.js-sw2', 'on']] },
|
||||
{ t: 3200, s: 3, cap: '<b>SIM & carrier</b> on — and the reason for phone-state access is stated.', add: [['.js-sw3', 'on']] },
|
||||
{ t: 4500, s: 4, cap: "A stolen phone can't warn you on its own screen — so alerts go out-of-band.", type: [['.js-email', 'jason.reid@icloud.com', 1500]], add: [['.js-email', 'filled']] },
|
||||
{ t: 3200, s: 3, cap: '<b>SIM & carrier</b> on – and the reason for phone-state access is stated.', add: [['.js-sw3', 'on']] },
|
||||
{ t: 4500, s: 4, cap: "A stolen phone can't warn you on its own screen – so alerts go out-of-band.", type: [['.js-email', 'jason.reid@icloud.com', 1500]], add: [['.js-email', 'filled']] },
|
||||
{ t: 6800, s: 5, cap: 'Address verified by code. That channel now carries SIM and location alerts.', flash: [['.js-send', 'press', 300]] },
|
||||
{ t: 7900, cap: 'Consent is revocable per module, and every string is localisable.' },
|
||||
],
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
--mse-shadow-lg: 0 8px 32px rgba(0,0,0,.40); --mse-shadow-xl: 0 24px 70px rgba(0,0,0,.55);
|
||||
font-family: 'Ubuntu', -apple-system, system-ui, sans-serif;
|
||||
color: var(--mse-text);
|
||||
padding: 28px 0 36px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -21,7 +22,7 @@
|
||||
============================================================ */
|
||||
.bd-mse .tabstrip {
|
||||
display: flex; gap: .5rem; overflow-x: auto; padding: .4rem;
|
||||
background: var(--mse-surface); border: 1px solid var(--mse-border); border-radius: 18px; margin-bottom: 2.5rem;
|
||||
background: var(--mse-surface); border: 1px solid var(--mse-border); border-radius: 18px; margin-bottom: 1.5rem;
|
||||
-webkit-mask-image: linear-gradient(90deg, transparent, #000 20px, #000 calc(100% - 20px), transparent);
|
||||
mask-image: linear-gradient(90deg, transparent, #000 20px, #000 calc(100% - 20px), transparent);
|
||||
}
|
||||
@@ -32,18 +33,18 @@
|
||||
.bd-mse .tab.on { background: var(--mse-accent); color: #fff; box-shadow: 0 6px 18px var(--mse-accent-glow); }
|
||||
.bd-mse .tab svg { width: 16px; height: 16px; flex-shrink: 0; }
|
||||
|
||||
.bd-mse .panel { display: none; grid-template-columns: 340px 1fr; gap: 4rem; align-items: start; max-width: 1180px; margin: 0 auto; }
|
||||
.bd-mse .panel { display: none; grid-template-columns: 232px 1fr; gap: 2.75rem; align-items: start; max-width: 1180px; margin: 0 auto; }
|
||||
.bd-mse .panel.on { display: grid; animation: mse-fade .45s ease; }
|
||||
@keyframes mse-fade { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
|
||||
|
||||
.bd-mse .copy { max-width: 640px; padding-top: 6px; }
|
||||
.bd-mse .copy .kicker { display: inline-flex; align-items: center; gap: .5rem; font-family: 'Ubuntu Mono', monospace; font-size: .78rem; letter-spacing: 1.4px; text-transform: uppercase; color: var(--mse-accent); font-weight: 700; }
|
||||
.bd-mse .copy .kicker::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--mse-accent); box-shadow: 0 0 0 3px var(--mse-accent-soft); }
|
||||
.bd-mse .copy h3 { font-size: 1.9rem; margin: .6rem 0 1rem; }
|
||||
.bd-mse .copy .desc { color: var(--mse-text-2); font-size: 1.03rem; line-height: 1.65; margin-bottom: 1.8rem; }
|
||||
.bd-mse .sig-title { font-size: .78rem; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mse-text-mute); font-weight: 700; margin-bottom: .9rem; }
|
||||
.bd-mse .sigs { list-style: none; display: grid; gap: .6rem; margin-bottom: 1.9rem; }
|
||||
.bd-mse .sigs li { display: flex; gap: .75rem; align-items: flex-start; padding: .8rem 1rem; background: var(--mse-surface); border: 1px solid var(--mse-border); border-radius: 12px; font-size: .92rem; color: var(--mse-text-2); }
|
||||
.bd-mse .copy h3 { font-size: 1.55rem; margin: .5rem 0 .75rem; }
|
||||
.bd-mse .copy .desc { color: var(--mse-text-2); font-size: .95rem; line-height: 1.55; margin-bottom: 1.2rem; }
|
||||
.bd-mse .sig-title { font-size: .74rem; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mse-text-mute); font-weight: 700; margin-bottom: .7rem; }
|
||||
.bd-mse .sigs { list-style: none; display: grid; gap: .5rem; margin-bottom: 1.3rem; }
|
||||
.bd-mse .sigs li { display: flex; gap: .65rem; align-items: flex-start; padding: .6rem .85rem; background: var(--mse-surface); border: 1px solid var(--mse-border); border-radius: 12px; font-size: .86rem; color: var(--mse-text-2); }
|
||||
.bd-mse .sigs li b { color: var(--mse-text); font-weight: 600; }
|
||||
.bd-mse .sigs li::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--mse-accent); flex-shrink: 0; margin-top: .55rem; }
|
||||
.bd-mse .pillrow { display: flex; flex-wrap: wrap; gap: .5rem; }
|
||||
@@ -53,7 +54,7 @@
|
||||
/* ============================================================
|
||||
PHONE + REEL CHROME
|
||||
============================================================ */
|
||||
.bd-mse .stage { width: 340px; }
|
||||
.bd-mse .stage { width: 340px; zoom: .68; }
|
||||
.bd-mse .phone {
|
||||
position: relative; width: 340px; padding: 11px; border-radius: 46px;
|
||||
background: linear-gradient(160deg,#3a3f45,#191c20 45%,#2c3137);
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<div class="ap-btn purple js-improve">How to improve</div>
|
||||
<div class="ap-card" data-at="2"><div class="ap-cardhead"><h5>Device trust</h5><span class="ap-badge good">Good</span></div><p>Your device is verified and up to date.</p></div>
|
||||
<div class="ap-card" data-at="3"><div class="ap-cardhead"><h5>Apps & permissions</h5><span class="ap-badge good">Good</span></div><p>No risky app permissions detected.</p></div>
|
||||
<div class="ap-card" data-at="4"><div class="ap-cardhead"><h5>Wi-Fi security</h5><span class="ap-badge good">Good</span></div><p>BT-T5F97X — Low risk.</p></div>
|
||||
<div class="ap-card" data-at="4"><div class="ap-cardhead"><h5>Wi-Fi security</h5><span class="ap-badge good">Good</span></div><p>BT-T5F97X – Low risk.</p></div>
|
||||
</div>
|
||||
<div class="ap-body-fade"></div>
|
||||
<div class="ap-tabbar">
|
||||
@@ -62,13 +62,13 @@
|
||||
<h3>One number your subscribers understand</h3>
|
||||
<p class="desc">Guardian collapses every protection signal on the handset into a single 0–100 safety score,
|
||||
with a delta against the previous check so users can see the effect of acting on advice. Underneath it,
|
||||
each contributing module reports its own plain-English status — no jargon, no CVE numbers, no dead ends.</p>
|
||||
each contributing module reports its own plain-English status – no jargon, no CVE numbers, no dead ends.</p>
|
||||
<div class="sig-title">What the module exposes</div>
|
||||
<ul class="sigs">
|
||||
<li><b>Composite score</b> — 0–100, weighted across all enabled modules</li>
|
||||
<li><b>Delta since last check</b> — signed change plus the previous value</li>
|
||||
<li><b>Per-module status</b> — Good / Attention / At risk, each with a one-line reason</li>
|
||||
<li><b>Remediation queue</b> — ordered, tappable actions behind "How to improve"</li>
|
||||
<li><b>Composite score</b> – 0–100, weighted across all enabled modules</li>
|
||||
<li><b>Delta since last check</b> – signed change plus the previous value</li>
|
||||
<li><b>Per-module status</b> – Good / Attention / At risk, each with a one-line reason</li>
|
||||
<li><b>Remediation queue</b> – ordered, tappable actions behind "How to improve"</li>
|
||||
</ul>
|
||||
<div class="pillrow"><span class="pill">score.observe()</span><span class="pill">score.delta</span><span class="pill">score.breakdown[]</span></div>
|
||||
</div>
|
||||
@@ -97,7 +97,7 @@
|
||||
<div class="ap-lastchk">Last check you were at 81.</div>
|
||||
</div>
|
||||
<div class="ap-btn purple">How to improve</div>
|
||||
<div class="ap-card js-sb" data-at="1"><div class="ap-cardhead"><h5>Safe browsing</h5><span class="ap-badge good">Good</span></div><p>Safe browsing is on — <span class="js-n">0</span> unsafe requests blocked today.</p></div>
|
||||
<div class="ap-card js-sb" data-at="1"><div class="ap-cardhead"><h5>Safe browsing</h5><span class="ap-badge good">Good</span></div><p>Safe browsing is on – <span class="js-n">0</span> unsafe requests blocked today.</p></div>
|
||||
<div class="ap-card" data-at="2"><div class="ap-cardhead"><h5>Calls</h5><span class="ap-badge good">Good</span></div><p>No scam calls today.</p></div>
|
||||
<div class="ap-card" data-at="3"><div class="ap-cardhead"><h5>SIM & carrier</h5><span class="ap-badge good">Good</span></div></div>
|
||||
</div>
|
||||
@@ -121,15 +121,15 @@
|
||||
<div class="copy">
|
||||
<span class="kicker">Module 02 · Protection tab</span>
|
||||
<h3>Network and telephony threats in one view</h3>
|
||||
<p class="desc">The Protection tab groups the modules that defend the connection itself — DNS filtering,
|
||||
<p class="desc">The Protection tab groups the modules that defend the connection itself – DNS filtering,
|
||||
scam-call screening and SIM integrity. Each card is a live status with today's counts, and taps
|
||||
through to a detail screen with the underlying event log.</p>
|
||||
<div class="sig-title">What the module exposes</div>
|
||||
<ul class="sigs">
|
||||
<li><b>Safe browsing status</b> — on/off plus unsafe requests blocked today</li>
|
||||
<li><b>Call screening status</b> — scam calls flagged in the current period</li>
|
||||
<li><b>SIM integrity status</b> — swap detection state</li>
|
||||
<li><b>Drill-down events</b> — timestamped log per module for support and dispute handling</li>
|
||||
<li><b>Safe browsing status</b> – on/off plus unsafe requests blocked today</li>
|
||||
<li><b>Call screening status</b> – scam calls flagged in the current period</li>
|
||||
<li><b>SIM integrity status</b> – swap detection state</li>
|
||||
<li><b>Drill-down events</b> – timestamped log per module for support and dispute handling</li>
|
||||
</ul>
|
||||
<div class="pillrow"><span class="pill">protection.summary()</span><span class="pill">protection.events()</span></div>
|
||||
</div>
|
||||
@@ -177,16 +177,16 @@
|
||||
<span class="kicker">Module 03 · Safe browsing</span>
|
||||
<h3>Malicious DNS detection</h3>
|
||||
<p class="desc">Guardian resolves DNS through a filtering layer that scores every lookup against
|
||||
live phishing, malware and command-and-control intelligence. Lookalike domains — the
|
||||
live phishing, malware and command-and-control intelligence. Lookalike domains – the
|
||||
<span class="mono">whtaspp-group</span> and <span class="mono">fxjn-web-whatsapp</span> class of
|
||||
typosquats — are blocked before the handset ever opens a connection. The user sees a plain list of what
|
||||
typosquats – are blocked before the handset ever opens a connection. The user sees a plain list of what
|
||||
was stopped and when, which is what turns a silent security control into something they'll pay for.</p>
|
||||
<div class="sig-title">What the module exposes</div>
|
||||
<ul class="sigs">
|
||||
<li><b>Blocked / total counts</b> — per day, per device, with on-demand refresh</li>
|
||||
<li><b>Blocked-domain log</b> — domain, timestamp, verdict and threat category</li>
|
||||
<li><b>Live verdicts</b> — subscribe to block events as they happen</li>
|
||||
<li><b>Allow-list control</b> — user or operator override for false positives</li>
|
||||
<li><b>Blocked / total counts</b> – per day, per device, with on-demand refresh</li>
|
||||
<li><b>Blocked-domain log</b> – domain, timestamp, verdict and threat category</li>
|
||||
<li><b>Live verdicts</b> – subscribe to block events as they happen</li>
|
||||
<li><b>Allow-list control</b> – user or operator override for false positives</li>
|
||||
</ul>
|
||||
<div class="pillrow"><span class="pill">safeBrowsing.stats()</span><span class="pill">safeBrowsing.blocked()</span><span class="pill">safeBrowsing.allow(domain)</span></div>
|
||||
</div>
|
||||
@@ -216,7 +216,7 @@
|
||||
<span class="chev">⌄</span>
|
||||
</div>
|
||||
<div class="acts"><span>Spam</span><span class="js-scam">Scam</span><span>OK</span></div>
|
||||
<div class="ap-thanks">✓ Reported — thanks, that helps everyone</div>
|
||||
<div class="ap-thanks">✓ Reported – thanks, that helps everyone</div>
|
||||
</div>
|
||||
|
||||
<div class="ap-body pt">
|
||||
@@ -229,7 +229,7 @@
|
||||
<div class="ap-lastchk">Last check you were at 81.</div>
|
||||
</div>
|
||||
<div class="ap-btn purple">How to improve</div>
|
||||
<div class="ap-card"><div class="ap-cardhead"><h5>Safe browsing</h5><span class="ap-badge good">Good</span></div><p>Safe browsing is on — 288 unsafe requests blocked today.</p></div>
|
||||
<div class="ap-card"><div class="ap-cardhead"><h5>Safe browsing</h5><span class="ap-badge good">Good</span></div><p>Safe browsing is on – 288 unsafe requests blocked today.</p></div>
|
||||
</div>
|
||||
<div class="ap-body-fade" style="bottom:0"></div>
|
||||
</div></div>
|
||||
@@ -245,15 +245,14 @@
|
||||
<span class="kicker">Module 04 · Call screening</span>
|
||||
<h3>Scam call warnings, rated by the user</h3>
|
||||
<p class="desc">Guardian screens inbound numbers against reputation intelligence and warns before the
|
||||
user engages. Immediately after the call ends, a heads-up prompt asks how it went —
|
||||
<b>Spam</b>, <b>Scam</b> or <b>OK</b>. That one tap is the feedback loop: it feeds a crowd-sourced
|
||||
user engages. Immediately after the call ends, a heads-up prompt asks how it went – <b>Spam</b>, <b>Scam</b> or <b>OK</b>. That one tap is the feedback loop: it feeds a crowd-sourced
|
||||
reputation graph that sharpens detection for every other subscriber on your network.</p>
|
||||
<div class="sig-title">What the module exposes</div>
|
||||
<ul class="sigs">
|
||||
<li><b>Pre-answer verdict</b> — reputation score and category for the calling number</li>
|
||||
<li><b>Post-call prompt</b> — Spam / Scam / OK, returned as a user report</li>
|
||||
<li><b>Daily scam count</b> — feeds the Calls card on the Protection tab</li>
|
||||
<li><b>Network learning</b> — reports aggregate into shared reputation intelligence</li>
|
||||
<li><b>Pre-answer verdict</b> – reputation score and category for the calling number</li>
|
||||
<li><b>Post-call prompt</b> – Spam / Scam / OK, returned as a user report</li>
|
||||
<li><b>Daily scam count</b> – feeds the Calls card on the Protection tab</li>
|
||||
<li><b>Network learning</b> – reports aggregate into shared reputation intelligence</li>
|
||||
</ul>
|
||||
<div class="pillrow"><span class="pill">calls.verdict(msisdn)</span><span class="pill">calls.report(verdict)</span><span class="pill">calls.today()</span></div>
|
||||
</div>
|
||||
@@ -295,14 +294,14 @@
|
||||
<h3>SIM-swap detection at the device layer</h3>
|
||||
<p class="desc">SIM swap is the pivot for most account-takeover fraud: move the number, intercept the OTP,
|
||||
drain the account. Guardian watches phone-state on the handset and records carrier, slot configuration
|
||||
and any change event — so a swap is detected from the victim's own device, independently of the
|
||||
and any change event – so a swap is detected from the victim's own device, independently of the
|
||||
network-side signal, and can be surfaced to your fraud team in real time.</p>
|
||||
<div class="sig-title">What the module exposes</div>
|
||||
<ul class="sigs">
|
||||
<li><b>Change verdict</b> — whether a SIM change has been detected</li>
|
||||
<li><b>Carrier and slot state</b> — current operator, single vs dual SIM</li>
|
||||
<li><b>Change history</b> — timestamp of the previous change, or none on record</li>
|
||||
<li><b>Out-of-band alerting</b> — routed to the recovery email, not just an on-device toast</li>
|
||||
<li><b>Change verdict</b> – whether a SIM change has been detected</li>
|
||||
<li><b>Carrier and slot state</b> – current operator, single vs dual SIM</li>
|
||||
<li><b>Change history</b> – timestamp of the previous change, or none on record</li>
|
||||
<li><b>Out-of-band alerting</b> – routed to the recovery email, not just an on-device toast</li>
|
||||
</ul>
|
||||
<div class="pillrow"><span class="pill">sim.state()</span><span class="pill">sim.onChange()</span><span class="pill">sim.history()</span></div>
|
||||
</div>
|
||||
@@ -341,14 +340,14 @@
|
||||
<h3>Permission risk, ranked by how fraud actually works</h3>
|
||||
<p class="desc">Rather than dumping the full Android permission table on the user, Guardian scans installed
|
||||
apps for the specific grants fraudsters abuse and ranks them High / Medium / Low. Each card explains the
|
||||
capability in one sentence — "can turn on your microphone" — and tapping it reveals exactly which apps
|
||||
capability in one sentence – "can turn on your microphone" – and tapping it reveals exactly which apps
|
||||
hold that grant, with a review-or-accept decision the user can actually make.</p>
|
||||
<div class="sig-title">What the module exposes</div>
|
||||
<ul class="sigs">
|
||||
<li><b>Risk-ranked permission set</b> — High / Medium / Low against a fraud-abuse model</li>
|
||||
<li><b>Holder attribution</b> — which installed apps hold each grant</li>
|
||||
<li><b>Review state</b> — per-permission accepted vs still to review</li>
|
||||
<li><b>On-demand rescan</b> — user-triggered, results feed the safety score</li>
|
||||
<li><b>Risk-ranked permission set</b> – High / Medium / Low against a fraud-abuse model</li>
|
||||
<li><b>Holder attribution</b> – which installed apps hold each grant</li>
|
||||
<li><b>Review state</b> – per-permission accepted vs still to review</li>
|
||||
<li><b>On-demand rescan</b> – user-triggered, results feed the safety score</li>
|
||||
</ul>
|
||||
<div class="pillrow"><span class="pill">permissions.scan()</span><span class="pill">permissions.holders(p)</span><span class="pill">permissions.accept(p)</span></div>
|
||||
</div>
|
||||
@@ -425,16 +424,16 @@
|
||||
<div class="copy">
|
||||
<span class="kicker">Module 07 · Location safety zones</span>
|
||||
<h3>Geofences the user sets themselves</h3>
|
||||
<p class="desc">Users define named zones with a radius — home, school, a parent's address — and Guardian
|
||||
<p class="desc">Users define named zones with a radius – home, school, a parent's address – and Guardian
|
||||
reports arrival and departure against them. It's the consumer-friendly half of location intelligence:
|
||||
the same signal that flags impossible travel for your fraud engine is presented here as reassurance
|
||||
rather than surveillance, with the user in control of every zone.</p>
|
||||
<div class="sig-title">What the module exposes</div>
|
||||
<ul class="sigs">
|
||||
<li><b>Zone CRUD</b> — create, activate, delete named zones with a metre radius</li>
|
||||
<li><b>Enter / exit events</b> — timestamped, per zone</li>
|
||||
<li><b>Impossible-travel signal</b> — feeds the Identity tab's Location status</li>
|
||||
<li><b>Configured / active counts</b> — summary for the zone list header</li>
|
||||
<li><b>Zone CRUD</b> – create, activate, delete named zones with a metre radius</li>
|
||||
<li><b>Enter / exit events</b> – timestamped, per zone</li>
|
||||
<li><b>Impossible-travel signal</b> – feeds the Identity tab's Location status</li>
|
||||
<li><b>Configured / active counts</b> – summary for the zone list header</li>
|
||||
</ul>
|
||||
<div class="pillrow"><span class="pill">zones.add(name, latLng, r)</span><span class="pill">zones.events()</span><span class="pill">zones.list()</span></div>
|
||||
</div>
|
||||
@@ -493,14 +492,14 @@
|
||||
<h3>Breach exposure and travel anomalies</h3>
|
||||
<p class="desc">The Identity tab pairs two long-running checks: continuous breach monitoring for the
|
||||
identifiers the user has registered, and behavioural location analysis that flags travel patterns
|
||||
inconsistent with the device's history. Both report as plain status lines — "no breaches found",
|
||||
"no unusual travel detected" — and both escalate out-of-band when they don't.</p>
|
||||
inconsistent with the device's history. Both report as plain status lines – "no breaches found",
|
||||
"no unusual travel detected" – and both escalate out-of-band when they don't.</p>
|
||||
<div class="sig-title">What the module exposes</div>
|
||||
<ul class="sigs">
|
||||
<li><b>Monitored identity count</b> — registered emails and numbers under watch</li>
|
||||
<li><b>Breach findings</b> — source, date and exposed field classes</li>
|
||||
<li><b>Travel anomaly verdict</b> — impossible-travel and velocity checks</li>
|
||||
<li><b>Escalation hooks</b> — webhook to your fraud platform on any positive finding</li>
|
||||
<li><b>Monitored identity count</b> – registered emails and numbers under watch</li>
|
||||
<li><b>Breach findings</b> – source, date and exposed field classes</li>
|
||||
<li><b>Travel anomaly verdict</b> – impossible-travel and velocity checks</li>
|
||||
<li><b>Escalation hooks</b> – webhook to your fraud platform on any positive finding</li>
|
||||
</ul>
|
||||
<div class="pillrow"><span class="pill">identity.monitor(email)</span><span class="pill">identity.breaches()</span><span class="pill">location.anomalies()</span></div>
|
||||
</div>
|
||||
@@ -530,7 +529,7 @@
|
||||
</div>
|
||||
<div class="ap-tog">
|
||||
<div class="ic" style="background:#16303a"><svg viewBox="0 0 24 24" fill="#7ab8e8"><path d="M6 2h8l4 4v16H6z"/><rect x="9" y="12" width="6" height="6" rx="1" fill="#16303a"/></svg></div>
|
||||
<div class="tx"><b>SIM & carrier</b><span>Phone-state access on — checking for SIM swaps.</span></div>
|
||||
<div class="tx"><b>SIM & carrier</b><span>Phone-state access on – checking for SIM swaps.</span></div>
|
||||
<div class="ap-sw js-sw3"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -563,16 +562,16 @@
|
||||
<div class="copy">
|
||||
<span class="kicker">Module 09 · Consent & settings</span>
|
||||
<h3>Every module is opt-in, and says why</h3>
|
||||
<p class="desc">Each protection module has its own toggle and its own justification — "phone-state access
|
||||
on — checking for SIM swaps" — so consent is informed and revocable at module granularity. The recovery
|
||||
<p class="desc">Each protection module has its own toggle and its own justification – "phone-state access
|
||||
on – checking for SIM swaps" – so consent is informed and revocable at module granularity. The recovery
|
||||
email is the deliberate design choice worth noting: a stolen handset can't be warned on its own screen,
|
||||
so location and SIM-change alerts go out-of-band to a verified address instead.</p>
|
||||
<div class="sig-title">What the module exposes</div>
|
||||
<ul class="sigs">
|
||||
<li><b>Per-module consent</b> — independent toggles with rationale strings you can localise</li>
|
||||
<li><b>Entitlement expiry</b> — subscription end date surfaced in-app</li>
|
||||
<li><b>Verified recovery channel</b> — email with code verification for out-of-band alerts</li>
|
||||
<li><b>Localisation</b> — all strings externalised, RTL supported</li>
|
||||
<li><b>Per-module consent</b> – independent toggles with rationale strings you can localise</li>
|
||||
<li><b>Entitlement expiry</b> – subscription end date surfaced in-app</li>
|
||||
<li><b>Verified recovery channel</b> – email with code verification for out-of-band alerts</li>
|
||||
<li><b>Localisation</b> – all strings externalised, RTL supported</li>
|
||||
</ul>
|
||||
<div class="pillrow"><span class="pill">consent.set(module, on)</span><span class="pill">recovery.verify(email)</span><span class="pill">entitlement.expiry</span></div>
|
||||
</div>
|
||||
|
||||
@@ -170,8 +170,8 @@ function showPage(id, navId) {
|
||||
btn.classList.add("on");
|
||||
});
|
||||
|
||||
// Company dropdown parent
|
||||
var co = ["p6", "p7", "p8", "p9"].indexOf(active) >= 0;
|
||||
// Company dropdown parent (News/p7 moved out to its own top-level nav item)
|
||||
var co = ["p6", "p8", "p9"].indexOf(active) >= 0;
|
||||
var coBtn = document.getElementById("nav-co");
|
||||
if (coBtn) {
|
||||
if (co) coBtn.classList.add("on");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1426,7 +1426,7 @@ textarea {
|
||||
}
|
||||
.stat-row-v {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
gap: 40px;
|
||||
padding: 18px 0;
|
||||
border-bottom: 1px solid var(--bdr);
|
||||
}
|
||||
@@ -1434,7 +1434,7 @@ textarea {
|
||||
border-bottom: none;
|
||||
}
|
||||
.sn-lg {
|
||||
font-size: 40px;
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
color: var(--ax);
|
||||
line-height: 1;
|
||||
|
||||
Reference in New Issue
Block a user