diff --git a/docs/.vitepress/theme/components/HeroVisual.vue b/docs/.vitepress/theme/components/HeroVisual.vue
new file mode 100644
index 0000000..44a399e
--- /dev/null
+++ b/docs/.vitepress/theme/components/HeroVisual.vue
@@ -0,0 +1,264 @@
+
+
+
+
+
+
+ // inbound · request inspection
+
+
+ live
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/HomeFeatureRail.vue b/docs/.vitepress/theme/components/HomeFeatureRail.vue
new file mode 100644
index 0000000..033e059
--- /dev/null
+++ b/docs/.vitepress/theme/components/HomeFeatureRail.vue
@@ -0,0 +1,157 @@
+
+
+
+
+
+ Capabilities
+ Engineered for production
+
+ Six guarantees that turn a daily scrape of upstream rules into something your traffic can actually
+ live behind — quietly, predictably, and without operator toil.
+
+
+
+
+
+ {{ f.eyebrow }}
+ {{ f.title }}
+ {{ f.body }}
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/HomeShowcase.vue b/docs/.vitepress/theme/components/HomeShowcase.vue
index fa0d8e4..24c3814 100644
--- a/docs/.vitepress/theme/components/HomeShowcase.vue
+++ b/docs/.vitepress/theme/components/HomeShowcase.vue
@@ -1,63 +1,62 @@
-
-
- Integrations
- Four web servers, one source of truth
-
- The same OWASP CRS rule set is converted into the native syntax of each platform —
- so you get equivalent protection whether you run Nginx, Apache, Traefik, or HAProxy.
-
+
+
+
-
-
-
-
600+
-
OWASP CRS patterns extracted
-
-
-
Daily
-
Automated rule refresh
-
-
-
4
-
Web server backends
-
-
-
MIT
-
Open-source license
-
-
-
+
+
+
+
{{ s.value }}
+
{{ s.label }}
+
{{ s.sub }}
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconApache.vue b/docs/.vitepress/theme/components/icons/IconApache.vue
index 0deda2a..5bb20ae 100644
--- a/docs/.vitepress/theme/components/icons/IconApache.vue
+++ b/docs/.vitepress/theme/components/icons/IconApache.vue
@@ -1,8 +1,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconHaproxy.vue b/docs/.vitepress/theme/components/icons/IconHaproxy.vue
index e6769ed..ffee542 100644
--- a/docs/.vitepress/theme/components/icons/IconHaproxy.vue
+++ b/docs/.vitepress/theme/components/icons/IconHaproxy.vue
@@ -1,9 +1,11 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconNginx.vue b/docs/.vitepress/theme/components/icons/IconNginx.vue
index 4f9c320..fcb1d54 100644
--- a/docs/.vitepress/theme/components/icons/IconNginx.vue
+++ b/docs/.vitepress/theme/components/icons/IconNginx.vue
@@ -1,6 +1,7 @@
-
-
-
-
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconTraefik.vue b/docs/.vitepress/theme/components/icons/IconTraefik.vue
index ab0e5fc..9ed3db6 100644
--- a/docs/.vitepress/theme/components/icons/IconTraefik.vue
+++ b/docs/.vitepress/theme/components/icons/IconTraefik.vue
@@ -1,5 +1,10 @@
-
-
-
+
+
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
index ef1009f..8f96ca2 100644
--- a/docs/.vitepress/theme/index.ts
+++ b/docs/.vitepress/theme/index.ts
@@ -1,6 +1,8 @@
-import { h } from 'vue'
+import { h, Fragment } from 'vue'
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
+import HeroVisual from './components/HeroVisual.vue'
+import HomeFeatureRail from './components/HomeFeatureRail.vue'
import HomeShowcase from './components/HomeShowcase.vue'
import './style.css'
@@ -8,7 +10,9 @@ export default {
extends: DefaultTheme,
Layout: () => {
return h(DefaultTheme.Layout, null, {
- 'home-features-after': () => h(HomeShowcase)
+ 'home-hero-image': () => h(HeroVisual),
+ 'home-features-after': () =>
+ h(Fragment, null, [h(HomeFeatureRail), h(HomeShowcase)])
})
}
} satisfies Theme
diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css
index 3c8424e..1e4d8fc 100644
--- a/docs/.vitepress/theme/style.css
+++ b/docs/.vitepress/theme/style.css
@@ -1,621 +1,641 @@
/* =============================================================
- Patterns Docs — Apple-native theme
- System fonts, refined typography, soft surfaces, glass nav.
+ Patterns Docs — refined home theme
+ v0/Vercel hairlines + Apple balance + Google data clarity.
============================================================= */
:root {
- /* Typography — system stack, identical to Apple's web properties */
- --vp-font-family-base:
- -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display",
- "Helvetica Neue", "Inter", "Segoe UI", Roboto, system-ui, sans-serif;
- --vp-font-family-mono:
- ui-monospace, "SF Mono", SFMono-Regular, "JetBrains Mono",
- Menlo, Consolas, "Liberation Mono", monospace;
+ /* Typography — system stack identical to Apple's web properties */
+ --vp-font-family-base:
+ -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display",
+ "Helvetica Neue", "Inter", "Segoe UI", Roboto, system-ui, sans-serif;
+ --vp-font-family-mono:
+ ui-monospace, "SF Mono", SFMono-Regular, "JetBrains Mono",
+ Menlo, Consolas, "Liberation Mono", monospace;
- /* Brand — Apple-system blue, refined */
- --apple-blue: #0071e3;
- --apple-blue-hover: #0077ed;
- --apple-blue-active: #006edb;
- --apple-cyan: #5ac8fa;
+ /* Brand */
+ --apple-blue: #0071e3;
+ --apple-blue-hover: #0077ed;
+ --apple-blue-active: #006edb;
+ --apple-red: #ff453a;
- /* Light surfaces */
- --vp-c-bg: #ffffff;
- --vp-c-bg-alt: #fbfbfd;
- --vp-c-bg-soft: #f5f5f7;
- --vp-c-bg-elv: #ffffff;
+ /* Light surfaces */
+ --vp-c-bg: #ffffff;
+ --vp-c-bg-alt: #fbfbfd;
+ --vp-c-bg-soft: #f5f5f7;
+ --vp-c-bg-elv: #ffffff;
- /* Borders — almost invisible, like Apple */
- --vp-c-divider: rgba(0, 0, 0, 0.07);
- --vp-c-border: rgba(0, 0, 0, 0.09);
- --vp-c-gutter: rgba(0, 0, 0, 0.06);
+ /* Borders — almost invisible */
+ --vp-c-divider: rgba(0, 0, 0, 0.08);
+ --vp-c-border: rgba(0, 0, 0, 0.10);
+ --vp-c-gutter: rgba(0, 0, 0, 0.06);
- /* Text — Apple uses near-black, high contrast */
- --vp-c-text-1: #1d1d1f;
- --vp-c-text-2: #515154;
- --vp-c-text-3: #86868b;
+ /* Text */
+ --vp-c-text-1: #1d1d1f;
+ --vp-c-text-2: #515154;
+ --vp-c-text-3: #86868b;
- /* Brand color slots used by VitePress */
- --vp-c-brand-1: var(--apple-blue);
- --vp-c-brand-2: var(--apple-blue-hover);
- --vp-c-brand-3: var(--apple-blue-active);
- --vp-c-brand-soft: rgba(0, 113, 227, 0.10);
+ /* Brand color slots */
+ --vp-c-brand-1: var(--apple-blue);
+ --vp-c-brand-2: var(--apple-blue-hover);
+ --vp-c-brand-3: var(--apple-blue-active);
+ --vp-c-brand-soft: rgba(0, 113, 227, 0.10);
- /* Tip / warning / etc — calmer than defaults */
- --vp-c-tip-1: #0071e3;
- --vp-c-tip-soft: rgba(0, 113, 227, 0.08);
- --vp-c-warning-1: #b25000;
- --vp-c-warning-soft: rgba(255, 159, 10, 0.10);
- --vp-c-danger-1: #c53030;
- --vp-c-danger-soft: rgba(255, 59, 48, 0.08);
+ /* Tip / warning */
+ --vp-c-tip-1: #0071e3;
+ --vp-c-tip-soft: rgba(0, 113, 227, 0.08);
+ --vp-c-warning-1: #b25000;
+ --vp-c-warning-soft: rgba(255, 159, 10, 0.10);
+ --vp-c-danger-1: #c53030;
+ --vp-c-danger-soft: rgba(255, 59, 48, 0.08);
- /* Layout rhythm */
- --vp-layout-max-width: 1440px;
- --vp-nav-height: 60px;
+ --vp-layout-max-width: 1440px;
+ --vp-nav-height: 60px;
- /* Radii — Apple loves soft, generous corners */
- --radius-xs: 6px;
- --radius-sm: 10px;
- --radius-md: 14px;
- --radius-lg: 20px;
- --radius-xl: 28px;
+ --radius-xs: 6px;
+ --radius-sm: 10px;
+ --radius-md: 14px;
+ --radius-lg: 18px;
+ --radius-xl: 24px;
- /* Elevation — soft, low-spread shadows */
- --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 3px rgba(0, 0, 0, 0.05);
- --shadow-2: 0 4px 16px -4px rgba(0, 0, 0, 0.08), 0 2px 6px -2px rgba(0, 0, 0, 0.04);
- --shadow-3: 0 16px 40px -12px rgba(0, 0, 0, 0.16), 0 4px 12px -4px rgba(0, 0, 0, 0.06);
+ --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 3px rgba(0, 0, 0, 0.05);
+ --shadow-2: 0 4px 16px -4px rgba(0, 0, 0, 0.08), 0 2px 6px -2px rgba(0, 0, 0, 0.04);
+ --shadow-3: 0 16px 40px -12px rgba(0, 0, 0, 0.16), 0 4px 12px -4px rgba(0, 0, 0, 0.06);
- /* Buttons / brand surface */
- --vp-button-brand-border: transparent;
- --vp-button-brand-text: #fff;
- --vp-button-brand-bg: var(--apple-blue);
- --vp-button-brand-hover-border: transparent;
- --vp-button-brand-hover-text: #fff;
- --vp-button-brand-hover-bg: var(--apple-blue-hover);
- --vp-button-brand-active-border: transparent;
- --vp-button-brand-active-text: #fff;
- --vp-button-brand-active-bg: var(--apple-blue-active);
+ /* Buttons */
+ --vp-button-brand-border: transparent;
+ --vp-button-brand-text: #fff;
+ --vp-button-brand-bg: var(--apple-blue);
+ --vp-button-brand-hover-border: transparent;
+ --vp-button-brand-hover-text: #fff;
+ --vp-button-brand-hover-bg: var(--apple-blue-hover);
+ --vp-button-brand-active-border: transparent;
+ --vp-button-brand-active-text: #fff;
+ --vp-button-brand-active-bg: var(--apple-blue-active);
- --vp-button-alt-border: transparent;
- --vp-button-alt-text: #1d1d1f;
- --vp-button-alt-bg: rgba(0, 0, 0, 0.06);
- --vp-button-alt-hover-bg: rgba(0, 0, 0, 0.09);
- --vp-button-alt-active-bg: rgba(0, 0, 0, 0.12);
+ --vp-button-alt-border: transparent;
+ --vp-button-alt-text: #1d1d1f;
+ --vp-button-alt-bg: rgba(0, 0, 0, 0.06);
+ --vp-button-alt-hover-bg: rgba(0, 0, 0, 0.09);
+ --vp-button-alt-active-bg: rgba(0, 0, 0, 0.12);
- /* Code blocks — soft surface in light, deep in dark */
- --vp-code-block-bg: #f5f5f7;
- --vp-code-block-color: #1d1d1f;
- --vp-code-line-highlight-color: rgba(0, 0, 0, 0.05);
- --vp-code-tab-bg: #f5f5f7;
- --vp-code-tab-divider: var(--vp-c-divider);
+ /* Code blocks — soft surface in light, deep in dark */
+ --vp-code-block-bg: #f5f5f7;
+ --vp-code-block-color: #1d1d1f;
+ --vp-code-line-highlight-color: rgba(0, 0, 0, 0.05);
+ --vp-code-tab-bg: #f5f5f7;
+ --vp-code-tab-divider: var(--vp-c-divider);
}
.dark {
- --vp-c-bg: #000000;
- --vp-c-bg-alt: #0a0a0c;
- --vp-c-bg-soft: #161618;
- --vp-c-bg-elv: #1c1c1e;
+ --vp-c-bg: #000000;
+ --vp-c-bg-alt: #0a0a0c;
+ --vp-c-bg-soft: #161618;
+ --vp-c-bg-elv: #131316;
- --vp-c-divider: rgba(255, 255, 255, 0.08);
- --vp-c-border: rgba(255, 255, 255, 0.10);
- --vp-c-gutter: rgba(255, 255, 255, 0.06);
+ --vp-c-divider: rgba(255, 255, 255, 0.08);
+ --vp-c-border: rgba(255, 255, 255, 0.12);
+ --vp-c-gutter: rgba(255, 255, 255, 0.06);
- --vp-c-text-1: #f5f5f7;
- --vp-c-text-2: #a1a1a6;
- --vp-c-text-3: #6e6e73;
+ --vp-c-text-1: #f5f5f7;
+ --vp-c-text-2: #a1a1a6;
+ --vp-c-text-3: #6e6e73;
- --apple-blue: #0a84ff;
- --apple-blue-hover: #409cff;
- --apple-blue-active: #0a84ff;
+ --apple-blue: #0a84ff;
+ --apple-blue-hover: #409cff;
+ --apple-blue-active: #0a84ff;
- --vp-c-brand-1: var(--apple-blue);
- --vp-c-brand-2: var(--apple-blue-hover);
- --vp-c-brand-3: var(--apple-blue-active);
- --vp-c-brand-soft: rgba(10, 132, 255, 0.16);
+ --vp-c-brand-1: var(--apple-blue);
+ --vp-c-brand-2: var(--apple-blue-hover);
+ --vp-c-brand-3: var(--apple-blue-active);
+ --vp-c-brand-soft: rgba(10, 132, 255, 0.18);
- --vp-c-tip-1: #0a84ff;
- --vp-c-tip-soft: rgba(10, 132, 255, 0.12);
- --vp-c-warning-1: #ffb340;
- --vp-c-warning-soft: rgba(255, 159, 10, 0.14);
- --vp-c-danger-1: #ff6961;
- --vp-c-danger-soft: rgba(255, 69, 58, 0.14);
+ --vp-c-tip-1: #0a84ff;
+ --vp-c-tip-soft: rgba(10, 132, 255, 0.12);
+ --vp-c-warning-1: #ffb340;
+ --vp-c-warning-soft: rgba(255, 159, 10, 0.14);
+ --vp-c-danger-1: #ff6961;
+ --vp-c-danger-soft: rgba(255, 69, 58, 0.14);
- --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4), 0 1px 3px rgba(0, 0, 0, 0.5);
- --shadow-2: 0 4px 16px -4px rgba(0, 0, 0, 0.6), 0 2px 6px -2px rgba(0, 0, 0, 0.4);
- --shadow-3: 0 16px 40px -12px rgba(0, 0, 0, 0.7), 0 4px 12px -4px rgba(0, 0, 0, 0.5);
+ --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4), 0 1px 3px rgba(0, 0, 0, 0.5);
+ --shadow-2: 0 4px 16px -4px rgba(0, 0, 0, 0.6), 0 2px 6px -2px rgba(0, 0, 0, 0.4);
+ --shadow-3: 0 16px 40px -12px rgba(0, 0, 0, 0.7), 0 4px 12px -4px rgba(0, 0, 0, 0.5);
- --vp-button-alt-text: #f5f5f7;
- --vp-button-alt-bg: rgba(255, 255, 255, 0.10);
- --vp-button-alt-hover-bg: rgba(255, 255, 255, 0.14);
- --vp-button-alt-active-bg: rgba(255, 255, 255, 0.18);
+ --vp-button-alt-text: #f5f5f7;
+ --vp-button-alt-bg: rgba(255, 255, 255, 0.10);
+ --vp-button-alt-hover-bg: rgba(255, 255, 255, 0.14);
+ --vp-button-alt-active-bg: rgba(255, 255, 255, 0.18);
- --vp-code-block-bg: #161618;
- --vp-code-block-color: #f5f5f7;
- --vp-code-line-highlight-color: rgba(255, 255, 255, 0.06);
- --vp-code-tab-bg: #161618;
- --vp-code-tab-divider: var(--vp-c-divider);
+ --vp-code-block-bg: #161618;
+ --vp-code-block-color: #f5f5f7;
+ --vp-code-line-highlight-color: rgba(255, 255, 255, 0.06);
+ --vp-code-tab-bg: #161618;
+ --vp-code-tab-divider: var(--vp-c-divider);
}
/* ---------- Global typography ---------- */
html {
- font-feature-settings: "kern", "liga", "calt", "ss01";
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
+ font-feature-settings: "kern", "liga", "calt", "ss01";
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
body {
- font-family: var(--vp-font-family-base);
- letter-spacing: -0.01em;
+ font-family: var(--vp-font-family-base);
+ letter-spacing: -0.01em;
}
.VPDoc h1 {
- letter-spacing: -0.025em;
- font-weight: 700;
- font-size: 2.4rem;
- line-height: 1.15;
+ letter-spacing: -0.025em;
+ font-weight: 700;
+ font-size: 2.4rem;
+ line-height: 1.15;
}
.VPDoc h2 {
- letter-spacing: -0.02em;
- font-weight: 650;
- margin-top: 3rem;
- padding-top: 1.5rem;
- border-top: 1px solid var(--vp-c-divider);
+ letter-spacing: -0.02em;
+ font-weight: 650;
+ margin-top: 3rem;
+ padding-top: 1.5rem;
+ border-top: 1px solid var(--vp-c-divider);
}
.VPDoc h3 {
- letter-spacing: -0.015em;
- font-weight: 600;
- margin-top: 2rem;
+ letter-spacing: -0.015em;
+ font-weight: 600;
+ margin-top: 2rem;
}
.VPDoc p,
.VPDoc li {
- font-size: 1rem;
- line-height: 1.7;
- color: var(--vp-c-text-1);
+ font-size: 1rem;
+ line-height: 1.7;
+ color: var(--vp-c-text-1);
}
.VPDoc a:not(.header-anchor) {
- text-decoration: none;
- font-weight: 500;
- color: var(--vp-c-brand-1);
- transition: color 0.15s ease;
+ text-decoration: none;
+ font-weight: 500;
+ color: var(--vp-c-brand-1);
+ transition: color 0.15s ease;
}
.VPDoc a:not(.header-anchor):hover {
- color: var(--vp-c-brand-2);
- text-decoration: underline;
- text-decoration-thickness: 1px;
- text-underline-offset: 3px;
+ color: var(--vp-c-brand-2);
+ text-decoration: underline;
+ text-decoration-thickness: 1px;
+ text-underline-offset: 3px;
}
/* ---------- Glass navigation ---------- */
.VPNav {
- background: transparent !important;
+ background: transparent !important;
}
.VPNavBar {
- background: rgba(255, 255, 255, 0.72) !important;
- -webkit-backdrop-filter: saturate(180%) blur(20px);
- backdrop-filter: saturate(180%) blur(20px);
- border-bottom: 1px solid var(--vp-c-divider) !important;
+ background: rgba(255, 255, 255, 0.72) !important;
+ -webkit-backdrop-filter: saturate(180%) blur(20px);
+ backdrop-filter: saturate(180%) blur(20px);
+ border-bottom: 1px solid var(--vp-c-divider) !important;
}
.dark .VPNavBar {
- background: rgba(0, 0, 0, 0.72) !important;
+ background: rgba(0, 0, 0, 0.72) !important;
}
.VPNavBar.has-sidebar .content-body {
- background: transparent !important;
+ background: transparent !important;
}
.VPNavBar .divider {
- display: none;
+ display: none;
}
.VPNavBarTitle .title {
- font-weight: 600;
- letter-spacing: -0.01em;
- border-bottom: none !important;
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ border-bottom: none !important;
}
.VPNavBarMenuLink,
.VPNavBarMenuGroup .text {
- font-size: 14px !important;
- font-weight: 450 !important;
- color: var(--vp-c-text-1) !important;
- opacity: 0.92;
+ font-size: 14px !important;
+ font-weight: 450 !important;
+ color: var(--vp-c-text-1) !important;
+ opacity: 0.9;
}
.VPNavBarMenuLink:hover,
.VPNavBarMenuGroup:hover .text {
- opacity: 1;
- color: var(--vp-c-text-1) !important;
+ opacity: 1;
+ color: var(--vp-c-text-1) !important;
}
/* ---------- Sidebar ---------- */
.VPSidebar {
- background: var(--vp-c-bg-alt) !important;
- border-right: 1px solid var(--vp-c-divider) !important;
+ background: var(--vp-c-bg-alt) !important;
+ border-right: 1px solid var(--vp-c-divider) !important;
}
.VPSidebarItem.level-0 > .item > .text {
- font-weight: 600 !important;
- letter-spacing: -0.005em;
- color: var(--vp-c-text-1) !important;
+ font-weight: 600 !important;
+ letter-spacing: -0.005em;
+ color: var(--vp-c-text-1) !important;
}
.VPSidebarItem .link .link-text {
- font-weight: 450;
- font-size: 14px;
+ font-weight: 450;
+ font-size: 14px;
}
.VPSidebarItem.is-active > .item .link-text {
- font-weight: 600 !important;
- color: var(--vp-c-brand-1) !important;
+ font-weight: 600 !important;
+ color: var(--vp-c-brand-1) !important;
}
/* ---------- Buttons ---------- */
.VPButton {
- border-radius: 980px !important;
- padding: 0 22px !important;
- height: 44px !important;
- line-height: 44px !important;
- font-weight: 500 !important;
- font-size: 15px !important;
- letter-spacing: -0.005em !important;
- transition: background-color 0.18s ease, transform 0.12s ease, box-shadow 0.18s ease !important;
+ border-radius: 980px !important;
+ padding: 0 22px !important;
+ height: 44px !important;
+ line-height: 44px !important;
+ font-weight: 500 !important;
+ font-size: 15px !important;
+ letter-spacing: -0.005em !important;
+ transition: background-color 0.18s ease, transform 0.12s ease, box-shadow 0.18s ease !important;
}
.VPButton.medium {
- height: 40px !important;
- line-height: 40px !important;
- font-size: 14px !important;
- padding: 0 18px !important;
+ height: 40px !important;
+ line-height: 40px !important;
+ font-size: 14px !important;
+ padding: 0 18px !important;
}
.VPButton.brand {
- box-shadow: 0 1px 2px rgba(0, 113, 227, 0.18);
+ box-shadow: 0 1px 2px rgba(0, 113, 227, 0.18);
}
.VPButton.brand:hover {
- transform: translateY(-1px);
- box-shadow: 0 4px 14px rgba(0, 113, 227, 0.30);
+ transform: translateY(-1px);
+ box-shadow: 0 4px 14px rgba(0, 113, 227, 0.25);
}
.VPButton.brand:active {
- transform: translateY(0);
+ transform: translateY(0);
}
/* ---------- Hero ---------- */
.VPHome .VPHero {
- padding: 96px 24px 72px !important;
+ padding: 88px 24px 56px !important;
}
.VPHero .container {
- max-width: 1120px !important;
+ max-width: 1200px !important;
+ gap: 48px !important;
+}
+.VPHero .main {
+ max-width: 580px;
}
.VPHero .name,
.VPHero .text {
- font-weight: 700 !important;
- letter-spacing: -0.04em !important;
- line-height: 1.05 !important;
- font-size: clamp(2.6rem, 5.4vw, 4.4rem) !important;
+ font-weight: 700 !important;
+ letter-spacing: -0.04em !important;
+ line-height: 1.04 !important;
+ font-size: clamp(2.4rem, 4.8vw, 3.8rem) !important;
}
.VPHero .name .clip {
- background: linear-gradient(120deg, #0a84ff 0%, #5ac8fa 50%, #0071e3 100%) !important;
- -webkit-background-clip: text !important;
- background-clip: text !important;
- -webkit-text-fill-color: transparent !important;
+ background: none !important;
+ -webkit-text-fill-color: var(--vp-c-text-1) !important;
+ color: var(--vp-c-text-1) !important;
}
.VPHero .tagline {
- margin-top: 22px !important;
- font-size: 1.25rem !important;
- font-weight: 400 !important;
- line-height: 1.5 !important;
- color: var(--vp-c-text-2) !important;
- letter-spacing: -0.005em !important;
- max-width: 720px;
+ margin-top: 22px !important;
+ font-size: 1.18rem !important;
+ font-weight: 400 !important;
+ line-height: 1.5 !important;
+ color: var(--vp-c-text-2) !important;
+ letter-spacing: -0.005em !important;
+ max-width: 540px;
}
.VPHero .actions {
- margin-top: 36px !important;
- gap: 12px !important;
+ margin-top: 36px !important;
+ gap: 12px !important;
}
.VPHero .image-container {
- transform: none !important;
+ transform: none !important;
+ margin: 0 !important;
+ width: auto !important;
+ height: auto !important;
}
.VPHero .image-bg {
- display: none !important;
+ display: none !important;
}
-.VPHero .image-src {
- max-width: 100% !important;
- filter: drop-shadow(0 30px 60px rgba(10, 132, 255, 0.18));
+.VPHero .image {
+ align-items: stretch !important;
+ justify-content: stretch !important;
+ margin: 0 !important;
+ flex-grow: 1;
}
-/* ---------- Features grid (default VitePress homepage) ---------- */
-
-.VPFeatures {
- padding: 8px 24px 80px !important;
-}
-.VPFeatures .container {
- max-width: 1120px !important;
-}
-.VPFeature {
- border: 1px solid var(--vp-c-divider) !important;
- background: var(--vp-c-bg-elv) !important;
- border-radius: var(--radius-lg) !important;
- padding: 28px !important;
- transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease !important;
- height: 100% !important;
- box-shadow: var(--shadow-1);
-}
-.VPFeature:hover {
- transform: translateY(-2px);
- box-shadow: var(--shadow-2);
- border-color: var(--vp-c-border) !important;
-}
-.VPFeature .icon {
- width: 52px !important;
- height: 52px !important;
- margin-bottom: 20px !important;
- background: var(--vp-c-brand-soft) !important;
- border-radius: 14px !important;
- font-size: 24px !important;
- display: inline-flex !important;
- align-items: center;
- justify-content: center;
- color: var(--vp-c-brand-1);
-}
-.VPFeature .title {
- font-size: 1.05rem !important;
- font-weight: 600 !important;
- letter-spacing: -0.01em !important;
- margin-bottom: 8px !important;
- color: var(--vp-c-text-1) !important;
-}
-.VPFeature .details {
- font-size: 0.94rem !important;
- line-height: 1.55 !important;
- color: var(--vp-c-text-2) !important;
+/* ---------- Default VitePress feature grid: kill it ---------- */
+/* We render our own HomeFeatureRail; if the frontmatter still has a */
+/* features array, hide the default grid so we don't double-render. */
+.VPHomeFeatures {
+ display: none !important;
}
/* ---------- Custom blocks (tip / warning) ---------- */
.custom-block {
- border-radius: var(--radius-md) !important;
- border: 1px solid var(--vp-c-divider) !important;
- padding: 16px 18px !important;
+ border-radius: var(--radius-md) !important;
+ border: 1px solid var(--vp-c-divider) !important;
+ padding: 16px 18px !important;
}
.custom-block.tip {
- border-color: var(--vp-c-brand-soft) !important;
- background: var(--vp-c-tip-soft) !important;
+ border-color: var(--vp-c-brand-soft) !important;
+ background: var(--vp-c-tip-soft) !important;
}
.custom-block.warning {
- background: var(--vp-c-warning-soft) !important;
- border-color: rgba(255, 159, 10, 0.18) !important;
+ background: var(--vp-c-warning-soft) !important;
+ border-color: rgba(255, 159, 10, 0.18) !important;
}
.custom-block .custom-block-title {
- font-weight: 600 !important;
- letter-spacing: -0.005em !important;
+ font-weight: 600 !important;
+ letter-spacing: -0.005em !important;
}
/* ---------- Code blocks ---------- */
.vp-code-group .tabs {
- border-radius: var(--radius-md) var(--radius-md) 0 0 !important;
+ border-radius: var(--radius-md) var(--radius-md) 0 0 !important;
}
div[class*="language-"] {
- border-radius: var(--radius-md) !important;
- box-shadow: var(--shadow-1);
+ border-radius: var(--radius-md) !important;
+ box-shadow: var(--shadow-1);
}
div[class*="language-"] code {
- font-feature-settings: "calt" 1, "liga" 0 !important;
- font-size: 13.5px !important;
- letter-spacing: -0.005em;
+ font-feature-settings: "calt" 1, "liga" 0 !important;
+ font-size: 13.5px !important;
+ letter-spacing: -0.005em;
}
-/* Inline code */
:not(pre) > code {
- background: var(--vp-c-bg-soft) !important;
- border: 1px solid var(--vp-c-divider) !important;
- border-radius: 6px !important;
- padding: 2px 6px !important;
- font-size: 0.88em !important;
- color: var(--vp-c-text-1) !important;
+ background: var(--vp-c-bg-soft) !important;
+ border: 1px solid var(--vp-c-divider) !important;
+ border-radius: 6px !important;
+ padding: 2px 6px !important;
+ font-size: 0.88em !important;
+ color: var(--vp-c-text-1) !important;
}
/* ---------- Tables ---------- */
.vp-doc table {
- border-collapse: separate !important;
- border-spacing: 0 !important;
- border: 1px solid var(--vp-c-divider) !important;
- border-radius: var(--radius-md) !important;
- overflow: hidden !important;
- display: table !important;
- width: 100% !important;
- margin: 24px 0 !important;
+ border-collapse: separate !important;
+ border-spacing: 0 !important;
+ border: 1px solid var(--vp-c-divider) !important;
+ border-radius: var(--radius-md) !important;
+ overflow: hidden !important;
+ display: table !important;
+ width: 100% !important;
+ margin: 24px 0 !important;
}
.vp-doc th {
- background: var(--vp-c-bg-soft) !important;
- font-weight: 600 !important;
- letter-spacing: -0.005em !important;
- font-size: 0.9rem !important;
- text-align: left !important;
+ background: var(--vp-c-bg-soft) !important;
+ font-weight: 600 !important;
+ letter-spacing: -0.005em !important;
+ font-size: 0.9rem !important;
+ text-align: left !important;
}
.vp-doc th,
.vp-doc td {
- border: none !important;
- border-bottom: 1px solid var(--vp-c-divider) !important;
- padding: 12px 16px !important;
+ border: none !important;
+ border-bottom: 1px solid var(--vp-c-divider) !important;
+ padding: 12px 16px !important;
}
.vp-doc tr:last-child td {
- border-bottom: none !important;
+ border-bottom: none !important;
}
.vp-doc tr {
- background: transparent !important;
+ background: transparent !important;
}
/* ---------- Footer ---------- */
.VPFooter {
- border-top: 1px solid var(--vp-c-divider) !important;
- padding: 28px 24px !important;
- background: var(--vp-c-bg-alt) !important;
+ border-top: 1px solid var(--vp-c-divider) !important;
+ padding: 28px 24px !important;
+ background: var(--vp-c-bg-alt) !important;
}
.VPFooter .message,
.VPFooter .copyright {
- font-size: 13px !important;
- color: var(--vp-c-text-3) !important;
+ font-size: 13px !important;
+ color: var(--vp-c-text-3) !important;
}
/* ---------- Search input ---------- */
.DocSearch-Button {
- border-radius: 980px !important;
- background: var(--vp-c-bg-soft) !important;
- border: 1px solid var(--vp-c-divider) !important;
- box-shadow: none !important;
- height: 36px !important;
+ border-radius: 980px !important;
+ background: var(--vp-c-bg-soft) !important;
+ border: 1px solid var(--vp-c-divider) !important;
+ box-shadow: none !important;
+ height: 36px !important;
}
-/* ---------- Subtle ambient gradient on home ---------- */
-
-.VPHome::before {
- content: "";
- position: absolute;
- inset: 0 0 auto 0;
- height: 720px;
- background:
- radial-gradient(60% 60% at 50% 0%, rgba(10, 132, 255, 0.10) 0%, transparent 70%),
- radial-gradient(40% 40% at 80% 10%, rgba(90, 200, 250, 0.08) 0%, transparent 70%);
- pointer-events: none;
- z-index: -1;
-}
-
-/* ---------- Custom home sections (used by HomeShowcase.vue) ---------- */
+/* ---------- Custom home sections ---------- */
.home-section {
- max-width: 1120px;
- margin: 0 auto;
- padding: 0 24px 96px;
+ max-width: 1120px;
+ margin: 0 auto;
+ padding: 24px 24px 80px;
}
-.home-section + .home-section {
- padding-top: 0;
+.home-section--stats {
+ padding-top: 0;
+ padding-bottom: 96px;
}
+
+.home-rail-head {
+ margin-bottom: 56px;
+ max-width: 720px;
+}
+
.home-eyebrow {
- font-size: 0.85rem;
- font-weight: 600;
- letter-spacing: 0.04em;
- text-transform: uppercase;
- color: var(--vp-c-brand-1);
- margin-bottom: 12px;
+ display: inline-block;
+ font-size: 0.78rem;
+ font-weight: 600;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ color: var(--vp-c-brand-1);
+ margin-bottom: 12px;
}
.home-title {
- font-size: clamp(1.75rem, 3.2vw, 2.4rem);
- font-weight: 650;
- letter-spacing: -0.025em;
- line-height: 1.15;
- margin: 0 0 12px;
- color: var(--vp-c-text-1);
+ font-size: clamp(1.6rem, 3.0vw, 2.2rem);
+ font-weight: 650;
+ letter-spacing: -0.025em;
+ line-height: 1.15;
+ margin: 0 0 14px;
+ color: var(--vp-c-text-1);
}
.home-lede {
- font-size: 1.1rem;
- line-height: 1.55;
- color: var(--vp-c-text-2);
- max-width: 720px;
- margin: 0 0 40px;
+ font-size: 1.05rem;
+ line-height: 1.55;
+ color: var(--vp-c-text-2);
+ max-width: 720px;
+ margin: 0;
}
+/* ---------- Platform showcase ---------- */
+
.platform-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
- gap: 16px;
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 0;
+ border-top: 1px solid var(--vp-c-divider);
+ border-bottom: 1px solid var(--vp-c-divider);
}
+
+@media (max-width: 900px) {
+ .platform-grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+@media (max-width: 520px) {
+ .platform-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
.platform-card {
- position: relative;
- display: block;
- padding: 24px;
- border-radius: var(--radius-lg);
- border: 1px solid var(--vp-c-divider);
- background: var(--vp-c-bg-elv);
- text-decoration: none;
- color: inherit;
- transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
+ --accent: var(--vp-c-brand-1);
+ position: relative;
+ display: block;
+ padding: 28px 24px 32px;
+ border-right: 1px solid var(--vp-c-divider);
+ text-decoration: none !important;
+ color: inherit;
+ transition: background-color 0.18s ease;
+}
+.platform-card:last-child {
+ border-right: none;
}
.platform-card:hover {
- transform: translateY(-2px);
- box-shadow: var(--shadow-2);
- border-color: var(--vp-c-border);
- text-decoration: none !important;
+ background: var(--vp-c-bg-alt);
}
+
+@media (max-width: 900px) {
+ .platform-card:nth-child(2n) {
+ border-right: none;
+ }
+ .platform-card:nth-child(-n+2) {
+ border-bottom: 1px solid var(--vp-c-divider);
+ }
+}
+@media (max-width: 520px) {
+ .platform-card {
+ border-right: none;
+ border-bottom: 1px solid var(--vp-c-divider);
+ }
+ .platform-card:last-child {
+ border-bottom: none;
+ }
+}
+
.platform-card .platform-icon {
- width: 44px;
- height: 44px;
- border-radius: 12px;
- background: var(--vp-c-brand-soft);
- color: var(--vp-c-brand-1);
- display: inline-flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 16px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 36px;
+ height: 36px;
+ margin-bottom: 18px;
+ color: var(--vp-c-text-2);
+ transition: color 0.2s ease, transform 0.2s ease;
+}
+.platform-card:hover .platform-icon {
+ color: var(--accent);
}
.platform-card .platform-icon svg {
- width: 24px;
- height: 24px;
+ width: 30px;
+ height: 30px;
}
.platform-card .platform-name {
- font-size: 1.05rem;
- font-weight: 600;
- letter-spacing: -0.01em;
- margin-bottom: 4px;
+ font-size: 1.05rem;
+ font-weight: 600;
+ letter-spacing: -0.012em;
+ color: var(--vp-c-text-1);
+ margin-bottom: 4px;
}
.platform-card .platform-meta {
- font-size: 0.88rem;
- color: var(--vp-c-text-3);
- margin-bottom: 14px;
+ font-family: var(--vp-font-family-mono);
+ font-size: 12px;
+ letter-spacing: -0.005em;
+ color: var(--vp-c-text-3);
}
.platform-card .platform-arrow {
- position: absolute;
- top: 24px;
- right: 24px;
- color: var(--vp-c-text-3);
- opacity: 0;
- transform: translateX(-4px);
- transition: opacity 0.2s ease, transform 0.2s ease;
+ position: absolute;
+ top: 28px;
+ right: 24px;
+ color: var(--vp-c-text-3);
+ opacity: 0;
+ transform: translateX(-4px);
+ transition: opacity 0.2s ease, transform 0.2s ease, color 0.2s ease;
}
.platform-card:hover .platform-arrow {
- opacity: 1;
- transform: translateX(0);
- color: var(--vp-c-brand-1);
+ opacity: 1;
+ transform: translateX(0);
+ color: var(--accent);
}
.platform-card .platform-arrow svg {
- width: 18px;
- height: 18px;
+ width: 18px;
+ height: 18px;
}
-/* ---------- Stat strip ---------- */
+/* ---------- Stats rail (Google data-driven) ---------- */
-.stats-strip {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
- gap: 24px;
- padding: 28px 32px;
- border-radius: var(--radius-lg);
- border: 1px solid var(--vp-c-divider);
- background: var(--vp-c-bg-alt);
+.stats-rail {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 0;
+ border-top: 1px solid var(--vp-c-divider);
+ border-bottom: 1px solid var(--vp-c-divider);
}
-.stat-item {
- text-align: left;
+
+@media (max-width: 720px) {
+ .stats-rail {
+ grid-template-columns: repeat(2, 1fr);
+ }
}
+
+.stat-cell {
+ padding: 28px 24px 32px;
+ border-right: 1px solid var(--vp-c-divider);
+}
+.stat-cell:last-child {
+ border-right: none;
+}
+@media (max-width: 720px) {
+ .stat-cell:nth-child(2n) { border-right: none; }
+ .stat-cell:nth-child(-n+2) { border-bottom: 1px solid var(--vp-c-divider); }
+}
+
.stat-value {
- font-size: 2rem;
- font-weight: 650;
- letter-spacing: -0.02em;
- color: var(--vp-c-text-1);
- line-height: 1.1;
+ font-size: 2.25rem;
+ font-weight: 600;
+ letter-spacing: -0.025em;
+ line-height: 1;
+ color: var(--vp-c-text-1);
+ font-variant-numeric: tabular-nums;
}
-.stat-label {
- font-size: 0.88rem;
- color: var(--vp-c-text-3);
- margin-top: 4px;
+.stat-key {
+ margin-top: 12px;
+ font-size: 0.92rem;
+ font-weight: 500;
+ color: var(--vp-c-text-1);
+ letter-spacing: -0.005em;
+}
+.stat-sub {
+ margin-top: 4px;
+ font-family: var(--vp-font-family-mono);
+ font-size: 11px;
+ color: var(--vp-c-text-3);
+ letter-spacing: 0.01em;
}
-/* ---------- Reduce motion respect ---------- */
+/* ---------- Reduce motion ---------- */
@media (prefers-reduced-motion: reduce) {
- * {
- transition-duration: 0s !important;
- animation-duration: 0s !important;
- }
+ * {
+ transition-duration: 0s !important;
+ animation-duration: 0s !important;
+ }
}
diff --git a/docs/index.md b/docs/index.md
index d37b164..9192ef9 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -5,9 +5,6 @@ hero:
name: Patterns
text: Production-grade WAF rules, on autopilot.
tagline: Automated OWASP Core Rule Set and bad-bot patterns, converted into native configurations for Nginx, Apache, Traefik, and HAProxy — refreshed every day.
- image:
- src: /hero-shield.svg
- alt: Patterns
actions:
- theme: brand
text: Get Started
@@ -15,53 +12,17 @@ hero:
- theme: alt
text: View on GitHub
link: https://github.com/fabriziosalmi/patterns
-
-features:
- - icon: ' '
- title: OWASP CRS Protection
- details: Defends against SQL injection, XSS, RCE, LFI, and RFI by deriving rules from the OWASP Core Rule Set — the same engine that powers ModSecurity worldwide.
- - icon: ' '
- title: Bad Bot Blocking
- details: Curated User-Agent lists from public sources block scrapers, AI crawlers, vulnerability scanners, and SEO spam — with configurable allow-lists for legitimate bots.
- - icon: ' '
- title: Native Multi-Server Output
- details: One source rule set, four idiomatic outputs — Nginx map+if, Apache SecRule, Traefik middleware TOML, and HAProxy ACL files.
- - icon: ' '
- title: Daily Automated Updates
- details: A GitHub Actions workflow re-fetches the latest CRS release, rebuilds every backend, and publishes a fresh release archive — without manual intervention.
- - icon: ' '
- title: Pre-Built Releases
- details: Drop-in archives are published on every run. Skip the toolchain — download nginx_waf.zip, apache_waf.zip, traefik_waf.zip, or haproxy_waf.zip.
- - icon: ' '
- title: Composable & Extensible
- details: Each backend is a small Python converter that consumes a single JSON intermediate. Adding a new platform is a few hundred lines — not a fork.
---
## Quick start
-Pull the latest release archive and include it in your existing server configuration — no toolchain required.
-
```bash
-# Pick the archive that matches your stack
curl -LO https://github.com/fabriziosalmi/patterns/releases/latest/download/nginx_waf.zip
unzip nginx_waf.zip -d /etc/nginx/waf_patterns
```
-Or build from source to customize before deploying:
-
-```bash
-git clone https://github.com/fabriziosalmi/patterns.git
-cd patterns
-pip install -r requirements.txt
-python owasp2json.py # Fetch the latest OWASP CRS
-python json2nginx.py # Or json2apache.py / json2traefik.py / json2haproxy.py
-python badbots.py # Generate bad-bot blocklists
-```
-
-::: tip Using Caddy?
-See the dedicated [caddy-waf](https://github.com/fabriziosalmi/caddy-waf) project for Caddy-specific WAF support.
-:::
+Or build from source — full toolchain instructions in [Getting Started](/getting-started).
diff --git a/docs/public/hero-shield.svg b/docs/public/hero-shield.svg
deleted file mode 100644
index 4db1cd7..0000000
--- a/docs/public/hero-shield.svg
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-