-
-
{% include logos/framework-soc2.html uid="nav-armor-trust-soc2" class="h-12 w-12" %}
-
{% include logos/framework-gdpr.html class="h-12 w-12" %}
+
+
+ {% include components/three-ops-rect.html height="160px" zoom="1" mount="data-nav-armor-ops" class="armor-preview-visual armor-preview-product absolute inset-0" %}
+
+
+ From Pro to Enterprise
+
-
-
Trust Center ⇗
-
SOC 2. GDPR. Security controls.
+
+
+
+
{% include logos/framework-soc2.html uid="nav-armor-preview-soc2" class="h-12 w-12" %}
+
{% include logos/framework-gdpr.html class="h-12 w-12" %}
+
@@ -257,6 +260,9 @@
+
+ Professional Security
+
Overview
@@ -312,6 +318,8 @@
const officeImage = document.querySelector('[data-office-image]');
const desktopNavMenu = document.getElementById('desktop-nav-menu');
const desktopFlyout = document.getElementById('desktop-nav-flyout');
+ const armorPreviewCard = desktopFlyout?.querySelector('[data-armor-preview-card]');
+ const armorPreviewLinks = desktopFlyout?.querySelectorAll('[data-armor-nav-link]') || [];
const desktopItems = desktopNavMenu?.querySelectorAll('[data-desktop-nav-item]') || [];
const desktopPanels = desktopFlyout?.querySelectorAll('[data-desktop-nav-panel]') || [];
const desktopCards = desktopFlyout?.querySelectorAll('.desktop-nav-card') || [];
@@ -335,6 +343,190 @@
let desktopActiveMenu = null;
let desktopActiveIndex = null;
let desktopCloseTimer = null;
+
+
+ const createNavArmorLightning = (canvas) => {
+ const ctx = canvas?.getContext?.('2d', { alpha: true });
+ if (!canvas || !ctx || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return null;
+ const state = { w: 0, h: 0, dpr: 1, bolts: [], raf: 0, active: false, lastStrikeAt: 0 };
+ const randBetween = (min, max) => min + Math.random() * (max - min);
+ const clamp = (v, min, max) => Math.min(max, Math.max(min, v));
+
+ const subdivide = (x1, y1, x2, y2, displacement, minDisp) => {
+ if (displacement < minDisp) return [{ x: x1, y: y1 }, { x: x2, y: y2 }];
+ const midX = (x1 + x2) / 2 + (Math.random() - 0.5) * displacement;
+ const midY = (y1 + y2) / 2 + (Math.random() - 0.5) * displacement;
+ const left = subdivide(x1, y1, midX, midY, displacement / 2, minDisp);
+ const right = subdivide(midX, midY, x2, y2, displacement / 2, minDisp);
+ return [...left.slice(0, -1), ...right];
+ };
+
+ const branchFrom = (startPoint, angle, length, depth) => {
+ const endX = startPoint.x + Math.cos(angle) * length;
+ const endY = startPoint.y + Math.sin(angle) * length;
+ const segments = subdivide(startPoint.x, startPoint.y, endX, endY, length * 0.45, 5);
+ const branches = [];
+ if (depth < 1 && Math.random() < 0.5) {
+ const branchIdx = Math.floor(Math.random() * (segments.length - 2)) + 1;
+ branches.push(branchFrom(segments[branchIdx], angle + (Math.random() - 0.5) * 1.6, length * 0.42, depth + 1));
+ }
+ return { segments, alpha: 0.72, decay: 0.07 + Math.random() * 0.03, width: Math.max(0.55, 1.2 - depth * 0.34), branches };
+ };
+
+ const createStrike = () => {
+ const { w, h } = state;
+ const anchorX = clamp(0.5 + (Math.random() - 0.5) * 0.35, 0.18, 0.82);
+ const startX = w * clamp(anchorX + (Math.random() - 0.5) * 0.18, 0.12, 0.88);
+ const startY = -h * randBetween(0.08, 0.22);
+ const endX = w * clamp(anchorX + (Math.random() - 0.5) * 0.42, 0.1, 0.9);
+ const endY = h * randBetween(0.62, 1.04);
+ const roughness = Math.hypot(endX - startX, endY - startY) * 0.36;
+ const segments = subdivide(startX, startY, endX, endY, roughness, 5);
+ const branches = [];
+ const angle = Math.atan2(endY - startY, endX - startX);
+ for (let i = 0; i < 2; i += 1) {
+ const idx = Math.floor(Math.random() * (segments.length - 2)) + 1;
+ branches.push(branchFrom(segments[idx], angle + (Math.random() - 0.5) * 2, Math.min(w, h) * randBetween(0.12, 0.24), 0));
+ }
+ return { segments, alpha: 0.82, decay: 0.07 + Math.random() * 0.025, width: 1.4 + Math.random() * 0.9, branches };
+ };
+
+ const collectPaths = (bolt, parentAlpha, paths) => {
+ const alpha = Math.min(parentAlpha, bolt.alpha);
+ if (alpha <= 0 || bolt.segments.length < 2) return;
+ paths.push({ segments: bolt.segments, alpha, width: bolt.width });
+ bolt.branches.forEach((branch) => collectPaths(branch, alpha * 0.58, paths));
+ };
+
+ const drawPath = (segments) => {
+ ctx.moveTo(segments[0].x, segments[0].y);
+ for (let i = 1; i < segments.length; i += 1) ctx.lineTo(segments[i].x, segments[i].y);
+ };
+
+ const resize = () => {
+ const rect = canvas.parentElement.getBoundingClientRect();
+ const dpr = Math.min(window.devicePixelRatio || 1, 2);
+ canvas.width = Math.max(1, Math.floor(rect.width * dpr));
+ canvas.height = Math.max(1, Math.floor(rect.height * dpr));
+ canvas.style.width = `${rect.width}px`;
+ canvas.style.height = `${rect.height}px`;
+ state.w = rect.width;
+ state.h = rect.height;
+ state.dpr = dpr;
+ };
+
+ const tick = (now) => {
+ const { w, h, dpr } = state;
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ ctx.clearRect(0, 0, w, h);
+ if (state.active && w && h && now - state.lastStrikeAt > randBetween(2100, 4200)) {
+ state.bolts.push(createStrike());
+ state.lastStrikeAt = now;
+ }
+ const paths = [];
+ state.bolts.forEach((bolt) => collectPaths(bolt, 1, paths));
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.globalCompositeOperation = 'lighter';
+ paths.forEach((path) => {
+ ctx.beginPath();
+ drawPath(path.segments);
+ ctx.strokeStyle = `rgba(236, 213, 63, ${path.alpha * 0.11})`;
+ ctx.lineWidth = 8;
+ ctx.stroke();
+ });
+ ctx.globalCompositeOperation = 'source-over';
+ paths.forEach((path) => {
+ ctx.beginPath();
+ drawPath(path.segments);
+ ctx.strokeStyle = `rgba(236, 213, 63, ${path.alpha * 0.55})`;
+ ctx.lineWidth = path.width;
+ ctx.stroke();
+ ctx.beginPath();
+ drawPath(path.segments);
+ ctx.strokeStyle = `rgba(255, 245, 180, ${path.alpha * 0.36})`;
+ ctx.lineWidth = path.width * 0.34;
+ ctx.stroke();
+ });
+ for (let i = state.bolts.length - 1; i >= 0; i -= 1) {
+ state.bolts[i].alpha -= state.bolts[i].decay;
+ if (state.bolts[i].alpha <= 0) state.bolts.splice(i, 1);
+ }
+ if (state.active || state.bolts.length) {
+ state.raf = window.requestAnimationFrame(tick);
+ } else {
+ state.raf = 0;
+ }
+ };
+
+ const start = () => {
+ resize();
+ state.active = true;
+ if (!state.bolts.length) state.bolts.push(createStrike());
+ if (!state.raf) state.raf = window.requestAnimationFrame(tick);
+ };
+ const stop = () => {
+ state.active = false;
+ };
+ window.addEventListener('resize', resize, { passive: true });
+ return { start, stop };
+ };
+
+ const getCurrentArmorPreviewLink = () => {
+ const pathname = window.location.pathname.replace(/\/+$/, '') || '/';
+ if (pathname === '/armor') {
+ return Array.from(armorPreviewLinks).find((link) => link.getAttribute('data-armor-preview-kind') === 'product') || null;
+ }
+ if (pathname === '/pricing') {
+ return Array.from(armorPreviewLinks).find((link) => link.getAttribute('data-armor-preview-kind') === 'pricing') || null;
+ }
+ return null;
+ };
+
+ const resetArmorPreview = () => {
+ if (!armorPreviewCard) return;
+ const currentLink = getCurrentArmorPreviewLink();
+ const kind = currentLink?.getAttribute('data-armor-preview-kind') || 'product';
+ armorPreviewCard.setAttribute('data-armor-preview-kind-current', kind);
+ armorPreviewCard.setAttribute('href', currentLink?.getAttribute('data-armor-preview-href') || '/armor');
+ const target = currentLink?.getAttribute('data-armor-preview-target');
+ if (target) {
+ armorPreviewCard.setAttribute('target', target);
+ armorPreviewCard.setAttribute('rel', 'noopener noreferrer');
+ } else {
+ armorPreviewCard.removeAttribute('target');
+ armorPreviewCard.removeAttribute('rel');
+ }
+ armorPreviewLinks.forEach((previewLink) => {
+ previewLink.toggleAttribute('data-armor-preview-active', previewLink === currentLink);
+ });
+ };
+
+ const setArmorPreview = (link) => {
+ if (!link || !armorPreviewCard) {
+ resetArmorPreview();
+ return;
+ }
+ armorPreviewCard.setAttribute('data-armor-preview-kind-current', link.getAttribute('data-armor-preview-kind') || 'product');
+ armorPreviewCard.setAttribute('href', link.getAttribute('data-armor-preview-href') || link.getAttribute('href') || '/armor');
+ const target = link.getAttribute('data-armor-preview-target');
+ if (target) {
+ armorPreviewCard.setAttribute('target', target);
+ armorPreviewCard.setAttribute('rel', 'noopener noreferrer');
+ } else {
+ armorPreviewCard.removeAttribute('target');
+ armorPreviewCard.removeAttribute('rel');
+ }
+ armorPreviewLinks.forEach((previewLink) => {
+ previewLink.toggleAttribute('data-armor-preview-active', previewLink === link);
+ });
+ };
+
+ armorPreviewLinks.forEach((link) => {
+ link.addEventListener('pointerenter', () => setArmorPreview(link));
+ link.addEventListener('focusin', () => setArmorPreview(link));
+ });
+ resetArmorPreview();
const clearDesktopClose = () => {
if (desktopCloseTimer) {
@@ -356,6 +548,12 @@
? Array.from(desktopPanels).find((panel) => panel.getAttribute('data-desktop-nav-panel') === desktopActiveMenu)
: null;
const isSamePanel = desktopActiveMenu === menuName;
+ if (menuName !== 'armor' && desktopActiveMenu === 'armor') {
+ resetArmorPreview();
+ } else if (menuName === 'armor' && desktopActiveMenu !== 'armor') {
+ resetArmorPreview();
+ }
+
desktopActiveMenu = menuName;
desktopActiveIndex = activeIndex;
@@ -406,6 +604,7 @@
}
desktopFlyout.classList.add('is-open');
+ desktopFlyout.classList.toggle('is-armor', menuName === 'armor');
desktopFlyout.style.opacity = '1';
desktopFlyout.style.pointerEvents = 'auto';
desktopFlyout.style.transform = 'translateX(-50%) translateY(0)';
@@ -415,7 +614,9 @@
const closeDesktopNav = () => {
desktopActiveMenu = null;
desktopActiveIndex = null;
+ resetArmorPreview();
desktopFlyout?.classList.remove('is-open');
+ desktopFlyout?.classList.remove('is-armor');
if (desktopFlyout) {
desktopFlyout.style.opacity = '0';
desktopFlyout.style.pointerEvents = 'none';
diff --git a/_includes/components/three-buckler-rect.html b/_includes/components/three-buckler-rect.html
index dbd8b2a4..aa20a114 100644
--- a/_includes/components/three-buckler-rect.html
+++ b/_includes/components/three-buckler-rect.html
@@ -1,6 +1,6 @@