From 41aecb7425232c9eeed71bb73bd278eaa93c9366 Mon Sep 17 00:00:00 2001 From: Roussange Alexandre Date: Thu, 7 May 2026 15:51:10 +0200 Subject: [PATCH 01/17] feat(ui): apply CrowdSec Docs visual refresh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces the new design system and component library based on the mockup. Dark-first with full light-mode support via CSS tokens. Design tokens & typography - src/css/crowdsec-tokens.css: --cs-* token file (surfaces, ink, brand colors, Infima bridge vars) dual-themed dark/light - src/css/crowdsec-typography.css: h1/h2/h3 scale, §NN auto-counter, lede paragraph, .cs-eyebrow utility - src/css/crowdsec-algolia.css: Algolia DocSearch re-skinned to --cs-* - src/css/custom.css: import new CSS files, drop conflicting Infima override blocks, update blockquote and link color to token vars Config - docusaurus.config.ts: add JetBrains Mono font, set respectPrefersColorScheme: false (dark default enforced) Components (src/components/docs/) - 10 new React components with co-located CSS Modules (no hex in code): DocsHero, PathCard/PathCards, RunningStrip, PathwayRow, GuidedSetupCard, PopularChips, ChallengeGrid, AccessCard, ConsoleMockup, PromoCard (console/cti/engine variants) - 11 SVG icon components in icons/ (custom stroke set, no icon lib) Theme swizzles - src/theme/MDXComponents.js: register all new components globally - src/theme/DocItem/Content/index.tsx: render eyebrow frontmatter above h1 - src/theme/TOC/index.tsx: inject PromoCard in right rail, variant selected by URL path (no useDoc() to stay safe in blog context) Pages - src/pages/index.tsx: rebuilt with new components; all hrefs preserved - unversioned/console/intro.mdx: eyebrow/rightRailPromo frontmatter, ChallengeGrid for the 5 security challenge items, AccessCard for signup --- crowdsec-docs/docusaurus.config.ts | 6 +- .../docs/AccessCard/index.module.css | 74 ++ .../src/components/docs/AccessCard/index.tsx | 37 + .../docs/ChallengeGrid/index.module.css | 56 + .../components/docs/ChallengeGrid/index.tsx | 36 + .../docs/ConsoleMockup/index.module.css | 34 + .../components/docs/ConsoleMockup/index.tsx | 111 ++ .../components/docs/DocsHero/index.module.css | 75 ++ .../src/components/docs/DocsHero/index.tsx | 24 + .../docs/GuidedSetupCard/index.module.css | 93 ++ .../components/docs/GuidedSetupCard/index.tsx | 40 + .../components/docs/PathCard/index.module.css | 69 ++ .../src/components/docs/PathCard/index.tsx | 39 + .../docs/PathCards/index.module.css | 18 + .../src/components/docs/PathCards/index.tsx | 10 + .../docs/PathwayRow/index.module.css | 134 +++ .../src/components/docs/PathwayRow/index.tsx | 76 ++ .../docs/PopularChips/index.module.css | 39 + .../components/docs/PopularChips/index.tsx | 24 + .../docs/PromoCard/index.module.css | 73 ++ .../src/components/docs/PromoCard/index.tsx | 116 +++ .../docs/RunningStrip/index.module.css | 50 + .../components/docs/RunningStrip/index.tsx | 24 + .../src/components/docs/icons/AlertIcon.tsx | 11 + .../components/docs/icons/BarChartIcon.tsx | 11 + .../components/docs/icons/BlocklistIcon.tsx | 12 + .../src/components/docs/icons/CheckIcon.tsx | 9 + .../src/components/docs/icons/ConsoleIcon.tsx | 11 + .../src/components/docs/icons/CtiIcon.tsx | 11 + .../src/components/docs/icons/GlobeIcon.tsx | 11 + .../src/components/docs/icons/LockIcon.tsx | 10 + .../src/components/docs/icons/SearchIcon.tsx | 10 + .../src/components/docs/icons/ShieldIcon.tsx | 9 + .../src/components/docs/icons/UsersIcon.tsx | 12 + crowdsec-docs/src/css/crowdsec-algolia.css | 60 ++ crowdsec-docs/src/css/crowdsec-tokens.css | 192 ++++ crowdsec-docs/src/css/crowdsec-typography.css | 98 ++ crowdsec-docs/src/css/custom.css | 69 +- crowdsec-docs/src/pages/index.tsx | 980 +++++------------- .../src/theme/DocItem/Content/index.tsx | 16 + crowdsec-docs/src/theme/MDXComponents.js | 25 + crowdsec-docs/src/theme/TOC/index.tsx | 48 +- crowdsec-docs/unversioned/console/intro.mdx | 55 +- 43 files changed, 2098 insertions(+), 820 deletions(-) create mode 100644 crowdsec-docs/src/components/docs/AccessCard/index.module.css create mode 100644 crowdsec-docs/src/components/docs/AccessCard/index.tsx create mode 100644 crowdsec-docs/src/components/docs/ChallengeGrid/index.module.css create mode 100644 crowdsec-docs/src/components/docs/ChallengeGrid/index.tsx create mode 100644 crowdsec-docs/src/components/docs/ConsoleMockup/index.module.css create mode 100644 crowdsec-docs/src/components/docs/ConsoleMockup/index.tsx create mode 100644 crowdsec-docs/src/components/docs/DocsHero/index.module.css create mode 100644 crowdsec-docs/src/components/docs/DocsHero/index.tsx create mode 100644 crowdsec-docs/src/components/docs/GuidedSetupCard/index.module.css create mode 100644 crowdsec-docs/src/components/docs/GuidedSetupCard/index.tsx create mode 100644 crowdsec-docs/src/components/docs/PathCard/index.module.css create mode 100644 crowdsec-docs/src/components/docs/PathCard/index.tsx create mode 100644 crowdsec-docs/src/components/docs/PathCards/index.module.css create mode 100644 crowdsec-docs/src/components/docs/PathCards/index.tsx create mode 100644 crowdsec-docs/src/components/docs/PathwayRow/index.module.css create mode 100644 crowdsec-docs/src/components/docs/PathwayRow/index.tsx create mode 100644 crowdsec-docs/src/components/docs/PopularChips/index.module.css create mode 100644 crowdsec-docs/src/components/docs/PopularChips/index.tsx create mode 100644 crowdsec-docs/src/components/docs/PromoCard/index.module.css create mode 100644 crowdsec-docs/src/components/docs/PromoCard/index.tsx create mode 100644 crowdsec-docs/src/components/docs/RunningStrip/index.module.css create mode 100644 crowdsec-docs/src/components/docs/RunningStrip/index.tsx create mode 100644 crowdsec-docs/src/components/docs/icons/AlertIcon.tsx create mode 100644 crowdsec-docs/src/components/docs/icons/BarChartIcon.tsx create mode 100644 crowdsec-docs/src/components/docs/icons/BlocklistIcon.tsx create mode 100644 crowdsec-docs/src/components/docs/icons/CheckIcon.tsx create mode 100644 crowdsec-docs/src/components/docs/icons/ConsoleIcon.tsx create mode 100644 crowdsec-docs/src/components/docs/icons/CtiIcon.tsx create mode 100644 crowdsec-docs/src/components/docs/icons/GlobeIcon.tsx create mode 100644 crowdsec-docs/src/components/docs/icons/LockIcon.tsx create mode 100644 crowdsec-docs/src/components/docs/icons/SearchIcon.tsx create mode 100644 crowdsec-docs/src/components/docs/icons/ShieldIcon.tsx create mode 100644 crowdsec-docs/src/components/docs/icons/UsersIcon.tsx create mode 100644 crowdsec-docs/src/css/crowdsec-algolia.css create mode 100644 crowdsec-docs/src/css/crowdsec-tokens.css create mode 100644 crowdsec-docs/src/css/crowdsec-typography.css create mode 100644 crowdsec-docs/src/theme/DocItem/Content/index.tsx create mode 100644 crowdsec-docs/src/theme/MDXComponents.js diff --git a/crowdsec-docs/docusaurus.config.ts b/crowdsec-docs/docusaurus.config.ts index fdd185d17..af32008f4 100644 --- a/crowdsec-docs/docusaurus.config.ts +++ b/crowdsec-docs/docusaurus.config.ts @@ -251,6 +251,10 @@ const config: Config = { { href: "https://fonts.googleapis.com/icon?family=Material+Icons", }, + { + href: "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap", + rel: "stylesheet", + }, ], themes: ["@docusaurus/theme-mermaid"], themeConfig: { @@ -258,7 +262,7 @@ const config: Config = { colorMode: { defaultMode: "dark", disableSwitch: false, - respectPrefersColorScheme: true, + respectPrefersColorScheme: false, }, announcementBar: { id: "banner_docs", diff --git a/crowdsec-docs/src/components/docs/AccessCard/index.module.css b/crowdsec-docs/src/components/docs/AccessCard/index.module.css new file mode 100644 index 000000000..c0baffe63 --- /dev/null +++ b/crowdsec-docs/src/components/docs/AccessCard/index.module.css @@ -0,0 +1,74 @@ +.card { + display: flex; + flex-direction: column; + gap: 12px; + padding: 20px 22px; + background: var(--cs-surface); + border: 1px solid var(--cs-border-hi); + border-radius: 12px; + margin: 16px 0; +} + +.top { + display: flex; + align-items: center; + gap: 12px; +} + +.iconWrap { + width: 38px; + height: 38px; + border-radius: 9px; + background: var(--cs-orange-soft); + border: 1px solid color-mix(in srgb, var(--cs-orange) 25%, transparent); + display: flex; + align-items: center; + justify-content: center; + color: var(--cs-orange); + flex-shrink: 0; +} + +.iconWrap svg { + width: 18px; + height: 18px; +} + +.title { + font-size: 14.5px; + font-weight: 600; + color: var(--cs-ink); +} + +.command { + display: flex; + align-items: center; + gap: 8px; + padding: 9px 14px; + background: var(--cs-bg); + border: 1px solid var(--cs-border); + border-radius: 7px; + font-family: var(--cs-font-mono); + font-size: 12.5px; + color: var(--cs-teal); +} + +.cta { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 18px; + border-radius: 7px; + background: var(--cs-orange); + color: #0A1120; + font-size: 13px; + font-weight: 600; + text-decoration: none; + transition: opacity 0.15s; + align-self: flex-start; +} + +.cta:hover { + opacity: 0.85; + text-decoration: none; + color: #0A1120; +} diff --git a/crowdsec-docs/src/components/docs/AccessCard/index.tsx b/crowdsec-docs/src/components/docs/AccessCard/index.tsx new file mode 100644 index 000000000..97a775a27 --- /dev/null +++ b/crowdsec-docs/src/components/docs/AccessCard/index.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import styles from './index.module.css'; + +type Props = { + icon?: React.ReactNode; + title: string; + command?: string; + ctaLabel: string; + ctaHref: string; +}; + +function DefaultIcon() { + return ( + + ); +} + +export default function AccessCard({ icon, title, command, ctaLabel, ctaHref }: Props) { + return ( +
+
+
{icon ?? }
+
{title}
+
+ {command && ( +
+ $ + {command} +
+ )} + {ctaLabel} → +
+ ); +} diff --git a/crowdsec-docs/src/components/docs/ChallengeGrid/index.module.css b/crowdsec-docs/src/components/docs/ChallengeGrid/index.module.css new file mode 100644 index 000000000..c1a99645d --- /dev/null +++ b/crowdsec-docs/src/components/docs/ChallengeGrid/index.module.css @@ -0,0 +1,56 @@ +.grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 14px; + margin: 20px 0; +} + +.cell { + display: flex; + flex-direction: column; + gap: 10px; + padding: 18px; + background: var(--cs-surface); + border: 1px solid var(--cs-border); + border-radius: 12px; +} + +.iconWrap { + width: 36px; + height: 36px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.iconWrap svg { + width: 18px; + height: 18px; +} + +.title { + font-size: 13.5px; + font-weight: 600; + color: var(--cs-ink); + line-height: 1.3; +} + +.body { + font-size: 12.5px; + color: var(--cs-ink-dim); + line-height: 1.55; +} + +@media (max-width: 800px) { + .grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 480px) { + .grid { + grid-template-columns: 1fr; + } +} diff --git a/crowdsec-docs/src/components/docs/ChallengeGrid/index.tsx b/crowdsec-docs/src/components/docs/ChallengeGrid/index.tsx new file mode 100644 index 000000000..b2f6b4eb8 --- /dev/null +++ b/crowdsec-docs/src/components/docs/ChallengeGrid/index.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import styles from './index.module.css'; + +export type Challenge = { + icon: React.ReactNode; + color: string; + title: string; + body: string; +}; + +type Props = { + challenges: Challenge[]; +}; + +export default function ChallengeGrid({ challenges }: Props) { + return ( +
+ {challenges.map((c, i) => ( +
+
+ {c.icon} +
+
{c.title}
+
{c.body}
+
+ ))} +
+ ); +} diff --git a/crowdsec-docs/src/components/docs/ConsoleMockup/index.module.css b/crowdsec-docs/src/components/docs/ConsoleMockup/index.module.css new file mode 100644 index 000000000..cab5909a6 --- /dev/null +++ b/crowdsec-docs/src/components/docs/ConsoleMockup/index.module.css @@ -0,0 +1,34 @@ +.wrap { + border-radius: 12px; + overflow: hidden; + border: 1px solid var(--cs-border-hi); + background: var(--cs-surface); + margin: 24px 0; +} + +.titleBar { + display: flex; + align-items: center; + gap: 6px; + padding: 10px 16px; + background: var(--cs-surface-2); + border-bottom: 1px solid var(--cs-border); +} + +.dot { + width: 10px; + height: 10px; + border-radius: 50%; +} + +.titleText { + margin-left: 8px; + font-family: var(--cs-font-mono); + font-size: 11px; + color: var(--cs-ink-mute); +} + +.svg { + width: 100%; + display: block; +} diff --git a/crowdsec-docs/src/components/docs/ConsoleMockup/index.tsx b/crowdsec-docs/src/components/docs/ConsoleMockup/index.tsx new file mode 100644 index 000000000..d6f6e3b86 --- /dev/null +++ b/crowdsec-docs/src/components/docs/ConsoleMockup/index.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import styles from './index.module.css'; + +export default function ConsoleMockup() { + return ( +
+
+
+
+
+ app.crowdsec.net — Console +
+ + {/* Background */} + + + {/* Left sidebar */} + + {/* Logo area */} + + C + + {/* Nav items */} + {[60, 90, 120, 150, 180, 210].map((y, i) => ( + + + + + + ))} + + {/* Top bar */} + + + + + + + + {/* Stat cards row */} + {[ + { x: 172, label: 'Alerts Today', value: '1,284', color: 'var(--cs-orange)' }, + { x: 340, label: 'Blocked IPs', value: '3,921', color: 'var(--cs-teal)' }, + { x: 508, label: 'Decisions', value: '8,406', color: 'var(--cs-violet)' }, + { x: 676, label: 'CTI Score', value: 'A+', color: 'var(--cs-blue)' }, + ].map((card) => ( + + + {card.label.toUpperCase()} + {card.value} + {/* Sparkline */} + + + ))} + + {/* Main chart area */} + + ALERTS OVER TIME + {/* Chart bars */} + {[0,1,2,3,4,5,6,7,8,9,10,11].map((i) => { + const heights = [40, 65, 50, 80, 55, 90, 45, 70, 60, 85, 50, 75]; + return ( + + ); + })} + {/* X-axis labels */} + {['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'].map((m, i) => ( + {m} + ))} + + {/* Right panel */} + + TOP THREATS + {[ + { label: 'SSH Bruteforce', pct: 82, color: 'var(--cs-orange)' }, + { label: 'Port Scan', pct: 61, color: 'var(--cs-violet)' }, + { label: 'HTTP Flood', pct: 45, color: 'var(--cs-teal)' }, + { label: 'CVE Exploit', pct: 34, color: 'var(--cs-pink)' }, + { label: 'Credential Stuff', pct: 22, color: 'var(--cs-blue)' }, + ].map((row, i) => ( + + {row.label} + + + + ))} + +
+ ); +} diff --git a/crowdsec-docs/src/components/docs/DocsHero/index.module.css b/crowdsec-docs/src/components/docs/DocsHero/index.module.css new file mode 100644 index 000000000..3802f8b4b --- /dev/null +++ b/crowdsec-docs/src/components/docs/DocsHero/index.module.css @@ -0,0 +1,75 @@ +.hero { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + padding: 48px 0 40px; + max-width: 760px; +} + +.glyph { + width: 56px; + height: 56px; + border-radius: 14px; + background: var(--cs-orange-soft); + border: 1px solid color-mix(in srgb, var(--cs-orange) 30%, transparent); + display: flex; + align-items: center; + justify-content: center; + color: var(--cs-orange); + font-size: 26px; + font-weight: 800; + font-family: var(--cs-font-sans); + flex-shrink: 0; +} + +.title { + font-size: 44px; + font-weight: 700; + letter-spacing: -0.025em; + line-height: 1.1; + color: var(--cs-ink); + text-wrap: balance; + margin: 0; +} + +.tagline { + font-size: 17px; + color: var(--cs-ink-dim); + line-height: 1.6; + max-width: 580px; + margin: 0; +} + +.cta { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 22px; + border-radius: 8px; + background: var(--cs-orange); + color: #0A1120; + font-weight: 600; + font-size: 14px; + text-decoration: none; + transition: opacity 0.15s, transform 0.15s; + border: none; + cursor: pointer; +} + +.cta:hover { + opacity: 0.88; + transform: translateY(-1px); + text-decoration: none; + color: #0A1120; +} + +@media (max-width: 768px) { + .hero { + padding: 32px 0 24px; + } + + .title { + font-size: 32px; + } +} diff --git a/crowdsec-docs/src/components/docs/DocsHero/index.tsx b/crowdsec-docs/src/components/docs/DocsHero/index.tsx new file mode 100644 index 000000000..dddf7fc4f --- /dev/null +++ b/crowdsec-docs/src/components/docs/DocsHero/index.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import styles from './index.module.css'; + +type Props = { + title: string; + tagline?: string; + ctaLabel?: string; + ctaHref?: string; +}; + +export default function DocsHero({ title, tagline, ctaLabel, ctaHref }: Props) { + return ( +
+ +

{title}

+ {tagline &&

{tagline}

} + {ctaLabel && ctaHref && ( + + {ctaLabel} → + + )} +
+ ); +} diff --git a/crowdsec-docs/src/components/docs/GuidedSetupCard/index.module.css b/crowdsec-docs/src/components/docs/GuidedSetupCard/index.module.css new file mode 100644 index 000000000..7e8166a1a --- /dev/null +++ b/crowdsec-docs/src/components/docs/GuidedSetupCard/index.module.css @@ -0,0 +1,93 @@ +.card { + display: flex; + flex-direction: column; + gap: 14px; + padding: 28px; + background: var(--cs-surface-2); + border: 1px solid var(--cs-border-hi); + border-radius: 14px; + margin: 28px 0; +} + +.top { + display: flex; + align-items: center; + gap: 14px; +} + +.iconWrap { + width: 44px; + height: 44px; + border-radius: 10px; + background: var(--cs-orange-soft); + border: 1px solid color-mix(in srgb, var(--cs-orange) 25%, transparent); + display: flex; + align-items: center; + justify-content: center; + color: var(--cs-orange); + flex-shrink: 0; +} + +.iconWrap svg { + width: 22px; + height: 22px; +} + +.title { + font-size: 16px; + font-weight: 700; + color: var(--cs-ink); +} + +.desc { + font-size: 13.5px; + color: var(--cs-ink-dim); + line-height: 1.6; +} + +.actions { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.primaryCta { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 9px 20px; + border-radius: 8px; + background: var(--cs-orange); + color: #0A1120; + font-size: 13.5px; + font-weight: 600; + text-decoration: none; + transition: opacity 0.15s; +} + +.primaryCta:hover { + opacity: 0.85; + text-decoration: none; + color: #0A1120; +} + +.secondaryCta { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 9px 20px; + border-radius: 8px; + background: transparent; + color: var(--cs-ink-dim); + border: 1px solid var(--cs-border-hi); + font-size: 13.5px; + font-weight: 500; + text-decoration: none; + transition: border-color 0.15s, color 0.15s; +} + +.secondaryCta:hover { + border-color: var(--cs-orange); + color: var(--cs-orange); + text-decoration: none; +} diff --git a/crowdsec-docs/src/components/docs/GuidedSetupCard/index.tsx b/crowdsec-docs/src/components/docs/GuidedSetupCard/index.tsx new file mode 100644 index 000000000..af482d951 --- /dev/null +++ b/crowdsec-docs/src/components/docs/GuidedSetupCard/index.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import styles from './index.module.css'; + +type Cta = { label: string; href: string }; + +type Props = { + title: string; + desc: string; + primaryCta: Cta; + secondaryCta?: Cta; + icon?: React.ReactNode; +}; + +function DefaultIcon() { + return ( + + ); +} + +export default function GuidedSetupCard({ title, desc, primaryCta, secondaryCta, icon }: Props) { + return ( +
+
+
{icon ?? }
+
{title}
+
+

{desc}

+
+ {primaryCta.label} → + {secondaryCta && ( + {secondaryCta.label} + )} +
+
+ ); +} diff --git a/crowdsec-docs/src/components/docs/PathCard/index.module.css b/crowdsec-docs/src/components/docs/PathCard/index.module.css new file mode 100644 index 000000000..e8429ec04 --- /dev/null +++ b/crowdsec-docs/src/components/docs/PathCard/index.module.css @@ -0,0 +1,69 @@ +.card { + display: flex; + flex-direction: column; + padding: 24px; + background: var(--cs-surface); + border: 1px solid var(--cs-border); + border-left: 3px solid var(--card-color, var(--cs-orange)); + border-radius: 12px; + text-decoration: none; + color: var(--cs-ink); + transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s; + flex: 1; + min-width: 0; +} + +.card:hover { + text-decoration: none; + color: var(--cs-ink); + transform: translateY(-2px); +} + +.iconWrap { + width: 44px; + height: 44px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + margin-bottom: 14px; + background: var(--cs-icon-soft, var(--cs-orange-soft)); + color: var(--card-color, var(--cs-orange)); +} + +.iconWrap svg { + width: 20px; + height: 20px; +} + +.title { + font-size: 15px; + font-weight: 700; + line-height: 1.25; + margin-bottom: 8px; + color: var(--cs-ink); +} + +.desc { + font-size: 13px; + color: var(--cs-ink-dim); + line-height: 1.55; + flex: 1; + margin-bottom: 16px; +} + +.tag { + display: inline-flex; + align-items: center; + padding: 3px 10px; + border-radius: 100px; + font-family: var(--cs-font-mono); + font-size: 10.5px; + letter-spacing: 0.5px; + font-weight: 500; + color: var(--card-color, var(--cs-orange)); + border: 1px solid color-mix(in srgb, var(--card-color, var(--cs-orange)) 35%, transparent); + background: color-mix(in srgb, var(--card-color, var(--cs-orange)) 10%, transparent); + align-self: flex-start; +} diff --git a/crowdsec-docs/src/components/docs/PathCard/index.tsx b/crowdsec-docs/src/components/docs/PathCard/index.tsx new file mode 100644 index 000000000..d94fbf11a --- /dev/null +++ b/crowdsec-docs/src/components/docs/PathCard/index.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import styles from './index.module.css'; + +type Props = { + color: string; + icon: React.ReactNode; + title: string; + desc: string; + tag: string; + href: string; +}; + +export default function PathCard({ color, icon, title, desc, tag, href }: Props) { + const handleMouseEnter = (e: React.MouseEvent) => { + const el = e.currentTarget; + el.style.boxShadow = `0 8px 24px ${color}22, 0 0 0 1px ${color}66`; + el.style.borderLeftColor = color; + }; + const handleMouseLeave = (e: React.MouseEvent) => { + const el = e.currentTarget; + el.style.boxShadow = ''; + el.style.borderLeftColor = ''; + }; + + return ( + +
{icon}
+
{title}
+
{desc}
+ → {tag} +
+ ); +} diff --git a/crowdsec-docs/src/components/docs/PathCards/index.module.css b/crowdsec-docs/src/components/docs/PathCards/index.module.css new file mode 100644 index 000000000..30ca14601 --- /dev/null +++ b/crowdsec-docs/src/components/docs/PathCards/index.module.css @@ -0,0 +1,18 @@ +.row { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + margin: 24px 0; +} + +@media (max-width: 900px) { + .row { + grid-template-columns: 1fr; + } +} + +@media (min-width: 600px) and (max-width: 900px) { + .row { + grid-template-columns: repeat(2, 1fr); + } +} diff --git a/crowdsec-docs/src/components/docs/PathCards/index.tsx b/crowdsec-docs/src/components/docs/PathCards/index.tsx new file mode 100644 index 000000000..ec9452602 --- /dev/null +++ b/crowdsec-docs/src/components/docs/PathCards/index.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import styles from './index.module.css'; + +type Props = { + children: React.ReactNode; +}; + +export default function PathCards({ children }: Props) { + return
{children}
; +} diff --git a/crowdsec-docs/src/components/docs/PathwayRow/index.module.css b/crowdsec-docs/src/components/docs/PathwayRow/index.module.css new file mode 100644 index 000000000..be667dfd8 --- /dev/null +++ b/crowdsec-docs/src/components/docs/PathwayRow/index.module.css @@ -0,0 +1,134 @@ +.row { + margin: 12px 0; + border: 1px solid var(--cs-border); + border-radius: 12px; + background: var(--cs-surface); + overflow: hidden; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 18px 22px; + cursor: pointer; + user-select: none; + gap: 16px; + transition: background 0.15s; +} + +.header:hover { + background: var(--cs-surface-2); +} + +.headerLeft { + display: flex; + align-items: center; + gap: 12px; +} + +.rail { + width: 3px; + height: 32px; + border-radius: 3px; + flex-shrink: 0; +} + +.eyebrow { + font-family: var(--cs-font-mono); + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.14em; + color: var(--cs-ink-mute); + margin-bottom: 2px; +} + +.title { + font-size: 15px; + font-weight: 600; + color: var(--cs-ink); +} + +.chevron { + width: 18px; + height: 18px; + color: var(--cs-ink-mute); + flex-shrink: 0; + transition: transform 0.2s; +} + +.chevron.open { + transform: rotate(180deg); +} + +.body { + padding: 0 22px 22px; + border-top: 1px solid var(--cs-border); +} + +.steps { + display: flex; + flex-direction: column; + gap: 0; + margin: 18px 0 20px; +} + +.step { + display: flex; + gap: 14px; + align-items: flex-start; + padding: 12px 0; +} + +.step + .step { + border-top: 1px solid var(--cs-border); +} + +.stepNum { + width: 28px; + height: 28px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-family: var(--cs-font-mono); + font-size: 11px; + font-weight: 700; + flex-shrink: 0; + margin-top: 1px; +} + +.stepContent { + flex: 1; +} + +.stepTitle { + font-size: 13.5px; + font-weight: 600; + color: var(--cs-ink); + margin-bottom: 3px; +} + +.stepDesc { + font-size: 12.5px; + color: var(--cs-ink-dim); + line-height: 1.5; +} + +.cta { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 18px; + border-radius: 7px; + font-size: 13px; + font-weight: 600; + text-decoration: none; + transition: opacity 0.15s, transform 0.15s; +} + +.cta:hover { + opacity: 0.82; + transform: translateY(-1px); + text-decoration: none; +} diff --git a/crowdsec-docs/src/components/docs/PathwayRow/index.tsx b/crowdsec-docs/src/components/docs/PathwayRow/index.tsx new file mode 100644 index 000000000..ab9e05b5f --- /dev/null +++ b/crowdsec-docs/src/components/docs/PathwayRow/index.tsx @@ -0,0 +1,76 @@ +import React, { useState } from 'react'; +import styles from './index.module.css'; + +type Step = { title: string; desc: string }; + +type Props = { + color: string; + title: string; + eyebrow: string; + steps: Step[]; + ctaLabel: string; + ctaHref: string; + defaultOpen?: boolean; +}; + +export default function PathwayRow({ color, title, eyebrow, steps, ctaLabel, ctaHref, defaultOpen = false }: Props) { + const [open, setOpen] = useState(defaultOpen); + + return ( +
+
setOpen((v) => !v)} role="button" aria-expanded={open}> +
+
+
+
{eyebrow}
+
{title}
+
+
+ +
+ + {open && ( +
+
+ {steps.map((step, i) => ( +
+
+ {String(i + 1).padStart(2, '0')} +
+
+
{step.title}
+
{step.desc}
+
+
+ ))} +
+ + {ctaLabel} → + +
+ )} +
+ ); +} diff --git a/crowdsec-docs/src/components/docs/PopularChips/index.module.css b/crowdsec-docs/src/components/docs/PopularChips/index.module.css new file mode 100644 index 000000000..529d334b6 --- /dev/null +++ b/crowdsec-docs/src/components/docs/PopularChips/index.module.css @@ -0,0 +1,39 @@ +.section { + margin: 28px 0; +} + +.heading { + font-family: var(--cs-font-mono); + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.14em; + color: var(--cs-ink-mute); + margin-bottom: 12px; +} + +.chips { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.chip { + display: inline-flex; + align-items: center; + padding: 6px 14px; + border-radius: 8px; + font-family: var(--cs-font-mono); + font-size: 12px; + color: var(--cs-ink-dim); + border: 1px solid var(--cs-border-hi); + background: var(--cs-surface); + text-decoration: none; + transition: border-color 0.15s, color 0.15s, background 0.15s; +} + +.chip:hover { + border-color: var(--cs-orange); + color: var(--cs-orange); + background: var(--cs-orange-soft); + text-decoration: none; +} diff --git a/crowdsec-docs/src/components/docs/PopularChips/index.tsx b/crowdsec-docs/src/components/docs/PopularChips/index.tsx new file mode 100644 index 000000000..171aa5683 --- /dev/null +++ b/crowdsec-docs/src/components/docs/PopularChips/index.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import styles from './index.module.css'; + +type Chip = { label: string; href: string }; + +type Props = { + heading?: string; + chips: Chip[]; +}; + +export default function PopularChips({ heading = 'Popular docs', chips }: Props) { + return ( +
+
{heading}
+
+ {chips.map((c) => ( + + {c.label} + + ))} +
+
+ ); +} diff --git a/crowdsec-docs/src/components/docs/PromoCard/index.module.css b/crowdsec-docs/src/components/docs/PromoCard/index.module.css new file mode 100644 index 000000000..ee3ce752d --- /dev/null +++ b/crowdsec-docs/src/components/docs/PromoCard/index.module.css @@ -0,0 +1,73 @@ +.card { + margin-top: 24px; + padding: 18px; + background: var(--cs-surface); + border: 1px solid var(--cs-border-hi); + border-radius: 12px; + display: flex; + flex-direction: column; + gap: 12px; +} + +.eyebrow { + font-family: var(--cs-font-mono); + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.18em; + color: var(--cs-ink-mute); + font-weight: 600; +} + +.title { + font-size: 13.5px; + font-weight: 700; + color: var(--cs-ink); + line-height: 1.3; +} + +.sparkline { + display: block; + width: 100%; + height: 36px; +} + +.perks { + display: flex; + flex-direction: column; + gap: 7px; +} + +.perk { + display: flex; + align-items: flex-start; + gap: 7px; + font-size: 11.5px; + color: var(--cs-ink-dim); + line-height: 1.4; +} + +.checkIcon { + width: 14px; + height: 14px; + flex-shrink: 0; + margin-top: 1px; +} + +.cta { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 14px; + border-radius: 7px; + font-size: 12px; + font-weight: 600; + text-decoration: none; + transition: opacity 0.15s; + text-align: center; + width: 100%; +} + +.cta:hover { + opacity: 0.85; + text-decoration: none; +} diff --git a/crowdsec-docs/src/components/docs/PromoCard/index.tsx b/crowdsec-docs/src/components/docs/PromoCard/index.tsx new file mode 100644 index 000000000..dcc77832d --- /dev/null +++ b/crowdsec-docs/src/components/docs/PromoCard/index.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import styles from './index.module.css'; + +export type PromoVariant = 'console' | 'cti' | 'engine'; + +type PromoData = { + eyebrow: string; + title: string; + color: string; + perks: string[]; + ctaLabel: string; + ctaHref: string; +}; + +const PROMO_DATA: Record = { + console: { + eyebrow: 'CrowdSec Console', + title: 'Manage your security from one place', + color: 'var(--cs-orange)', + perks: [ + 'Visualize alerts in real-time', + 'Manage blocklists & allowlists', + 'CTI intelligence dashboard', + ], + ctaLabel: 'Open the Console →', + ctaHref: 'https://app.crowdsec.net/signup?mtm_campaign=Console&mtm_source=docs&mtm_medium=tocAd', + }, + cti: { + eyebrow: 'IP Reputation & CTI', + title: 'Query 13B+ IP signals with our CTI API', + color: 'var(--cs-violet)', + perks: [ + 'Real-time IP reputation scoring', + 'Threat intelligence feeds', + 'REST API with generous free tier', + ], + ctaLabel: 'CTI API docs →', + ctaHref: '/u/cti_api/intro', + }, + engine: { + eyebrow: 'Security Engine', + title: 'Open-source log-based threat detection', + color: 'var(--cs-teal)', + perks: [ + 'Detects attacks from your own logs', + 'Shares blocklists with the community', + 'Installs in minutes on any system', + ], + ctaLabel: 'Get started →', + ctaHref: '/u/getting_started/installation/linux', + }, +}; + +function CheckIcon() { + return ( + + ); +} + +function Sparkline({ color }: { color: string }) { + return ( + + ); +} + +type Props = { + variant?: PromoVariant; +}; + +export default function PromoCard({ variant = 'console' }: Props) { + const data = PROMO_DATA[variant]; + if (!data) return null; + + return ( +
+
{data.eyebrow}
+
{data.title}
+ +
+ {data.perks.map((perk) => ( +
+ + {perk} +
+ ))} +
+ + {data.ctaLabel} + +
+ ); +} diff --git a/crowdsec-docs/src/components/docs/RunningStrip/index.module.css b/crowdsec-docs/src/components/docs/RunningStrip/index.module.css new file mode 100644 index 000000000..73e90f637 --- /dev/null +++ b/crowdsec-docs/src/components/docs/RunningStrip/index.module.css @@ -0,0 +1,50 @@ +.strip { + display: flex; + align-items: center; + gap: 16px; + flex-wrap: wrap; + padding: 10px 18px; + background: var(--cs-surface); + border: 1px solid var(--cs-border); + border-radius: 10px; + margin: 16px 0 24px; +} + +.label { + font-family: var(--cs-font-mono); + font-size: 10.5px; + letter-spacing: 0.8px; + text-transform: uppercase; + color: var(--cs-ink-mute); + white-space: nowrap; + flex-shrink: 0; +} + +.pills { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.pill { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 5px 12px; + border-radius: 7px; + font-size: 12.5px; + line-height: 1; + white-space: nowrap; + color: var(--cs-ink-dim); + border: 1px solid var(--cs-border-hi); + background: var(--cs-bg); + text-decoration: none; + transition: border-color 0.15s, color 0.15s, background 0.15s; +} + +.pill:hover { + border-color: var(--cs-orange); + color: var(--cs-orange); + background: var(--cs-orange-soft); + text-decoration: none; +} diff --git a/crowdsec-docs/src/components/docs/RunningStrip/index.tsx b/crowdsec-docs/src/components/docs/RunningStrip/index.tsx new file mode 100644 index 000000000..fb1e337dc --- /dev/null +++ b/crowdsec-docs/src/components/docs/RunningStrip/index.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import styles from './index.module.css'; + +type Pill = { label: string; href: string }; + +type Props = { + label: string; + pills: Pill[]; +}; + +export default function RunningStrip({ label, pills }: Props) { + return ( +
+ {label} +
+ {pills.map((p) => ( + + {p.label} + + ))} +
+
+ ); +} diff --git a/crowdsec-docs/src/components/docs/icons/AlertIcon.tsx b/crowdsec-docs/src/components/docs/icons/AlertIcon.tsx new file mode 100644 index 000000000..c81ce9615 --- /dev/null +++ b/crowdsec-docs/src/components/docs/icons/AlertIcon.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +export default function AlertIcon({ className, style }: { className?: string; style?: React.CSSProperties }) { + return ( + + ); +} diff --git a/crowdsec-docs/src/components/docs/icons/BarChartIcon.tsx b/crowdsec-docs/src/components/docs/icons/BarChartIcon.tsx new file mode 100644 index 000000000..ce66d3e7d --- /dev/null +++ b/crowdsec-docs/src/components/docs/icons/BarChartIcon.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +export default function BarChartIcon({ className, style }: { className?: string; style?: React.CSSProperties }) { + return ( + + ); +} diff --git a/crowdsec-docs/src/components/docs/icons/BlocklistIcon.tsx b/crowdsec-docs/src/components/docs/icons/BlocklistIcon.tsx new file mode 100644 index 000000000..6599466a2 --- /dev/null +++ b/crowdsec-docs/src/components/docs/icons/BlocklistIcon.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export default function BlocklistIcon({ className, style }: { className?: string; style?: React.CSSProperties }) { + return ( + + ); +} diff --git a/crowdsec-docs/src/components/docs/icons/CheckIcon.tsx b/crowdsec-docs/src/components/docs/icons/CheckIcon.tsx new file mode 100644 index 000000000..1aeb61b3d --- /dev/null +++ b/crowdsec-docs/src/components/docs/icons/CheckIcon.tsx @@ -0,0 +1,9 @@ +import React from 'react'; + +export default function CheckIcon({ className, style }: { className?: string; style?: React.CSSProperties }) { + return ( + + ); +} diff --git a/crowdsec-docs/src/components/docs/icons/ConsoleIcon.tsx b/crowdsec-docs/src/components/docs/icons/ConsoleIcon.tsx new file mode 100644 index 000000000..2a75d4eb9 --- /dev/null +++ b/crowdsec-docs/src/components/docs/icons/ConsoleIcon.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +export default function ConsoleIcon({ className, style }: { className?: string; style?: React.CSSProperties }) { + return ( + + ); +} diff --git a/crowdsec-docs/src/components/docs/icons/CtiIcon.tsx b/crowdsec-docs/src/components/docs/icons/CtiIcon.tsx new file mode 100644 index 000000000..b9cb2e12c --- /dev/null +++ b/crowdsec-docs/src/components/docs/icons/CtiIcon.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +export default function CtiIcon({ className, style }: { className?: string; style?: React.CSSProperties }) { + return ( + + ); +} diff --git a/crowdsec-docs/src/components/docs/icons/GlobeIcon.tsx b/crowdsec-docs/src/components/docs/icons/GlobeIcon.tsx new file mode 100644 index 000000000..9a250e3e5 --- /dev/null +++ b/crowdsec-docs/src/components/docs/icons/GlobeIcon.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +export default function GlobeIcon({ className, style }: { className?: string; style?: React.CSSProperties }) { + return ( + + ); +} diff --git a/crowdsec-docs/src/components/docs/icons/LockIcon.tsx b/crowdsec-docs/src/components/docs/icons/LockIcon.tsx new file mode 100644 index 000000000..85a824f1a --- /dev/null +++ b/crowdsec-docs/src/components/docs/icons/LockIcon.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +export default function LockIcon({ className, style }: { className?: string; style?: React.CSSProperties }) { + return ( + + ); +} diff --git a/crowdsec-docs/src/components/docs/icons/SearchIcon.tsx b/crowdsec-docs/src/components/docs/icons/SearchIcon.tsx new file mode 100644 index 000000000..14e04fd2f --- /dev/null +++ b/crowdsec-docs/src/components/docs/icons/SearchIcon.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +export default function SearchIcon({ className, style }: { className?: string; style?: React.CSSProperties }) { + return ( + + ); +} diff --git a/crowdsec-docs/src/components/docs/icons/ShieldIcon.tsx b/crowdsec-docs/src/components/docs/icons/ShieldIcon.tsx new file mode 100644 index 000000000..972d1be7a --- /dev/null +++ b/crowdsec-docs/src/components/docs/icons/ShieldIcon.tsx @@ -0,0 +1,9 @@ +import React from 'react'; + +export default function ShieldIcon({ className, style }: { className?: string; style?: React.CSSProperties }) { + return ( + + ); +} diff --git a/crowdsec-docs/src/components/docs/icons/UsersIcon.tsx b/crowdsec-docs/src/components/docs/icons/UsersIcon.tsx new file mode 100644 index 000000000..c32f1a12a --- /dev/null +++ b/crowdsec-docs/src/components/docs/icons/UsersIcon.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export default function UsersIcon({ className, style }: { className?: string; style?: React.CSSProperties }) { + return ( + + ); +} diff --git a/crowdsec-docs/src/css/crowdsec-algolia.css b/crowdsec-docs/src/css/crowdsec-algolia.css new file mode 100644 index 000000000..ff1db3518 --- /dev/null +++ b/crowdsec-docs/src/css/crowdsec-algolia.css @@ -0,0 +1,60 @@ +/* ───────────────────────────────────────────────────────────────────────────── + CrowdSec Algolia DocSearch theming + Maps --docsearch-* vars to --cs-* tokens for dark and light modes. +───────────────────────────────────────────────────────────────────────────── */ + +[data-theme='dark'] .DocSearch, +:root .DocSearch { + --docsearch-primary-color: var(--cs-orange); + --docsearch-text-color: var(--cs-ink); + --docsearch-muted-color: var(--cs-ink-mute); + --docsearch-container-background: rgba(10, 17, 32, 0.80); + --docsearch-modal-background: var(--cs-surface); + --docsearch-searchbox-background: var(--cs-bg); + --docsearch-searchbox-focus-background: var(--cs-bg-soft); + --docsearch-hit-color: var(--cs-ink); + --docsearch-hit-active-color: var(--cs-bg); + --docsearch-hit-background: var(--cs-surface-2); + --docsearch-footer-background: var(--cs-bg-soft); + --docsearch-key-gradient: linear-gradient(180deg, var(--cs-surface) 0%, var(--cs-bg) 100%); + --docsearch-key-shadow: 0 1px 0 var(--cs-border-hi), 0 1px 3px rgba(0,0,0,0.35); + --docsearch-searchbox-shadow: inset 0 0 0 1px var(--cs-orange); + --docsearch-modal-shadow: 0 8px 40px rgba(0,0,0,0.40); +} + +[data-theme='light'] .DocSearch { + --docsearch-primary-color: var(--cs-orange); + --docsearch-text-color: var(--cs-ink); + --docsearch-muted-color: var(--cs-ink-mute); + --docsearch-container-background: rgba(15, 23, 42, 0.55); + --docsearch-modal-background: var(--cs-surface); + --docsearch-searchbox-background: var(--cs-surface-2); + --docsearch-searchbox-focus-background: var(--cs-bg-soft); + --docsearch-hit-color: var(--cs-ink); + --docsearch-hit-active-color: #FFFFFF; + --docsearch-hit-background: var(--cs-bg-soft); + --docsearch-footer-background: var(--cs-surface-2); + --docsearch-key-gradient: linear-gradient(180deg, var(--cs-bg-soft) 0%, var(--cs-surface-2) 100%); + --docsearch-key-shadow: 0 1px 0 var(--cs-border-hi), 0 1px 3px rgba(0,0,0,0.15); + --docsearch-searchbox-shadow: inset 0 0 0 1px var(--cs-orange); + --docsearch-modal-shadow: 0 8px 40px rgba(0,0,0,0.15); +} + +/* Homepage search bar override — use token vars */ +.homepage-search .DocSearch-Button { + background: var(--cs-surface) !important; + border-color: var(--cs-border-hi) !important; +} + +.homepage-search .DocSearch-Button:hover { + border-color: var(--cs-orange) !important; + box-shadow: 0 0 0 1px var(--cs-orange) !important; +} + +.homepage-search .DocSearch-Button-Placeholder { + color: var(--cs-ink-mute) !important; +} + +.homepage-search .DocSearch-Search-Icon { + color: var(--cs-ink-mute) !important; +} diff --git a/crowdsec-docs/src/css/crowdsec-tokens.css b/crowdsec-docs/src/css/crowdsec-tokens.css new file mode 100644 index 000000000..da30f7576 --- /dev/null +++ b/crowdsec-docs/src/css/crowdsec-tokens.css @@ -0,0 +1,192 @@ +/* ───────────────────────────────────────────────────────────────────────────── + CrowdSec Design Tokens + Single source of truth for all colors. All component CSS uses var(--cs-*). + No hex colors are allowed in component files. +───────────────────────────────────────────────────────────────────────────── */ + +/* DARK — default */ +:root, +[data-theme='dark'] { + /* Surfaces */ + --cs-bg: #0A1120; + --cs-bg-soft: #0E1729; + --cs-surface: #111B30; + --cs-surface-2: #162238; + --cs-border: rgba(148, 163, 184, 0.10); + --cs-border-hi: rgba(148, 163, 184, 0.18); + + /* Text */ + --cs-ink: #F1F5F9; + --cs-ink-dim: #94A3B8; + --cs-ink-mute: #64748B; + + /* Brand + path colors */ + --cs-orange: #F9B124; + --cs-teal: #34D399; + --cs-violet: #A78BFA; + --cs-blue: #60A5FA; + --cs-pink: #F472B6; + + /* Tinted soft backgrounds via color-mix */ + --cs-orange-soft: color-mix(in srgb, var(--cs-orange) 12%, transparent); + --cs-teal-soft: color-mix(in srgb, var(--cs-teal) 12%, transparent); + --cs-violet-soft: color-mix(in srgb, var(--cs-violet) 14%, transparent); + --cs-blue-soft: color-mix(in srgb, var(--cs-blue) 12%, transparent); + --cs-pink-soft: color-mix(in srgb, var(--cs-pink) 12%, transparent); + + /* Type */ + --cs-font-sans: 'Instrument Sans', system-ui, -apple-system, sans-serif; + --cs-font-mono: 'JetBrains Mono', 'Roboto Mono', ui-monospace, Menlo, monospace; + + /* ── Infima / Docusaurus bridge ──────────────────────────────────────── */ + --ifm-background-color: var(--cs-bg); + --ifm-background-surface-color: var(--cs-bg-soft); + + --ifm-color-primary: var(--cs-orange); + --ifm-color-primary-dark: #E2890C; + --ifm-color-primary-darker: #C77808; + --ifm-color-primary-darkest: #A76406; + --ifm-color-primary-light: #FBBE45; + --ifm-color-primary-lighter: #FCC85F; + --ifm-color-primary-lightest: #FDD988; + + --ifm-color-content: var(--cs-ink); + --ifm-color-content-secondary: var(--cs-ink-dim); + + --ifm-toc-border-color: var(--cs-border); + + --ifm-menu-color: var(--cs-ink-dim); + --ifm-menu-color-active: var(--cs-orange); + --ifm-menu-color-background-active: var(--cs-orange-soft); + --ifm-menu-color-background-hover: var(--cs-orange-soft); + + --ifm-font-family-base: var(--cs-font-sans); + --ifm-font-family-monospace: var(--cs-font-mono); + --ifm-heading-font-family: var(--cs-font-sans); + + --ifm-link-color: var(--cs-orange); + --ifm-link-hover-color: var(--cs-orange); + + --ifm-code-background: var(--cs-bg-soft); + --ifm-navbar-background-color: color-mix(in srgb, var(--cs-bg) 85%, transparent); + + --ifm-hr-border-color: var(--cs-border-hi); + --ifm-blockquote-border-color: var(--cs-orange); + + --ifm-tabs-color-active: var(--cs-orange); + --ifm-tabs-color-active-border: var(--cs-orange); + + --ifm-breadcrumb-color-active: var(--cs-orange); + --ifm-breadcrumb-separator-color: var(--cs-ink-mute); +} + +/* LIGHT — inverted surfaces, adjusted path colors for AA contrast */ +[data-theme='light'] { + /* Surfaces */ + --cs-bg: #FAFAF7; + --cs-bg-soft: #FFFFFF; + --cs-surface: #FFFFFF; + --cs-surface-2: #F4F2EC; + --cs-border: rgba(15, 23, 42, 0.08); + --cs-border-hi: rgba(15, 23, 42, 0.14); + + /* Text */ + --cs-ink: #0F172A; + --cs-ink-dim: #475569; + --cs-ink-mute: #64748B; + + /* Brand + path — darkened for AA on light backgrounds */ + --cs-orange: #C77808; + --cs-teal: #047857; + --cs-violet: #6D4FE0; + --cs-blue: #2563EB; + --cs-pink: #DB2777; + + --cs-orange-soft: color-mix(in srgb, var(--cs-orange) 10%, transparent); + --cs-teal-soft: color-mix(in srgb, var(--cs-teal) 10%, transparent); + --cs-violet-soft: color-mix(in srgb, var(--cs-violet) 10%, transparent); + --cs-blue-soft: color-mix(in srgb, var(--cs-blue) 10%, transparent); + --cs-pink-soft: color-mix(in srgb, var(--cs-pink) 10%, transparent); + + /* ── Infima / Docusaurus bridge ──────────────────────────────────────── */ + --ifm-background-color: var(--cs-bg); + --ifm-background-surface-color: var(--cs-bg-soft); + + --ifm-color-primary: var(--cs-orange); + --ifm-color-primary-dark: #A76406; + --ifm-color-primary-darker: #8F5605; + --ifm-color-primary-darkest: #6E4204; + --ifm-color-primary-light: #E2890C; + --ifm-color-primary-lighter: #F09920; + --ifm-color-primary-lightest: #F9B124; + + --ifm-color-content: var(--cs-ink); + --ifm-color-content-secondary: var(--cs-ink-dim); + + --ifm-toc-border-color: var(--cs-border); + + --ifm-menu-color: var(--cs-ink-dim); + --ifm-menu-color-active: var(--cs-orange); + --ifm-menu-color-background-active: var(--cs-orange-soft); + --ifm-menu-color-background-hover: var(--cs-orange-soft); + + --ifm-font-family-base: var(--cs-font-sans); + --ifm-font-family-monospace: var(--cs-font-mono); + --ifm-heading-font-family: var(--cs-font-sans); + + --ifm-link-color: var(--cs-orange); + --ifm-link-hover-color: var(--cs-orange); + + --ifm-code-background: var(--cs-surface-2); + --ifm-navbar-background-color: var(--cs-bg-soft); + + --ifm-hr-border-color: var(--cs-border-hi); + --ifm-blockquote-border-color: var(--cs-orange); + + --ifm-tabs-color-active: var(--cs-orange); + --ifm-tabs-color-active-border: var(--cs-orange); + + --ifm-breadcrumb-color-active: var(--cs-orange); + --ifm-breadcrumb-separator-color: var(--cs-ink-mute); +} + +/* ── Sidebar active link styling ────────────────────────────────────────── */ +.menu__link--active:not(.menu__link--sublist) { + border-left: 2px solid var(--cs-orange); + padding-left: calc(var(--ifm-menu-link-padding-horizontal) - 2px); + background: var(--cs-orange-soft) !important; + color: var(--cs-orange) !important; +} + +/* Top-level sidebar category label: eyebrow-style mono uppercase */ +.menu__list > .menu__list-item > .menu__list-item-collapsible > .menu__link--sublist { + font-family: var(--cs-font-mono); + font-size: 10px; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--cs-ink-mute); + font-weight: 600; +} + +/* Active state for sublist headings */ +.menu__list > .menu__list-item > .menu__list-item-collapsible > .menu__link--sublist.menu__link--active { + color: var(--cs-orange); +} + +/* Ensure link color uses the token */ +a { + color: var(--ifm-link-color); +} + +/* Table of contents active link */ +.table-of-contents__link--active { + color: var(--cs-orange) !important; + font-weight: 600; + border-left-color: var(--cs-orange) !important; +} + +/* Tabs active state */ +.tabs__item--active { + border-bottom-color: var(--cs-orange) !important; + color: var(--cs-orange) !important; +} diff --git a/crowdsec-docs/src/css/crowdsec-typography.css b/crowdsec-docs/src/css/crowdsec-typography.css new file mode 100644 index 000000000..1f598653e --- /dev/null +++ b/crowdsec-docs/src/css/crowdsec-typography.css @@ -0,0 +1,98 @@ +/* ───────────────────────────────────────────────────────────────────────────── + CrowdSec Typography System + Applies to doc pages via .theme-doc-markdown. No hex colors — uses --cs-* vars. +───────────────────────────────────────────────────────────────────────────── */ + +/* ── Base prose container ───────────────────────────────────────────────── */ +.theme-doc-markdown { + counter-reset: cs-section; + line-height: 1.65; +} + +.theme-doc-markdown > p, +.theme-doc-markdown > ul > li, +.theme-doc-markdown > ol > li { + max-width: 720px; + line-height: 1.65; +} + +/* ── Headings ───────────────────────────────────────────────────────────── */ +article h1, +.theme-doc-markdown h1 { + font-size: 44px; + font-weight: 700; + letter-spacing: -0.025em; + text-wrap: balance; + line-height: 1.1; + margin-bottom: 0.5rem; +} + +article h2, +.theme-doc-markdown h2 { + font-size: 28px; + font-weight: 700; + letter-spacing: -0.02em; + line-height: 1.2; +} + +article h3, +.theme-doc-markdown h3 { + font-size: 18.5px; + font-weight: 600; + letter-spacing: -0.01em; + line-height: 1.3; +} + +/* ── § NN auto-counter on h2 ────────────────────────────────────────────── */ +.theme-doc-markdown > h2 { + counter-increment: cs-section; + display: flex; + align-items: baseline; + gap: 14px; +} + +.theme-doc-markdown > h2::before { + content: "§ " counter(cs-section, decimal-leading-zero); + font: 600 12px/1 var(--cs-font-mono); + color: var(--cs-orange); + letter-spacing: 0.16em; + flex-shrink: 0; + opacity: 0.8; +} + +/* ── Lede paragraph (first

after the page header) ───────────────────── */ +.theme-doc-markdown > p:first-of-type { + font-size: 16px; + color: var(--cs-ink-dim); + line-height: 1.65; + max-width: 720px; +} + +/* ── Eyebrow element (rendered by DocItem/Content swizzle) ──────────────── */ +.cs-eyebrow { + font-family: var(--cs-font-mono); + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.18em; + color: var(--cs-orange); + font-weight: 600; + margin-bottom: 8px; + display: block; +} + +/* ── Mobile overrides ───────────────────────────────────────────────────── */ +@media (max-width: 768px) { + article h1, + .theme-doc-markdown h1 { + font-size: 32px; + } + + article h2, + .theme-doc-markdown h2 { + font-size: 22px; + } + + .theme-doc-markdown > h2::before { + font-size: 10px; + } +} diff --git a/crowdsec-docs/src/css/custom.css b/crowdsec-docs/src/css/custom.css index 1cb496aab..e0141ea07 100644 --- a/crowdsec-docs/src/css/custom.css +++ b/crowdsec-docs/src/css/custom.css @@ -5,6 +5,9 @@ @import "tailwindcss/utilities"; @import url("colors.css"); /* SHOULD BE ON TOP */ +@import url("crowdsec-tokens.css"); +@import url("crowdsec-typography.css"); +@import url("crowdsec-algolia.css"); @import url("alerts.css"); @import url("code.css"); @import url("navbar.css"); @@ -30,29 +33,7 @@ body, @apply bg-card; } -html[data-theme="light"] .menu__link--active { - --ifm-menu-color-active: rgb(var(--primary)); -} -html[data-theme="light"] .navbar-sidebar__item .menu__list .menu__link--active { - --ifm-menu-color-active: rgb(var(--secondary)); -} - -html[data-theme="light"] .tabs__item--active { - --ifm-color-primary: rgb(var(--primary)); - --ifm-tabs-color-active: rgb(var(--primary)); - border-bottom: 2px solid rgb(var(--primary)); -} -html[data-theme="light"] .table-of-contents__link:hover { - --ifm-color-primary: rgb(var(--primary)); -} -html[data-theme="light"] .breadcrumbs__link { - --ifm-breadcrumb-color-active: rgb(var(--primary)); - --ifm-link-hover-color: rgb(var(--primary)); -} -html[data-theme="light"] .dropdown__link--active, -html[data-theme="light"] .dropdown__link--active:hover { - --ifm-link-color: rgb(var(--primary)); -} +/* Infima active state overrides are now handled by crowdsec-tokens.css */ html[data-theme="dark"] { --docusaurus-highlighted-code-line-bg: rgba(255, 255, 255, 0.1); @@ -189,11 +170,11 @@ div.markdown .doc-quick-strip__pill { } a { - @apply text-primary; + color: var(--ifm-link-color); } blockquote { - --ifm-blockquote-border-color: rgb(var(--primary)); + --ifm-blockquote-border-color: var(--cs-orange); --ifm-blockquote-background-color: transparent; } @@ -383,43 +364,7 @@ html:has(.homepage-search) [class*="navbarSearchContainer"] { } } -/* Algolia DocSearch modal theming */ -[data-theme="light"] .DocSearch { - --docsearch-primary-color: rgb(var(--primary)); - --docsearch-text-color: rgb(var(--foreground)); - --docsearch-muted-color: rgb(var(--muted-foreground)); - --docsearch-container-background: rgb(var(--color-gray-900) / 0.55); - --docsearch-modal-background: rgb(var(--card)); - --docsearch-searchbox-background: rgb(var(--input)); - --docsearch-searchbox-focus-background: rgb(var(--card)); - --docsearch-hit-color: rgb(var(--foreground)); - --docsearch-hit-active-color: rgb(var(--primary-foreground)); - --docsearch-hit-background: rgb(var(--background)); - --docsearch-footer-background: rgb(var(--muted)); - --docsearch-key-gradient: linear-gradient(180deg, rgb(var(--background)), rgb(var(--muted))); - --docsearch-key-shadow: 0 1px 0 rgb(var(--border)), 0 1px 3px rgba(0, 0, 0, 0.15); -} - -[data-theme="light"] .DocSearch-Commands-Key { - background: rgb(var(--color-gray-200)); - box-shadow: 0 1px 0 rgb(var(--color-gray-300)); -} - -[data-theme="dark"] .DocSearch { - --docsearch-primary-color: rgb(var(--primary)); - --docsearch-text-color: rgb(var(--foreground)); - --docsearch-muted-color: rgb(var(--muted-foreground)); - --docsearch-container-background: rgb(var(--color-gray-950) / 0.72); - --docsearch-modal-background: rgb(var(--card)); - --docsearch-searchbox-background: rgb(var(--input)); - --docsearch-searchbox-focus-background: rgb(var(--card)); - --docsearch-hit-color: rgb(var(--foreground)); - --docsearch-hit-active-color: rgb(var(--primary-foreground)); - --docsearch-hit-background: rgb(var(--background)); - --docsearch-footer-background: rgb(var(--background)); - --docsearch-key-gradient: linear-gradient(180deg, rgb(var(--background)), rgb(var(--muted))); - --docsearch-key-shadow: 0 1px 0 rgb(var(--border)), 0 1px 3px rgba(0, 0, 0, 0.35); -} +/* Algolia DocSearch modal theming is in crowdsec-algolia.css */ /* Quick guide specific CSS /u/getting_started/... */ .sideBarItemRecommended a::after { diff --git a/crowdsec-docs/src/pages/index.tsx b/crowdsec-docs/src/pages/index.tsx index 45f382f39..cafa1c32d 100644 --- a/crowdsec-docs/src/pages/index.tsx +++ b/crowdsec-docs/src/pages/index.tsx @@ -1,740 +1,254 @@ -import Link from "@docusaurus/Link"; -import Layout from "@theme/Layout"; -import SearchBar from "@theme/SearchBar"; -import { ExternalLink } from "lucide-react"; -import React, { useEffect, useState } from "react"; -import { Button } from "../ui/button"; - -// ── Intent card ────────────────────────────────────────────────────────────── - -type IntentCardProps = { - icon: React.ReactNode; - title: string; - desc: string; - pill: string; - accent: string; - href: string; - aka?: string[]; -}; - -const IntentCard = ({ icon, title, desc, pill, accent, href, aka }: IntentCardProps) => ( - { - const el = e.currentTarget as HTMLAnchorElement; - el.style.borderColor = accent; - el.style.boxShadow = `0 8px 24px ${accent}22, 0 0 0 1px ${accent}`; - el.style.transform = "translateY(-2px)"; - el.style.borderRadius = "14px"; - }} - onMouseLeave={(e) => { - const el = e.currentTarget as HTMLAnchorElement; - el.style.borderColor = ""; - el.style.boxShadow = ""; - el.style.transform = ""; - }} - > -

- -); - -// ── Schema / path block ─────────────────────────────────────────────────────── - -type Step = { - num: number; - icon: string; - title: string; - desc: string; - hint?: string; -}; - -type SchemaBlockProps = { - id: string; - color: string; - eyebrowIcon: string; - eyebrow: string; - title: string; - ctaLabel: string; - ctaHref: string; - steps: Step[]; - open: boolean; - onToggle: () => void; -}; - -const SchemaBlock = ({ id, color, eyebrowIcon, eyebrow, title, ctaLabel, ctaHref, steps, open, onToggle }: SchemaBlockProps) => ( -
- {/* left accent strip */} -
- {/* subtle radial glow */} -
- - {/* header - always visible, clickable to toggle */} - - - {/* collapsible step flow */} - {open && ( -
- {steps.map((step, i) => ( -
- {i > 0 && ( -
- → -
- )} - {step.hint && ( -
- {step.hint} -
- )} -
- {step.num} -
-
{step.icon}
-
{step.title}
-
{step.desc}
-
- ))} -
- )} -
-); - -// ── Data ────────────────────────────────────────────────────────────────────── - -const ORANGE = "#f97316"; -const GREEN = "#22d3a0"; -const BLUE = "#60a5fa"; - -const intents: IntentCardProps[] = [ - { - icon: Security Engine, - accent: ORANGE, - title: "Detect & Block attacks on my servers", - desc: "Locally identify and ban bad behaving IPs observed in your logs and requests with CrowdSec Detection Scenarios, and Virtual-Patching Collections.", - pill: "Security Engine", - href: "/security-engine", - aka: ["IDPS", "WAF", "CrowdSec FOSS"], - }, - { - icon: Blocklists, - accent: GREEN, - title: "Push a Blocklists into my firewall, CDN or WAF", - desc: "You manage network perimeter devices and want a URL to subscribe to. No agent to install.", - pill: "Blocklist Integration Endpoint", - href: "/blocklists", - aka: ["Threat Feeds", "IOC Streams", "Deny-list"], - }, - { - icon: CTI, - accent: BLUE, - title: "Investigate IPs Behaviors and Enrich Alerts", - desc: "You're a security analyst or developer who wants IP context, behaviors, CVEs, Aggressivity... In a browser or via REST API.", - pill: "IP Reputation & CTI", - href: "/u/cti_api/intro", - aka: ["IoC Lookup", "Threat Intel"], - }, +import Layout from '@theme/Layout'; +import SearchBar from '@theme/SearchBar'; +import React, { useEffect } from 'react'; + +import PathCard from '../components/docs/PathCard'; +import PathCards from '../components/docs/PathCards'; +import PopularChips from '../components/docs/PopularChips'; +import RunningStrip from '../components/docs/RunningStrip'; +import GuidedSetupCard from '../components/docs/GuidedSetupCard'; +import PathwayRow from '../components/docs/PathwayRow'; +import ShieldIcon from '../components/docs/icons/ShieldIcon'; +import BlocklistIcon from '../components/docs/icons/BlocklistIcon'; +import CtiIcon from '../components/docs/icons/CtiIcon'; + +/* ── Colour tokens — must match crowdsec-tokens.css dark values so the + home page (which sits outside doc pages) also looks correct. */ +const CS_ORANGE = 'var(--cs-orange)'; +const CS_TEAL = 'var(--cs-teal)'; +const CS_VIOLET = 'var(--cs-violet)'; + +const securityEngineSteps = [ + { + title: 'Install the Security Engine', + desc: 'Runs on your server, detects attack patterns in real time. Immediately protected with the CrowdSec Community Blocklist.', + }, + { + title: 'Activate the WAF module', + desc: 'Layer in the AppSec component to inspect HTTP traffic and block web exploits before they reach your app.', + }, + { + title: 'Subscribe to blocklists', + desc: 'Add a selection of extra blocklists on top of the built-in detection & community blocklist.', + }, + { + title: 'Craft your own rules', + desc: 'Write custom scenarios for your stack, then share them back with the community on the Hub.', + }, ]; -const schemas: Omit[] = [ - { - id: "schema-engine", - color: ORANGE, - eyebrowIcon: "🛡️", - eyebrow: "Security Engine", - title: "Detect and block malicious behaviors on your infrastructure", - ctaLabel: "Install CrowdSec →", - ctaHref: "/security-engine", - steps: [ - { - num: 1, - icon: "⚡", - title: "Install the Security Engine", - desc: "Runs on your server, detects attack patterns in real time. Immediately protected, and continuously updated with CrowdSec Community Blocklist.", - }, - { - num: 2, - icon: "🛡️", - hint: "RECOMMENDED", - title: "Activate the WAF module", - desc: "Layer in the AppSec component to inspect HTTP traffic and block web exploits before they reach your app.", - }, - { - num: 3, - icon: "📋", - hint: "OPTIONAL", - title: "Subscribe to blocklists", - desc: "Add a selection of extra blocklists on top of the built-in detection & community blocklist", - }, - { - num: 4, - icon: "✍️", - hint: "OPTIONAL", - title: "Craft your own rules", - desc: "Write custom scenarios for your stack, then share them back with the community on the Hub.", - }, - ], - }, - { - id: "schema-blocklists", - color: GREEN, - eyebrowIcon: "🚫", - eyebrow: "Blocklists", - title: "Push curated threat feeds directly into your firewall, CDN, or WAF", - ctaLabel: "Discover Blocklists →", - ctaHref: "/blocklists", - steps: [ - { - num: 1, - icon: "🔌", - title: "Create a blocklist integration endpoint", - desc: "Generates a dedicated URL and credentials to serve blocklists to your perimeter devices.", - }, - { - num: 2, - icon: "🗂️", - title: "Choose which blocklists to serve", - desc: "Select from curated feeds by threat category: scanners, bots, TOR exits, exploits, and more.", - }, - { - num: 3, - icon: "🔗", - title: "Plug it in as an external threat feed", - desc: "Point your firewall, CDN, or WAF at the endpoint. Use the feed to protect your infrastructure.", - }, - ], - }, - { - id: "schema-cti", - color: BLUE, - eyebrowIcon: "🔍", - eyebrow: "IP Reputation & CTI", - title: "Query threat intel in the browser or via API in your tools", - ctaLabel: "Explore CTI →", - ctaHref: "/u/cti_api/intro", - steps: [ - { - num: 1, - icon: "🖥️", - title: "Look up any IP in the Console", - desc: "Search instantly from our Web UI. Get reputation score, behaviors, attack history, and CVE links.", - }, - { - num: 2, - icon: "🔑", - hint: "Integrate", - title: "Generate a CTI API key", - desc: "Unlock programmatic access to 30+ data points on IPs detected by CrowdSec Network.", - }, - { - num: 3, - icon: "⚙️", - hint: "Enrich", - title: "Connect to your SIEM/SOAR/TIP", - desc: "Native integrations for Splunk, Sentinel, QRadar, TheHive, OpenCTI, MISP, and more.", - }, - ], - }, +const blocklistSteps = [ + { + title: 'Create a blocklist integration endpoint', + desc: 'Generates a dedicated URL and credentials to serve blocklists to your perimeter devices.', + }, + { + title: 'Choose which blocklists to serve', + desc: 'Select from curated feeds by threat category: scanners, bots, TOR exits, exploits, and more.', + }, + { + title: 'Plug it in as an external threat feed', + desc: 'Point your firewall, CDN, or WAF at the endpoint. Use the feed to protect your infrastructure.', + }, ]; -// ── Page ────────────────────────────────────────────────────────────────────── - -const HomePage = () => { - useEffect(() => { - document.body.classList.add("homepage"); - document.documentElement.classList.add("homepage"); - return () => { - document.body.classList.remove("homepage"); - document.documentElement.classList.remove("homepage"); - }; - }, []); - - const [openSchema, setOpenSchema] = useState(null); - - const toggleSchema = (id: string) => setOpenSchema((prev) => (prev === id ? null : id)); - - return ( - -
- {/* Hero */} -
-
-
-

- Find the right -
- CrowdSec tool for you -

-

- IDPS/WAF | Blocklist feeds | IP Reputation -

-
-
- - {/* Search */} -
-
-
- -
-
-
- - {/* Intent strip */} -
-
-
- I want to… -
-
- {intents.map((i) => ( - - ))} -
- - {/* Existing user strip */} -
- - Already running CrowdSec? - -
- {[ - { label: "🖥️ Open the Console", href: "https://app.crowdsec.net", external: true }, - { label: "🛡️ Activate the WAF", href: "/docs/next/appsec/intro" }, - { label: "📊 Measure what is being Blocked", href: "/u/console/remediation_metrics" }, - { label: "🩺 Check my Stack Health", href: "/u/console/stackhealth" }, - ].map(({ label, href, external }) => ( - - {label} - {external && } - - ))} -
-
-
-
- - {/* How each path works - accordion */} -
-
-
-
💡 how each path works -
-
- - {schemas.map((s) => ( - toggleSchema(s.id)} /> - ))} -
-
+const ctiSteps = [ + { + title: 'Look up any IP in the Console', + desc: 'Search instantly from our Web UI. Get reputation score, behaviors, attack history, and CVE links.', + }, + { + title: 'Generate a CTI API key', + desc: 'Unlock programmatic access to 30+ data points on IPs detected by CrowdSec Network.', + }, + { + title: 'Connect to your SIEM/SOAR/TIP', + desc: 'Native integrations for Splunk, Sentinel, QRadar, TheHive, OpenCTI, MISP, and more.', + }, +]; - {/* Not sure / fallback */} -
-
-
-
-
Not sure where to start?
-
- Answer a few questions and get a recommended path with install steps for your stack. -
-
-
- - - - - - -
-
-
-
+const alreadyRunningPills = [ + { label: '🖥️ Open the Console', href: 'https://app.crowdsec.net' }, + { label: '🛡️ Activate the WAF', href: '/docs/next/appsec/intro' }, + { label: '📊 Measure what is being Blocked', href: '/u/console/remediation_metrics' }, + { label: '🩺 Check my Stack Health', href: '/u/console/stackhealth' }, +]; - {/* Popular docs */} -
-
-
- Popular docs -
-
- {[ - { label: "🖥️ Console", href: "/u/console/intro" }, - { label: "🛡️ AppSec / WAF", href: "/docs/next/appsec/intro" }, - { label: "💻 CLI Reference", href: "/docs/next/cscli/" }, - { label: "🔑 CTI API Keys", href: "/u/console/ip_reputation/api_keys" }, - { label: "❓ Troubleshooting", href: "/u/troubleshooting/intro" }, - // Need to redo the prompt this one is out of date - // { - // label: "📖 Docs AI Assistant", - // href: "https://chatgpt.com/g/g-682c3a61a78081918417571116c2b563-crowdsec-documentation", - // external: true, - // }, - { label: "🌐 WWW - CrowdSec", href: "https://www.crowdsec.net", external: true }, - ].map(({ label, href, external }) => ( - - {label} - {external && } - - ))} -
-
-
-
-
- ); -}; +const popularChips = [ + { label: '🖥️ Console', href: '/u/console/intro' }, + { label: '🛡️ AppSec / WAF', href: '/docs/next/appsec/intro' }, + { label: '💻 CLI Reference', href: '/docs/next/cscli/' }, + { label: '🔑 CTI API Keys', href: '/u/console/ip_reputation/api_keys' }, + { label: '❓ Troubleshooting', href: '/u/troubleshooting/intro' }, + { label: '🌐 CrowdSec.net', href: 'https://www.crowdsec.net' }, +]; -export default HomePage; +export default function HomePage() { + useEffect(() => { + document.body.classList.add('homepage'); + document.documentElement.classList.add('homepage'); + return () => { + document.body.classList.remove('homepage'); + document.documentElement.classList.remove('homepage'); + }; + }, []); + + return ( + +
+
+ {/* ── Hero ──────────────────────────────────────────────────── */} +
+ + + {/* ── Search ────────────────────────────────────────────────── */} +
+ +
+ + {/* ── Path cards ────────────────────────────────────────────── */} + + } + title="Detect & Block attacks on my servers" + desc="Locally identify and ban bad behaving IPs observed in your logs and requests with CrowdSec Detection Scenarios, and Virtual-Patching Collections." + tag="Security Engine" + href="/security-engine" + /> + } + title="Push a Blocklist into my firewall, CDN or WAF" + desc="You manage network perimeter devices and want a URL to subscribe to. No agent to install." + tag="Blocklist Integration" + href="/blocklists" + /> + } + title="Investigate IPs Behaviors and Enrich Alerts" + desc="You're a security analyst or developer who wants IP context, behaviors, CVEs, Aggressivity... In a browser or via REST API." + tag="IP Reputation & CTI" + href="/u/cti_api/intro" + /> + + + {/* ── Already running strip ─────────────────────────────────── */} + + + {/* ── How each path works ───────────────────────────────────── */} +
+
+ 💡 how each path works +
+
+ + + + + + {/* ── Guided setup card ─────────────────────────────────────── */} + + + {/* ── Popular docs ──────────────────────────────────────────── */} + +
+
+
+ ); +} diff --git a/crowdsec-docs/src/theme/DocItem/Content/index.tsx b/crowdsec-docs/src/theme/DocItem/Content/index.tsx new file mode 100644 index 000000000..3b1ca77b1 --- /dev/null +++ b/crowdsec-docs/src/theme/DocItem/Content/index.tsx @@ -0,0 +1,16 @@ +import { useDoc } from '@docusaurus/plugin-content-docs/client'; +import DocItemContent from '@theme-original/DocItem/Content'; +import type { Props } from '@theme/DocItem/Content'; +import React from 'react'; + +export default function DocItemContentWrapper(props: Props): React.JSX.Element { + const { frontMatter } = useDoc(); + const eyebrow = (frontMatter as Record).eyebrow as string | undefined; + + return ( + <> + {eyebrow && {eyebrow}} + + + ); +} diff --git a/crowdsec-docs/src/theme/MDXComponents.js b/crowdsec-docs/src/theme/MDXComponents.js new file mode 100644 index 000000000..9af0578ee --- /dev/null +++ b/crowdsec-docs/src/theme/MDXComponents.js @@ -0,0 +1,25 @@ +import MDXComponents from '@theme-original/MDXComponents'; +import AccessCard from '@site/src/components/docs/AccessCard'; +import ChallengeGrid from '@site/src/components/docs/ChallengeGrid'; +import ConsoleMockup from '@site/src/components/docs/ConsoleMockup'; +import DocsHero from '@site/src/components/docs/DocsHero'; +import GuidedSetupCard from '@site/src/components/docs/GuidedSetupCard'; +import PathCard from '@site/src/components/docs/PathCard'; +import PathCards from '@site/src/components/docs/PathCards'; +import PopularChips from '@site/src/components/docs/PopularChips'; +import PromoCard from '@site/src/components/docs/PromoCard'; +import RunningStrip from '@site/src/components/docs/RunningStrip'; + +export default { + ...MDXComponents, + AccessCard, + ChallengeGrid, + ConsoleMockup, + DocsHero, + GuidedSetupCard, + PathCard, + PathCards, + PopularChips, + PromoCard, + RunningStrip, +}; diff --git a/crowdsec-docs/src/theme/TOC/index.tsx b/crowdsec-docs/src/theme/TOC/index.tsx index 88ed93ad3..cc99b070d 100644 --- a/crowdsec-docs/src/theme/TOC/index.tsx +++ b/crowdsec-docs/src/theme/TOC/index.tsx @@ -1,24 +1,36 @@ -import ConsoleAd from "@site/src/components/console-ad"; -import TOCItems from "@theme/TOCItems"; -import clsx from "clsx"; -import React from "react"; +import { useLocation } from '@docusaurus/router'; +import PromoCard, { type PromoVariant } from '@site/src/components/docs/PromoCard'; +import TOCItems from '@theme/TOCItems'; +import clsx from 'clsx'; +import React from 'react'; -// Define custom classNames -const LINK_CLASS_NAME = "table-of-contents__link toc-highlight"; -const LINK_ACTIVE_CLASS_NAME = "table-of-contents__link--active"; +const LINK_CLASS_NAME = 'table-of-contents__link toc-highlight'; +const LINK_ACTIVE_CLASS_NAME = 'table-of-contents__link--active'; + +function usePromoVariant(): PromoVariant { + const { pathname } = useLocation(); + if (pathname.startsWith('/u/cti_api') || pathname.startsWith('/u/console/ip_reputation')) return 'cti'; + if (pathname.startsWith('/u/blocklists') || pathname.startsWith('/blocklists')) return 'engine'; + if (pathname.startsWith('/u/console')) return 'console'; + return 'console'; +} + +function RailPromoCard() { + const variant = usePromoVariant(); + return ; +} -// Modified Table of Contents component const TableOfContent = ({ className, ...props }): React.JSX.Element => ( - + ); export default TableOfContent; diff --git a/crowdsec-docs/unversioned/console/intro.mdx b/crowdsec-docs/unversioned/console/intro.mdx index db08e2477..8731eac7b 100644 --- a/crowdsec-docs/unversioned/console/intro.mdx +++ b/crowdsec-docs/unversioned/console/intro.mdx @@ -1,6 +1,8 @@ --- id: intro title: Introduction to the CrowdSec Console +eyebrow: Console · Introduction +rightRailPromo: console --- CrowdSec is an open-source and collaborative cybersecurity solution that protects websites, servers, and IT infrastructures from attacks. It leverages the power of the community to create a continuously updated security model that adapts to emerging threats. By analyzing users' logs in real-time, CrowdSec identifies and blocks malicious behavior across the entire network. The participatory approach ensures that the intelligence gathered is shared across the community when one user is attacked, enabling all users to block potential threats without external intervention. @@ -9,17 +11,58 @@ CrowdSec is an open-source and collaborative cybersecurity solution that protect The CrowdSec Console is a central component of the CrowdSec ecosystem, offering a user-friendly web interface that allows administrators to visualize and manage the security data generated by CrowdSec. The Console enhances the overall usability of CrowdSec by providing a clear, intuitive platform for monitoring threats and adjusting security policies on IT infrastructures. + + ## A tool to mitigate security challenges The CrowdSec Console addresses several key cybersecurity challenges: -* **Complexity of Threat Detection**: By providing a unified interface for monitoring and analysis, it simplifies the process of identifying and understanding security threats. -* **Response Time**: Enhances the ability to quickly respond to and mitigate detected threats, reducing potential damage. -* **Scalability and Management**: This offers a scalable solution that can manage security data across multiple installations, ideal for small and large businesses. -* **Proactive Threat Blocking**: The Console enables users to leverage dynamically updated blocklists, which are crucial for preemptively blocking known malicious IP addresses before they can attack. These blocklists are generated through the collective intelligence of the CrowdSec community, offering a robust layer of defense against widespread threats. -* **Informed Decision-Making**: Access to detailed CTI data enables administrators to make informed decisions about their security posture. It helps in understanding adversaries' tactics, techniques, and procedures (TTPs), thus improving the effectiveness of response strategies and security policies. +, + color: "var(--cs-orange)", + title: "Complexity of Threat Detection", + body: "By providing a unified interface for monitoring and analysis, it simplifies the process of identifying and understanding security threats." + }, + { + icon: , + color: "var(--cs-teal)", + title: "Response Time", + body: "Enhances the ability to quickly respond to and mitigate detected threats, reducing potential damage." + }, + { + icon: , + color: "var(--cs-violet)", + title: "Scalability and Management", + body: "Offers a scalable solution that can manage security data across multiple installations, ideal for small and large businesses." + }, + { + icon: , + color: "var(--cs-blue)", + title: "Proactive Threat Blocking", + body: "Enables users to leverage dynamically updated blocklists, crucial for preemptively blocking known malicious IP addresses before they can attack." + }, + { + icon: , + color: "var(--cs-pink)", + title: "Informed Decision-Making", + body: "Access to detailed CTI data enables administrators to make informed decisions about their security posture, improving effectiveness of response strategies." + }, + { + icon: , + color: "var(--cs-teal)", + title: "Visibility & Metrics", + body: "Track remediation metrics, alert trends, and community contributions to continuously improve your security posture." + } +]} /> ## Accessing the Console -Getting access to the Console is as simple as following [this link](https://app.crowdsec.net/signup?mtm_campaign=Console&mtm_source=docs&mtm_medium=pageAd&mtm_content=Introduction) and creating your account. +Getting access to the Console is straightforward: + From cfb3914667a9a708bdd20ed21aa06c684ea0f355 Mon Sep 17 00:00:00 2001 From: Roussange Alexandre Date: Thu, 7 May 2026 16:08:39 +0200 Subject: [PATCH 02/17] fix(ui): apply visual feedback corrections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MDXComponents: rename .js → .tsx; add explicit imports in console/intro.mdx to fix "AccessCard not defined" error - Hero: full-width section with 64px grid background, masked radial fade; h1 centered with orange gradient on 'CrowdSec tool' span; orange dot badge above heading - PathCard: rewritten to match mockup exactly — corner radial glow, 40px icon box with color border, eyebrow top-right, tags row, dashed footer divider with audience label + arrow CTA - RunningStrip: gradient background, Eyebrow on left, spacer, colored icon links on right with hover state - PathwayRow: CTA button always in header when closed; moves into content body when accordion is open; sub-text appears under title when expanded - Navbar: backdrop-filter blur(8px), rgba dark background, orange hover/active link color, border from --cs-border token - Footer: token-based styling — dark bg, mono uppercase titles, dimmed link text, orange hover, mono copyright strip --- .../src/components/docs/PathCard/index.tsx | 170 ++++++++-- .../src/components/docs/PathwayRow/index.tsx | 206 ++++++++++-- .../components/docs/RunningStrip/index.tsx | 76 ++++- crowdsec-docs/src/css/crowdsec-tokens.css | 40 +++ crowdsec-docs/src/css/navbar.css | 34 +- crowdsec-docs/src/pages/index.tsx | 318 +++++++++--------- .../{MDXComponents.js => MDXComponents.tsx} | 0 crowdsec-docs/unversioned/console/intro.mdx | 4 + 8 files changed, 621 insertions(+), 227 deletions(-) rename crowdsec-docs/src/theme/{MDXComponents.js => MDXComponents.tsx} (100%) diff --git a/crowdsec-docs/src/components/docs/PathCard/index.tsx b/crowdsec-docs/src/components/docs/PathCard/index.tsx index d94fbf11a..bb856e2b0 100644 --- a/crowdsec-docs/src/components/docs/PathCard/index.tsx +++ b/crowdsec-docs/src/components/docs/PathCard/index.tsx @@ -1,39 +1,167 @@ -import React from 'react'; -import styles from './index.module.css'; +import React, { useState } from 'react'; type Props = { + eyebrow?: string; color: string; icon: React.ReactNode; title: string; desc: string; tag: string; + tags?: string[]; + audience?: string; href: string; }; -export default function PathCard({ color, icon, title, desc, tag, href }: Props) { - const handleMouseEnter = (e: React.MouseEvent) => { - const el = e.currentTarget; - el.style.boxShadow = `0 8px 24px ${color}22, 0 0 0 1px ${color}66`; - el.style.borderLeftColor = color; - }; - const handleMouseLeave = (e: React.MouseEvent) => { - const el = e.currentTarget; - el.style.boxShadow = ''; - el.style.borderLeftColor = ''; - }; +export default function PathCard({ eyebrow, color, icon, title, desc, tag, tags = [], audience, href }: Props) { + const [hover, setHover] = useState(false); return ( setHover(true)} + onMouseLeave={() => setHover(false)} + style={{ + position: 'relative', + padding: 24, + background: hover ? 'var(--cs-surface-2)' : 'var(--cs-surface)', + border: `1px solid ${hover ? `${color}55` : 'var(--cs-border)'}`, + borderRadius: 14, + transition: 'all 200ms cubic-bezier(.16,1,.3,1)', + transform: hover ? 'translateY(-2px)' : 'none', + boxShadow: hover + ? `0 18px 48px rgba(0,0,0,0.45), 0 0 0 1px ${color}22 inset` + : '0 1px 0 rgba(255,255,255,0.02) inset', + overflow: 'hidden', + cursor: 'pointer', + display: 'flex', + flexDirection: 'column', + textDecoration: 'none', + color: 'inherit', + }} > -
{icon}
-
{title}
-
{desc}
- → {tag} + {/* Corner glow */} +
+ + {/* Header row: icon + eyebrow */} +
+
+ {icon} +
+ {eyebrow && ( +
+ {eyebrow} +
+ )} +
+ +

+ {title} +

+ +

+ {desc} +

+ + {/* Tags */} + {tags.length > 0 && ( +
+ {tags.map((t, i) => ( + {t} + ))} +
+ )} + + {/* Footer */} +
+ {audience && ( +
+ {audience} +
+ )} +
+ {tag} + +
+
); } diff --git a/crowdsec-docs/src/components/docs/PathwayRow/index.tsx b/crowdsec-docs/src/components/docs/PathwayRow/index.tsx index ab9e05b5f..7b277ca11 100644 --- a/crowdsec-docs/src/components/docs/PathwayRow/index.tsx +++ b/crowdsec-docs/src/components/docs/PathwayRow/index.tsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import styles from './index.module.css'; type Step = { title: string; desc: string }; @@ -7,67 +6,200 @@ type Props = { color: string; title: string; eyebrow: string; + sub?: string; steps: Step[]; ctaLabel: string; ctaHref: string; defaultOpen?: boolean; + icon?: React.ReactNode; }; -export default function PathwayRow({ color, title, eyebrow, steps, ctaLabel, ctaHref, defaultOpen = false }: Props) { +function ArrowIcon() { + return ( + + ); +} + +export default function PathwayRow({ color, title, eyebrow, sub, steps, ctaLabel, ctaHref, defaultOpen = false, icon }: Props) { const [open, setOpen] = useState(defaultOpen); return ( -
-
setOpen((v) => !v)} role="button" aria-expanded={open}> -
-
-
-
{eyebrow}
-
{title}
+
+ {/* Header row — always visible */} +
+ {/* Color rail */} +
+ + {/* Icon */} + {icon && ( +
+ {icon} +
+ )} + + {/* Labels */} +
+
+ {eyebrow} +
+
+ {title}
+ {open && sub && ( +
+ {sub} +
+ )}
- + +
+ {/* Expanded body — steps + CTA */} {open && ( -
-
+
+
{steps.map((step, i) => ( -
-
+
0 ? '1px solid var(--cs-border)' : undefined, + }}> +
{String(i + 1).padStart(2, '0')}
-
-
{step.title}
-
{step.desc}
+
+
+ {step.title} +
+
+ {step.desc} +
))}
+ + {/* CTA in content when OPEN */} - {ctaLabel} → + {ctaLabel}
)} diff --git a/crowdsec-docs/src/components/docs/RunningStrip/index.tsx b/crowdsec-docs/src/components/docs/RunningStrip/index.tsx index fb1e337dc..a2855203c 100644 --- a/crowdsec-docs/src/components/docs/RunningStrip/index.tsx +++ b/crowdsec-docs/src/components/docs/RunningStrip/index.tsx @@ -1,21 +1,75 @@ import React from 'react'; -import styles from './index.module.css'; -type Pill = { label: string; href: string }; +type Link = { icon?: React.ReactNode; label: string; href: string; color?: string; ext?: boolean }; type Props = { - label: string; - pills: Pill[]; + label?: string; + links: Link[]; }; -export default function RunningStrip({ label, pills }: Props) { +export default function RunningStrip({ label = 'Already running CrowdSec?', links }: Props) { return ( -
- {label} -
- {pills.map((p) => ( - - {p.label} +
+
+ {label} +
+ +
+ + diff --git a/crowdsec-docs/src/css/crowdsec-tokens.css b/crowdsec-docs/src/css/crowdsec-tokens.css index da30f7576..26961dc10 100644 --- a/crowdsec-docs/src/css/crowdsec-tokens.css +++ b/crowdsec-docs/src/css/crowdsec-tokens.css @@ -190,3 +190,43 @@ a { border-bottom-color: var(--cs-orange) !important; color: var(--cs-orange) !important; } + +/* ── Footer ─────────────────────────────────────────────────────────────── */ +.footer { + background: var(--cs-bg) !important; + border-top: 1px solid var(--cs-border); + padding: 40px 0 28px; +} + +.footer__title { + font-family: var(--cs-font-mono); + font-size: 10.5px; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--cs-ink-mute) !important; + font-weight: 600; + margin-bottom: 12px; +} + +.footer__link-item { + color: var(--cs-ink-dim) !important; + font-size: 13.5px; + transition: color 0.15s; +} + +.footer__link-item:hover { + color: var(--cs-orange) !important; +} + +.footer__copyright { + color: var(--cs-ink-mute); + font-size: 12px; + font-family: var(--cs-font-mono); + border-top: 1px solid var(--cs-border); + padding-top: 20px; + margin-top: 8px; +} + +[data-theme='light'] .footer { + background: var(--cs-surface-2) !important; +} diff --git a/crowdsec-docs/src/css/navbar.css b/crowdsec-docs/src/css/navbar.css index f50ce34da..485102225 100644 --- a/crowdsec-docs/src/css/navbar.css +++ b/crowdsec-docs/src/css/navbar.css @@ -1,20 +1,40 @@ .navbar { - @apply border-b border-border border-solid; + background: rgba(8, 14, 26, 0.85) !important; + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + border-bottom: 1px solid var(--cs-border) !important; +} + +[data-theme='light'] .navbar { + background: rgba(250, 250, 247, 0.90) !important; + border-bottom: 1px solid var(--cs-border) !important; } .navbar__link { - --ifm-navbar-link-hover-color: rgb(var(--primary)); + --ifm-navbar-link-hover-color: var(--cs-orange); + color: var(--cs-ink-dim); + font-size: 13.5px; + font-weight: 500; + border-radius: 6px; + padding: 6px 10px; + transition: color 0.15s, background 0.15s; +} + +.navbar__link:hover, +.navbar__link--active { + color: var(--cs-orange) !important; + background: color-mix(in srgb, var(--cs-orange) 12%, transparent); } .navbar, .navbar-sidebar { - @apply bg-card text-foreground border-border/80; - --ifm-navbar-link-color: rgb(var(--foreground)); - --ifm-menu-color: rgb(var(--foreground)); + --ifm-navbar-link-color: var(--cs-ink-dim); + --ifm-menu-color: var(--cs-ink-dim); } -.navbar { - @apply border border-b border-solid; +/* Logo area */ +.navbar__logo { + height: 28px; } .navbar-separator { diff --git a/crowdsec-docs/src/pages/index.tsx b/crowdsec-docs/src/pages/index.tsx index cafa1c32d..7adcde781 100644 --- a/crowdsec-docs/src/pages/index.tsx +++ b/crowdsec-docs/src/pages/index.tsx @@ -8,70 +8,55 @@ import PopularChips from '../components/docs/PopularChips'; import RunningStrip from '../components/docs/RunningStrip'; import GuidedSetupCard from '../components/docs/GuidedSetupCard'; import PathwayRow from '../components/docs/PathwayRow'; -import ShieldIcon from '../components/docs/icons/ShieldIcon'; -import BlocklistIcon from '../components/docs/icons/BlocklistIcon'; -import CtiIcon from '../components/docs/icons/CtiIcon'; -/* ── Colour tokens — must match crowdsec-tokens.css dark values so the - home page (which sits outside doc pages) also looks correct. */ +/* ── Colour vars — reference CSS tokens (work in dark + light) */ const CS_ORANGE = 'var(--cs-orange)'; const CS_TEAL = 'var(--cs-teal)'; const CS_VIOLET = 'var(--cs-violet)'; +const CS_BLUE = 'var(--cs-blue)'; + +/* ── Small inline icons for the RunningStrip */ +function IconCompass() { + return ; +} +function IconShield() { + return ; +} +function IconGauge() { + return ; +} +function IconPulse() { + return ; +} +function IconFeed() { + return ; +} +function IconGlobe() { + return ; +} const securityEngineSteps = [ - { - title: 'Install the Security Engine', - desc: 'Runs on your server, detects attack patterns in real time. Immediately protected with the CrowdSec Community Blocklist.', - }, - { - title: 'Activate the WAF module', - desc: 'Layer in the AppSec component to inspect HTTP traffic and block web exploits before they reach your app.', - }, - { - title: 'Subscribe to blocklists', - desc: 'Add a selection of extra blocklists on top of the built-in detection & community blocklist.', - }, - { - title: 'Craft your own rules', - desc: 'Write custom scenarios for your stack, then share them back with the community on the Hub.', - }, + { title: 'Install the Security Engine', desc: 'Runs on your server, detects attack patterns in real time. Immediately protected with the CrowdSec Community Blocklist.' }, + { title: 'Activate the WAF module', desc: 'Layer in the AppSec component to inspect HTTP traffic and block web exploits before they reach your app.' }, + { title: 'Subscribe to blocklists', desc: 'Add a selection of extra blocklists on top of the built-in detection & community blocklist.' }, + { title: 'Craft your own rules', desc: 'Write custom scenarios for your stack, then share them back with the community on the Hub.' }, ]; - const blocklistSteps = [ - { - title: 'Create a blocklist integration endpoint', - desc: 'Generates a dedicated URL and credentials to serve blocklists to your perimeter devices.', - }, - { - title: 'Choose which blocklists to serve', - desc: 'Select from curated feeds by threat category: scanners, bots, TOR exits, exploits, and more.', - }, - { - title: 'Plug it in as an external threat feed', - desc: 'Point your firewall, CDN, or WAF at the endpoint. Use the feed to protect your infrastructure.', - }, + { title: 'Create a blocklist integration endpoint', desc: 'Generates a dedicated URL and credentials to serve blocklists to your perimeter devices.' }, + { title: 'Choose which blocklists to serve', desc: 'Select from curated feeds by threat category: scanners, bots, TOR exits, exploits, and more.' }, + { title: 'Plug it in as an external threat feed', desc: 'Point your firewall, CDN, or WAF at the endpoint. Use the feed to protect your infrastructure.' }, ]; - const ctiSteps = [ - { - title: 'Look up any IP in the Console', - desc: 'Search instantly from our Web UI. Get reputation score, behaviors, attack history, and CVE links.', - }, - { - title: 'Generate a CTI API key', - desc: 'Unlock programmatic access to 30+ data points on IPs detected by CrowdSec Network.', - }, - { - title: 'Connect to your SIEM/SOAR/TIP', - desc: 'Native integrations for Splunk, Sentinel, QRadar, TheHive, OpenCTI, MISP, and more.', - }, + { title: 'Look up any IP in the Console', desc: 'Search instantly from our Web UI. Get reputation score, behaviors, attack history, and CVE links.' }, + { title: 'Generate a CTI API key', desc: 'Unlock programmatic access to 30+ data points on IPs detected by CrowdSec Network.' }, + { title: 'Connect to your SIEM/SOAR/TIP', desc: 'Native integrations for Splunk, Sentinel, QRadar, TheHive, OpenCTI, MISP, and more.' }, ]; -const alreadyRunningPills = [ - { label: '🖥️ Open the Console', href: 'https://app.crowdsec.net' }, - { label: '🛡️ Activate the WAF', href: '/docs/next/appsec/intro' }, - { label: '📊 Measure what is being Blocked', href: '/u/console/remediation_metrics' }, - { label: '🩺 Check my Stack Health', href: '/u/console/stackhealth' }, +const alreadyRunningLinks = [ + { icon: , label: 'Open the Console', href: 'https://app.crowdsec.net', color: CS_ORANGE, ext: true }, + { icon: , label: 'Activate the WAF', href: '/docs/next/appsec/intro', color: CS_TEAL }, + { icon: , label: 'Measure what is being blocked', href: '/u/console/remediation_metrics', color: CS_VIOLET }, + { icon: , label: 'Check my Stack Health', href: '/u/console/stackhealth', color: CS_BLUE }, ]; const popularChips = [ @@ -96,148 +81,179 @@ export default function HomePage() { return (
-
- {/* ── Hero ──────────────────────────────────────────────────── */} -
-
diff --git a/crowdsec-docs/src/theme/MDXComponents.js b/crowdsec-docs/src/theme/MDXComponents.tsx similarity index 100% rename from crowdsec-docs/src/theme/MDXComponents.js rename to crowdsec-docs/src/theme/MDXComponents.tsx diff --git a/crowdsec-docs/unversioned/console/intro.mdx b/crowdsec-docs/unversioned/console/intro.mdx index 8731eac7b..165b06458 100644 --- a/crowdsec-docs/unversioned/console/intro.mdx +++ b/crowdsec-docs/unversioned/console/intro.mdx @@ -5,6 +5,10 @@ eyebrow: Console · Introduction rightRailPromo: console --- +import AccessCard from '@site/src/components/docs/AccessCard'; +import ChallengeGrid from '@site/src/components/docs/ChallengeGrid'; +import ConsoleMockup from '@site/src/components/docs/ConsoleMockup'; + CrowdSec is an open-source and collaborative cybersecurity solution that protects websites, servers, and IT infrastructures from attacks. It leverages the power of the community to create a continuously updated security model that adapts to emerging threats. By analyzing users' logs in real-time, CrowdSec identifies and blocks malicious behavior across the entire network. The participatory approach ensures that the intelligence gathered is shared across the community when one user is attacked, enabling all users to block potential threats without external intervention. ## The CrowdSec Console From e87241cf2c0e04ed231748018a84fdd29e14ff7e Mon Sep 17 00:00:00 2001 From: Roussange Alexandre Date: Thu, 7 May 2026 17:41:46 +0200 Subject: [PATCH 03/17] feat(ui): refactor home page components to use them across the global doc --- crowdsec-docs/plugins/gtag/gtag.ts | 6 +- .../components/docs/ChallengeGrid/index.tsx | 120 +++++++-- .../src/components/docs/DocCard/index.tsx | 251 ++++++++++++++++++ .../src/components/docs/DocCardGrid/index.tsx | 19 ++ .../src/components/docs/PathCard/index.tsx | 41 +-- .../docs/PathCards/index.module.css | 15 +- .../src/components/docs/PathwayRow/index.tsx | 126 ++++++--- .../src/components/docs/QuickStrip/index.tsx | 118 ++++++++ crowdsec-docs/src/css/crowdsec-tokens.css | 184 ++++++++++--- crowdsec-docs/src/css/custom.css | 2 +- crowdsec-docs/src/css/navbar.css | 85 ++++-- crowdsec-docs/src/pages/index.tsx | 32 +-- .../theme/DocSidebarItem/Category/index.tsx | 18 +- .../src/theme/DocSidebarItem/Link/index.tsx | 21 +- .../src/theme/Footer/Layout/index.tsx | 145 +++++++++- crowdsec-docs/src/theme/MDXComponents.tsx | 6 + crowdsec-docs/unversioned/console/intro.mdx | 42 +-- .../console/ip_reputation/intro.mdx | 68 +++-- .../premium_upgrade/features_overview.mdx | 13 +- .../premium_upgrade/testing_premium.mdx | 126 ++------- crowdsec-docs/unversioned/cti_api/intro.mdx | 139 +++++----- 21 files changed, 1146 insertions(+), 431 deletions(-) create mode 100644 crowdsec-docs/src/components/docs/DocCard/index.tsx create mode 100644 crowdsec-docs/src/components/docs/DocCardGrid/index.tsx create mode 100644 crowdsec-docs/src/components/docs/QuickStrip/index.tsx diff --git a/crowdsec-docs/plugins/gtag/gtag.ts b/crowdsec-docs/plugins/gtag/gtag.ts index 163057706..fbbf10d41 100644 --- a/crowdsec-docs/plugins/gtag/gtag.ts +++ b/crowdsec-docs/plugins/gtag/gtag.ts @@ -24,8 +24,10 @@ const clientModule = { setTimeout(() => { // Always refer to the variable on window in case it gets overridden // elsewhere. - window.gtag("set", "page_path", location.pathname + location.search + location.hash) - window.gtag("event", "page_view") + if (typeof window.gtag === "function") { + window.gtag("set", "page_path", location.pathname + location.search + location.hash) + window.gtag("event", "page_view") + } }) } }, diff --git a/crowdsec-docs/src/components/docs/ChallengeGrid/index.tsx b/crowdsec-docs/src/components/docs/ChallengeGrid/index.tsx index b2f6b4eb8..8314d426c 100644 --- a/crowdsec-docs/src/components/docs/ChallengeGrid/index.tsx +++ b/crowdsec-docs/src/components/docs/ChallengeGrid/index.tsx @@ -1,8 +1,57 @@ import React from 'react'; -import styles from './index.module.css'; + +/* Named icon set — keeps MDX content simple: pass iconName="search" */ +const ICONS: Record = { + search: ( + + ), + pulse: ( + + ), + box: ( + + ), + shield: ( + + ), + compass: ( + + ), + gauge: ( + + ), + lock: ( + + ), + globe: ( + + ), + bell: ( + + ), +}; export type Challenge = { - icon: React.ReactNode; + iconName?: string; + icon?: React.ReactNode; color: string; title: string; body: string; @@ -14,23 +63,58 @@ type Props = { export default function ChallengeGrid({ challenges }: Props) { return ( -
- {challenges.map((c, i) => ( -
-
- {c.icon} +
+ {challenges.map((c, i) => { + const icon = c.icon ?? (c.iconName ? ICONS[c.iconName] : null); + return ( +
+
+ {icon && ( +
+
{icon}
+
+ )} +
+ {String(i + 1).padStart(2, '0')} +
+
+
+ {c.title} +
+
+ {c.body} +
-
{c.title}
-
{c.body}
-
- ))} + ); + })}
); } diff --git a/crowdsec-docs/src/components/docs/DocCard/index.tsx b/crowdsec-docs/src/components/docs/DocCard/index.tsx new file mode 100644 index 000000000..5e71002db --- /dev/null +++ b/crowdsec-docs/src/components/docs/DocCard/index.tsx @@ -0,0 +1,251 @@ +import React from 'react'; + +/* Shared icon set — same as ChallengeGrid. Pass iconName="search" */ +const ICONS: Record = { + search: , + key: , + terminal: , + globe: , + shield: , + feed: , + compass: , + book: , + plug: , + alert: , + gauge: , + lock: , + box: , + pulse: , + users: , +}; + +export type DocCardLink = { + label: string; + href: string; + external?: boolean; +}; + +export type DocCardProps = { + /* Content */ + title: string; + desc?: string; + badge?: string; + + /* Icon — pass either iconName (string key) or a ReactNode */ + iconName?: string; + icon?: React.ReactNode; + + /* Color — must be a CSS var or valid CSS color. Defaults to --cs-orange */ + color?: string; + + /* CTA */ + href?: string; + ctaLabel?: string; + links?: DocCardLink[]; + + /* Optional shell command */ + command?: string; + + /* Misc */ + premium?: boolean; + children?: React.ReactNode; +}; + +const mix = (c: string, pct: number) => `color-mix(in srgb, ${c} ${pct}%, transparent)`; + +function ExternalArrow() { + return ( + + ); +} + +function ArrowRight() { + return ( + + ); +} + +export default function DocCard({ + title, + desc, + badge, + iconName, + icon, + color = 'var(--cs-orange)', + href, + ctaLabel, + links, + command, + premium, + children, +}: DocCardProps) { + const resolvedIcon = icon ?? (iconName ? ICONS[iconName] : null); + + const card = ( +
+ {/* Header: icon + title + premium badge */} +
+ {resolvedIcon && ( +
+
{resolvedIcon}
+
+ )} +
+ {badge && ( +
+ {badge} +
+ )} +
+
+ {title} +
+ {premium && ( + Premium + )} +
+
+
+ + {/* Description */} + {desc && ( +
+ {desc} +
+ )} + + {/* Extra content slot */} + {children &&
{children}
} + + {/* Command block */} + {command && ( +
+ $ + + {command} + +
+ )} + + {/* Link list */} + {links && links.length > 0 && ( +
+ {links.map((l) => ( + + {l.label} + {l.external ? : } + + ))} +
+ )} + + {/* Single CTA */} + {ctaLabel && href && ( + + {ctaLabel} + + )} +
+ ); + + if (href && !ctaLabel && !links) { + return ( + + {card} + + ); + } + + return card; +} diff --git a/crowdsec-docs/src/components/docs/DocCardGrid/index.tsx b/crowdsec-docs/src/components/docs/DocCardGrid/index.tsx new file mode 100644 index 000000000..ca4c75721 --- /dev/null +++ b/crowdsec-docs/src/components/docs/DocCardGrid/index.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +type Props = { + children: React.ReactNode; + cols?: 2 | 3; +}; + +export default function DocCardGrid({ children, cols = 3 }: Props) { + return ( +
+ {children} +
+ ); +} diff --git a/crowdsec-docs/src/components/docs/PathCard/index.tsx b/crowdsec-docs/src/components/docs/PathCard/index.tsx index bb856e2b0..2cd008241 100644 --- a/crowdsec-docs/src/components/docs/PathCard/index.tsx +++ b/crowdsec-docs/src/components/docs/PathCard/index.tsx @@ -12,6 +12,9 @@ type Props = { href: string; }; +/* color-mix() helper so CSS vars like var(--cs-orange) can be used with opacity */ +const mix = (c: string, pct: number) => `color-mix(in srgb, ${c} ${pct}%, transparent)`; + export default function PathCard({ eyebrow, color, icon, title, desc, tag, tags = [], audience, href }: Props) { const [hover, setHover] = useState(false); @@ -24,19 +27,21 @@ export default function PathCard({ eyebrow, color, icon, title, desc, tag, tags position: 'relative', padding: 24, background: hover ? 'var(--cs-surface-2)' : 'var(--cs-surface)', - border: `1px solid ${hover ? `${color}55` : 'var(--cs-border)'}`, + /* Border matches icon color on hover */ + border: `1px solid ${hover ? mix(color, 40) : 'var(--cs-border)'}`, borderRadius: 14, - transition: 'all 200ms cubic-bezier(.16,1,.3,1)', - transform: hover ? 'translateY(-2px)' : 'none', + transition: 'border-color 200ms, background 200ms, box-shadow 200ms', + /* No translateY — avoids layout shift */ boxShadow: hover - ? `0 18px 48px rgba(0,0,0,0.45), 0 0 0 1px ${color}22 inset` - : '0 1px 0 rgba(255,255,255,0.02) inset', + ? `0 16px 40px rgba(0,0,0,0.40), inset 0 0 0 1px ${mix(color, 15)}` + : 'inset 0 1px 0 rgba(255,255,255,0.02)', overflow: 'hidden', cursor: 'pointer', display: 'flex', flexDirection: 'column', textDecoration: 'none', color: 'inherit', + minWidth: 0, }} > {/* Corner glow */} @@ -49,7 +54,7 @@ export default function PathCard({ eyebrow, color, icon, title, desc, tag, tags borderRadius: '50%', background: color, filter: 'blur(60px)', - opacity: hover ? 0.18 : 0.10, + opacity: hover ? 0.18 : 0.09, transition: 'opacity 200ms', pointerEvents: 'none', }} /> @@ -60,8 +65,9 @@ export default function PathCard({ eyebrow, color, icon, title, desc, tag, tags width: 40, height: 40, borderRadius: 10, - background: `${color}15`, - border: `1px solid ${color}33`, + /* Use color-mix so CSS vars work as opacity-tinted backgrounds */ + background: mix(color, 14), + border: `1px solid ${mix(color, 28)}`, display: 'flex', alignItems: 'center', justifyContent: 'center', @@ -75,7 +81,7 @@ export default function PathCard({ eyebrow, color, icon, title, desc, tag, tags fontFamily: 'var(--cs-font-mono)', fontSize: 11, letterSpacing: '0.18em', - textTransform: 'uppercase', + textTransform: 'uppercase' as const, color, fontWeight: 500, }}> @@ -114,10 +120,10 @@ export default function PathCard({ eyebrow, color, icon, title, desc, tag, tags fontFamily: 'var(--cs-font-mono)', fontSize: 10.5, letterSpacing: '0.06em', - textTransform: 'uppercase', + textTransform: 'uppercase' as const, color: i === 0 ? color : 'var(--cs-ink-dim)', - border: `1px solid ${i === 0 ? `${color}33` : 'rgba(148,163,184,0.20)'}`, - background: i === 0 ? `${color}0d` : 'rgba(148,163,184,0.06)', + border: `1px solid ${i === 0 ? mix(color, 28) : mix('var(--cs-ink-dim)', 20)}`, + background: i === 0 ? mix(color, 8) : mix('var(--cs-ink-dim)', 6), padding: '3px 8px', borderRadius: 4, fontWeight: 500, @@ -130,7 +136,7 @@ export default function PathCard({ eyebrow, color, icon, title, desc, tag, tags
{audience}
@@ -152,9 +162,10 @@ export default function PathCard({ eyebrow, color, icon, title, desc, tag, tags color, fontWeight: 600, fontSize: 13, - transform: hover ? 'translateX(2px)' : 'none', - transition: 'transform 180ms', marginLeft: 'auto', + transition: 'gap 160ms', + whiteSpace: 'nowrap', + flexShrink: 0, }}> {tag}
@@ -52,8 +71,8 @@ export default function PathwayRow({ color, title, eyebrow, sub, steps, ctaLabel width: 36, height: 36, borderRadius: 9, - background: `${color}15`, - border: `1px solid ${color}33`, + background: mix(color, 14), + border: `1px solid ${mix(color, 28)}`, display: 'flex', alignItems: 'center', justifyContent: 'center', @@ -86,7 +105,7 @@ export default function PathwayRow({ color, title, eyebrow, sub, steps, ctaLabel )}
- {/* CTA — visible in header when CLOSED, hidden when open */} + {/* CTA in header when CLOSED */} {!open && (
- {/* Expanded body — steps + CTA */} + {/* Expanded body — horizontal steps grid + CTA */} {open && (
-
+ {/* Horizontal step grid */} +
{steps.map((step, i) => (
0 ? '1px solid var(--cs-border)' : undefined, + padding: '12px 14px', + borderRadius: 9, + background: 'var(--cs-surface-2)', + border: `1px solid var(--cs-border)`, + position: 'relative', }}> -
- {String(i + 1).padStart(2, '0')} -
-
-
- {step.title} -
-
- {step.desc} + {/* Step number + optional hint badge in same row */} +
+
+ {String(i + 1).padStart(2, '0')}
+ {step.hint && ( + + {step.hint} + + )} +
+
+ {step.title} +
+
+ {step.desc}
))} @@ -192,11 +231,12 @@ export default function PathwayRow({ color, title, eyebrow, sub, steps, ctaLabel padding: '8px 18px', borderRadius: 7, background: color, - color: '#0A1120', + color: 'var(--cs-btn-text)', fontSize: 13, fontWeight: 600, textDecoration: 'none', - boxShadow: `0 6px 20px ${color}40`, + boxShadow: `0 4px 16px ${mix(color, 30)}`, + whiteSpace: 'nowrap', }} > {ctaLabel} diff --git a/crowdsec-docs/src/components/docs/QuickStrip/index.tsx b/crowdsec-docs/src/components/docs/QuickStrip/index.tsx new file mode 100644 index 000000000..121469280 --- /dev/null +++ b/crowdsec-docs/src/components/docs/QuickStrip/index.tsx @@ -0,0 +1,118 @@ +import React from 'react'; + +export type QuickStripLink = { + label: string; + href: string; + icon?: React.ReactNode; + external?: boolean; + color?: string; +}; + +export type QuickStripProps = { + /** Simple monospace eyebrow label on the left (e.g. "Quick access") */ + label?: string; + /** Bold title — for the "Need help?" / rich-left variant */ + title?: string; + /** Subtitle below the title */ + subtitle?: string; + links: QuickStripLink[]; +}; + +function ExternalArrow() { + return ( + + ); +} + +export default function QuickStrip({ label, title, subtitle, links }: QuickStripProps) { + return ( +
+ {/* Left: simple label OR rich title+subtitle */} + {(label || title) && ( +
+ {label && ( +
+ {label} +
+ )} + {title && ( +
+ {title} +
+ )} + {subtitle && ( +
+ {subtitle} +
+ )} +
+ )} + +
+ ); +} diff --git a/crowdsec-docs/src/css/crowdsec-tokens.css b/crowdsec-docs/src/css/crowdsec-tokens.css index 26961dc10..3a1579c5f 100644 --- a/crowdsec-docs/src/css/crowdsec-tokens.css +++ b/crowdsec-docs/src/css/crowdsec-tokens.css @@ -27,6 +27,9 @@ --cs-blue: #60A5FA; --cs-pink: #F472B6; + /* Button text: dark on bright/light bg in dark mode */ + --cs-btn-text: #0A1120; + /* Tinted soft backgrounds via color-mix */ --cs-orange-soft: color-mix(in srgb, var(--cs-orange) 12%, transparent); --cs-teal-soft: color-mix(in srgb, var(--cs-teal) 12%, transparent); @@ -95,12 +98,15 @@ --cs-ink-dim: #475569; --cs-ink-mute: #64748B; - /* Brand + path — darkened for AA on light backgrounds */ - --cs-orange: #C77808; - --cs-teal: #047857; - --cs-violet: #6D4FE0; - --cs-blue: #2563EB; - --cs-pink: #DB2777; + /* Brand + path — vibrant enough to carry white text (AA large text) */ + --cs-orange: #D4890A; + --cs-teal: #0A9960; + --cs-violet: #7C5CE8; + --cs-blue: #2B72E5; + --cs-pink: #E03880; + + /* Button text: white on dark/vibrant bg in light mode */ + --cs-btn-text: #FFFFFF; --cs-orange-soft: color-mix(in srgb, var(--cs-orange) 10%, transparent); --cs-teal-soft: color-mix(in srgb, var(--cs-teal) 10%, transparent); @@ -150,27 +156,135 @@ --ifm-breadcrumb-separator-color: var(--cs-ink-mute); } -/* ── Sidebar active link styling ────────────────────────────────────────── */ +/* ── Sidebar ──────────────────────────────────────────────────────────────── */ + +/* Container background */ +.theme-doc-sidebar-container aside { + background: var(--cs-bg-soft) !important; + border-right: none !important; +} + +/* All menu links: base style */ +.menu__link { + color: var(--cs-ink-dim); + font-size: 13.5px; + font-weight: 500; + border-radius: 7px; + padding: 7px 10px; + border-left: 2px solid transparent; + transition: color 0.12s, background 0.12s; +} + +.menu__link:hover { + color: var(--cs-ink); + background: color-mix(in srgb, var(--cs-ink) 5%, transparent); +} + +/* Active non-category link */ .menu__link--active:not(.menu__link--sublist) { - border-left: 2px solid var(--cs-orange); - padding-left: calc(var(--ifm-menu-link-padding-horizontal) - 2px); - background: var(--cs-orange-soft) !important; color: var(--cs-orange) !important; + background: color-mix(in srgb, var(--cs-orange) 14%, transparent) !important; + border-left: 2px solid var(--cs-orange); + padding-left: calc(10px - 2px); + font-weight: 600; } -/* Top-level sidebar category label: eyebrow-style mono uppercase */ +/* Top-level category labels: eyebrow mono */ .menu__list > .menu__list-item > .menu__list-item-collapsible > .menu__link--sublist { font-family: var(--cs-font-mono); - font-size: 10px; + font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--cs-ink-mute); font-weight: 600; + padding: 8px 10px 6px; + border-radius: 0; + border-left: none; + background: transparent !important; +} + +.menu__list > .menu__list-item > .menu__list-item-collapsible > .menu__link--sublist:hover { + background: transparent !important; + color: var(--cs-ink-dim); } -/* Active state for sublist headings */ .menu__list > .menu__list-item > .menu__list-item-collapsible > .menu__link--sublist.menu__link--active { color: var(--cs-orange); + background: transparent !important; +} + +/* ── Sublist (expandable category) links — same look as regular links ──── */ +/* Reset any Infima special treatment on .menu__link--sublist */ +.menu__link--sublist { + font-family: var(--cs-font-sans) !important; + font-size: 13.5px !important; + font-weight: 500 !important; + text-transform: none !important; + letter-spacing: normal !important; + color: var(--cs-ink-dim) !important; +} + +/* Remove Infima's built-in CSS caret for sublist — we use the React chevron */ +.menu__link--sublist-caret:after, +.menu__link--sublist:after { + display: none !important; +} + +/* Nested items: slightly smaller */ +.menu__list .menu__list .menu__link { + font-size: 13px; + padding-left: 14px; +} + +/* ── Breadcrumb: transparent bg, matches page background ──────────────── */ +nav.theme-doc-breadcrumbs { + background: transparent !important; +} + +/* ── Dropdown menu ────────────────────────────────────────────────────────── */ +.dropdown__menu { + background: var(--cs-surface) !important; + border: 1px solid var(--cs-border-hi) !important; + border-radius: 10px !important; + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35) !important; + padding: 6px !important; + min-width: 200px; +} + +/* items inside the dropdown: only class 'dropdown__link', NOT navbar__link */ +.dropdown__link, +.dropdown__menu a, +.dropdown__menu .navbar__link { + color: var(--cs-ink-dim) !important; + font-size: 12px !important; + font-weight: 500 !important; + border-radius: 6px; + padding: 7px 12px !important; + transition: color 0.12s, background 0.12s; +} + +.dropdown__link:hover, +.dropdown__menu a:hover { + color: var(--cs-orange) !important; + background: color-mix(in srgb, var(--cs-orange) 12%, transparent) !important; +} + +.dropdown__link--active, +.dropdown__link--active:hover { + color: var(--cs-orange) !important; + background: color-mix(in srgb, var(--cs-orange) 12%, transparent) !important; +} + +[data-theme='light'] .dropdown__menu { + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12) !important; +} + +/* ── Secondary navbar (breadcrumb bar) ────────────────────────────────────── */ +/* Target via attribute selector to be resilient to CSS module hash changes */ +[class*="secondaryNavbar_"] { + background: var(--cs-bg-soft) !important; + border-bottom: 1px solid var(--cs-border) !important; + box-shadow: none !important; } /* Ensure link color uses the token */ @@ -191,42 +305,40 @@ a { color: var(--cs-orange) !important; } -/* ── Footer ─────────────────────────────────────────────────────────────── */ -.footer { - background: var(--cs-bg) !important; - border-top: 1px solid var(--cs-border); - padding: 40px 0 28px; +/* ── Footer link columns (rendered inside FooterLayout by Docusaurus) ──────── */ +.footer__col { + padding: 0; } .footer__title { - font-family: var(--cs-font-mono); - font-size: 10.5px; - letter-spacing: 0.14em; - text-transform: uppercase; + font-family: var(--cs-font-mono) !important; + font-size: 10.5px !important; + letter-spacing: 0.14em !important; + text-transform: uppercase !important; color: var(--cs-ink-mute) !important; - font-weight: 600; - margin-bottom: 12px; + font-weight: 600 !important; + margin-bottom: 14px !important; +} + +.footer__item { + margin-bottom: 8px; + list-style: none; } .footer__link-item { color: var(--cs-ink-dim) !important; - font-size: 13.5px; - transition: color 0.15s; + font-size: 13.5px !important; + font-weight: 400; + text-decoration: none; + transition: color 0.12s; + line-height: 1.5; } .footer__link-item:hover { color: var(--cs-orange) !important; + text-decoration: none; } .footer__copyright { - color: var(--cs-ink-mute); - font-size: 12px; - font-family: var(--cs-font-mono); - border-top: 1px solid var(--cs-border); - padding-top: 20px; - margin-top: 8px; -} - -[data-theme='light'] .footer { - background: var(--cs-surface-2) !important; + display: none; /* handled by FooterLayout */ } diff --git a/crowdsec-docs/src/css/custom.css b/crowdsec-docs/src/css/custom.css index e0141ea07..2d541567f 100644 --- a/crowdsec-docs/src/css/custom.css +++ b/crowdsec-docs/src/css/custom.css @@ -30,7 +30,7 @@ body, } .theme-doc-sidebar-container { - @apply bg-card; + background: var(--cs-bg-soft) !important; } /* Infima active state overrides are now handled by crowdsec-tokens.css */ diff --git a/crowdsec-docs/src/css/navbar.css b/crowdsec-docs/src/css/navbar.css index 485102225..21b141cb5 100644 --- a/crowdsec-docs/src/css/navbar.css +++ b/crowdsec-docs/src/css/navbar.css @@ -40,14 +40,45 @@ .navbar-separator { display: inline-block; width: 1px; - height: 24px; - background: var(--ifm-color-emphasis-300); - margin: 0 0.75rem; + height: 18px; + background: var(--cs-border-hi); + margin: 0 6px; vertical-align: middle; + align-self: center; } nav.theme-doc-breadcrumbs { - display: none; + font-family: var(--cs-font-mono); + font-size: 12px; + letter-spacing: 0.04em; + color: var(--cs-ink-mute); + margin-bottom: 20px; +} + +.breadcrumbs__item { + font-size: 12px; +} + +.breadcrumbs__link { + color: var(--cs-ink-mute) !important; + background: transparent !important; + padding: 4px 6px; + border-radius: 5px; + transition: color 0.12s; +} + +.breadcrumbs__link:hover { + color: var(--cs-ink) !important; +} + +.breadcrumbs__item--active .breadcrumbs__link { + color: var(--cs-ink) !important; + font-weight: 500; +} + +.breadcrumbs__separator { + color: var(--cs-ink-mute); + opacity: 0.5; } /** Patch some colors **/ @@ -120,17 +151,19 @@ html[data-theme="light"] .navbar-sidebar__item > .menu__list .menu__caret::befor url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 275.05 249.82' %3E%3Cstyle%3E%3C!%5BCDATA%5B.C%7Bletter-spacing:-0.04em%7D%5D%5D%3E%3C/style%3E%3Cpath d='M39.26 178.8a20.79 20.79 0 0 1-7 4.92 24 24 0 0 1-9.76 1.84 23.7 23.7 0 0 1-9.86-1.9 20.78 20.78 0 0 1-7-5 21.06 21.06 0 0 1-4.21-7.07A24 24 0 0 1 0 163.47a24.34 24.34 0 0 1 1.41-8.14 21 21 0 0 1 4.21-7.09 20.78 20.78 0 0 1 7-5 23.7 23.7 0 0 1 9.86-1.9 23.64 23.64 0 0 1 9.76 1.87 20.72 20.72 0 0 1 7 5l-5.1 4.24a14.52 14.52 0 0 0-4.92-3.57 16.37 16.37 0 0 0-6.88-1.35 14.84 14.84 0 0 0-14 8.25 17.57 17.57 0 0 0 0 15.33 14.85 14.85 0 0 0 14 8.24 16.65 16.65 0 0 0 6.79-1.29 14.39 14.39 0 0 0 4.87-3.44l5.09 4.24zm2.45-25.49h6.21v3.13a9.62 9.62 0 0 1 2.33-1.81 13.23 13.23 0 0 1 2.89-1.23 18.62 18.62 0 0 1 3.14-.65 16.42 16.42 0 0 1 3.13-.06v6a15.17 15.17 0 0 0-5.65.37 7.41 7.41 0 0 0-3.51 2.15 8.42 8.42 0 0 0-1.81 3.56 19.24 19.24 0 0 0-.52 4.67v15.06h-6.21zM75 185.19a16.52 16.52 0 0 1-9.15-2.39 15.31 15.31 0 0 1-5.5-6 17.42 17.42 0 0 1-1.84-7.8 16.7 16.7 0 0 1 1.81-7.8 15.59 15.59 0 0 1 5.5-6 16.39 16.39 0 0 1 9.18-2.4 16.42 16.42 0 0 1 9.19 2.4 15.67 15.67 0 0 1 5.5 6 16.7 16.7 0 0 1 1.81 7.8 17.55 17.55 0 0 1-1.84 7.8 15.39 15.39 0 0 1-5.5 6 16.53 16.53 0 0 1-9.16 2.39zm0-27.77a9.23 9.23 0 0 0-5.71 1.72 10.8 10.8 0 0 0-3.41 4.27 14.13 14.13 0 0 0 0 11.12 10.73 10.73 0 0 0 3.41 4.27 10.35 10.35 0 0 0 11.43 0 10.82 10.82 0 0 0 3.41-4.27 14.24 14.24 0 0 0 0-11.12 10.89 10.89 0 0 0-3.41-4.27 9.26 9.26 0 0 0-5.72-1.72zm49 19.48l6.51-23.53h7.31l-10.2 31.21h-7.31l-6.45-24-6.45 24h-7.31l-10.2-31.21h7.31l6.51 23.53 6.52-23.53h7.12zm39.79-35.33h6.27v43h-6.27v-4.43a12.91 12.91 0 0 1-4.7 3.81 15 15 0 0 1-11.61.56 15.41 15.41 0 0 1-9-8.3 19 19 0 0 1 0-14.5 15.38 15.38 0 0 1 9-8.29 15.09 15.09 0 0 1 11.63.55 12.86 12.86 0 0 1 4.71 3.81zm-10.23 15.85a9.82 9.82 0 0 0-4.5 1 9.61 9.61 0 0 0-3.22 2.62 11.21 11.21 0 0 0-1.93 3.71 14.37 14.37 0 0 0 0 8.48 11.36 11.36 0 0 0 1.93 3.72 9.7 9.7 0 0 0 3.22 2.61 10.82 10.82 0 0 0 9 0 9.7 9.7 0 0 0 3.22-2.61 11.36 11.36 0 0 0 1.93-3.72 14.36 14.36 0 0 0 0-8.48 11.21 11.21 0 0 0-1.93-3.71 9.61 9.61 0 0 0-3.22-2.62 9.85 9.85 0 0 0-4.5-1zm37.26-16.09a28.52 28.52 0 0 1 9.22 1.56 33 33 0 0 1 7.68 3.9l-3.44 3.94-.74.86a21.17 21.17 0 0 0-7-3.78 18.59 18.59 0 0 0-6.45-.83 13 13 0 0 0-5.19 1.32 7.92 7.92 0 0 0-3.19 2.7 3.84 3.84 0 0 0-.49 3.26 5.56 5.56 0 0 0 2.88 3 30.3 30.3 0 0 0 6.05 2.49q3.42 1 6.73 2.21a32.23 32.23 0 0 1 6 2.89 10.15 10.15 0 0 1 4 4.51 11.41 11.41 0 0 1 1 3.84 10.55 10.55 0 0 1-.58 4.21 8.33 8.33 0 0 1-1.48 2.67 11.45 11.45 0 0 1-2.27 2.12 17.44 17.44 0 0 1-5.84 2.58 28.66 28.66 0 0 1-6.2.86 33.14 33.14 0 0 1-5.32-.4 24.81 24.81 0 0 1-4.76-1.23 25.47 25.47 0 0 1-4.48-2.15 31.86 31.86 0 0 1-4.49-3.28l3.38-3.87.86-1a24.25 24.25 0 0 0 7.56 4.77 23.32 23.32 0 0 0 8.41 1.32 10.3 10.3 0 0 0 4.64-1.08 8.57 8.57 0 0 0 3.29-2.64 4.07 4.07 0 0 0 .71-3.35c-.27-1.19-1.26-2.29-3-3.32a22.84 22.84 0 0 0-4.27-1.87l-5.16-1.66-5.29-1.81a23.21 23.21 0 0 1-4.67-2.37 11.79 11.79 0 0 1-3.31-3.25 7.6 7.6 0 0 1-1.2-4.52 10.62 10.62 0 0 1 1.5-5.47 13.24 13.24 0 0 1 3.75-3.93 17.11 17.11 0 0 1 5.19-2.39 21.53 21.53 0 0 1 5.84-.8zm35.88 39.13a9.5 9.5 0 0 0 5.13-1.32 9.93 9.93 0 0 0 3.35-3.41l4.54 3.5a14.91 14.91 0 0 1-5.28 4.27 17.06 17.06 0 0 1-7.74 1.63 18 18 0 0 1-6.64-1.14 15.19 15.19 0 0 1-4.94-3.07 14.82 14.82 0 0 1-3.26-4.45 17.51 17.51 0 0 1 0-15.12 14.82 14.82 0 0 1 3.26-4.45 15.36 15.36 0 0 1 4.94-3.07 19.88 19.88 0 0 1 13.27 0 15.3 15.3 0 0 1 4.95 3.07 14.82 14.82 0 0 1 3.26 4.45 17.53 17.53 0 0 1 1.56 9.9h-26.41a13.65 13.65 0 0 0 1.07 3.5 10.56 10.56 0 0 0 2 2.95 9 9 0 0 0 3 2 10.16 10.16 0 0 0 4 .74zm10.14-13.88a14 14 0 0 0-1.08-3.51 10.51 10.51 0 0 0-2-2.94 8.89 8.89 0 0 0-3-2 11 11 0 0 0-7.92 0 8.89 8.89 0 0 0-3 2 10.51 10.51 0 0 0-2 2.94 13.6 13.6 0 0 0-1.07 3.51h20.15zm32.93 16.89a17.1 17.1 0 0 1-7.87 1.66 16.44 16.44 0 0 1-9.15-2.4 15.22 15.22 0 0 1-5.5-6 17.42 17.42 0 0 1-1.84-7.8 16.67 16.67 0 0 1 1.81-7.8 15.52 15.52 0 0 1 5.5-6 16.39 16.39 0 0 1 9.18-2.4 17.1 17.1 0 0 1 7.87 1.66 15 15 0 0 1 5.28 4.36l-4.55 3.57a10.1 10.1 0 0 0-3.37-3.54 9.51 9.51 0 0 0-5.23-1.38 9.16 9.16 0 0 0-5.71 1.72 10.8 10.8 0 0 0-3.41 4.27 14.13 14.13 0 0 0 0 11.12 10.8 10.8 0 0 0 3.41 4.27 9.16 9.16 0 0 0 5.71 1.72 9.51 9.51 0 0 0 5.23-1.38 10.15 10.15 0 0 0 3.37-3.53l4.55 3.56a14.91 14.91 0 0 1-5.28 4.32z' fill='black'/%3E%3Cpath d='M110 117.18l-.72 6.65h.06l.66-6.65z' fill='black' /%3E%3Ctext class='C' transform='translate(83.51 237.44)' fill='black' font-size='61' font-family='BellinzoRegularRegular,Bellinzo Regular'%3EHub%3C/text%3E%3Cg fill='black' %3E%3Cpath d='M102.63 63.66a20.63 20.63 0 0 1 1.69 3.48h0a27.11 27.11 0 0 0-2.14-5.92c-1.56-3-3.86-2.86-5.3 0a29.14 29.14 0 0 0-2.14 6 23.85 23.85 0 0 1 1.68-3.55c1.69-2.87 4.39-2.99 6.21-.01zm73.63 0a21.42 21.42 0 0 1 1.74 3.48h0a26.6 26.6 0 0 0-2.14-5.92c-1.55-3-3.85-2.86-5.29 0a29.14 29.14 0 0 0-2.14 6 23.85 23.85 0 0 1 1.68-3.55c1.63-2.87 4.33-2.99 6.15-.01zM140 46.06a27.85 27.85 0 0 1 2.37 5.32 34.92 34.92 0 0 0-2.77-7.78c-1.94-3.72-4.81-3.57-6.61 0a37.88 37.88 0 0 0-2.77 7.9 30.88 30.88 0 0 1 2.36-5.44c1.99-3.57 5.22-3.72 7.42 0zM95 67.82a5.51 5.51 0 0 0 3.59 4.59l.13 4 .46 4.78c.19 1.13.48 1.08.66 0 .25-1.52.32-3.15.46-4.78a39 39 0 0 0 .12-4 5.5 5.5 0 0 0 3.6-4.59 9.08 9.08 0 0 1-9 0z'/%3E%3Cpath d='M168.63 67.82a5.5 5.5 0 0 0 3.6 4.59 39 39 0 0 0 .12 4l.46 4.78c.2 1.13.48 1.08.66 0 .25-1.52.32-3.15.46-4.78l.13-4a5.51 5.51 0 0 0 3.59-4.59 9.08 9.08 0 0 1-9 0zM188 75.94c1 4.27 2.23 13.48 3.5 24.14C190.43 90 189.28 81 188 75.94zm-46.18-24.07a11.36 11.36 0 0 1-11.25 0c.52 2.88 2.27 5.13 4.49 5.71l.15 5.05.58 6c.24 1.41.59 1.35.82 0 .31-1.9.4-3.93.57-6 .14-1.66.18-3.35.16-5.05 2.21-.58 3.97-2.83 4.48-5.71zM98.54 72.41l.13 4 .46 4.78c.19 1.13.48 1.08.66 0 .25-1.52.32-3.15.46-4.78a39 39 0 0 0 .12-4 5.5 5.5 0 0 0 3.6-4.59 9.08 9.08 0 0 1-9 0 5.51 5.51 0 0 0 3.57 4.59zm60.6 19.68c-1.59-13.3-3.15-24.78-4.37-30.1a7.1 7.1 0 0 0 6.87-5.84h0a8.19 8.19 0 0 0 .12-1.11h.2a4.47 4.47 0 0 0 2.74-8.18 7.1 7.1 0 0 0-1.8-13.51 4.47 4.47 0 0 0-3.43-7.22 7.17 7.17 0 0 0 2.87-2.52c1.67-2.61 1.94-14.4-4-22-2.44-3.12-4.83-1.24-5 .89-.47 6.52-.52 11.42-2.22 17a4.46 4.46 0 0 0-7.92-2.05 7.1 7.1 0 0 0-14 0 4.47 4.47 0 0 0-7.94 2c-1.69-5.59-1.75-10.49-2.22-17-.15-2.13-2.54-4-5-.89-5.93 7.58-5.66 19.37-4 22a7.17 7.17 0 0 0 2.87 2.52 4.48 4.48 0 0 0-3.43 7.22 7.1 7.1 0 0 0-1.79 13.51 4.47 4.47 0 0 0 2.73 8.18h.21a6.36 6.36 0 0 0 .11 1.07h0a7.1 7.1 0 0 0 7 5.89h0c-2 8.51-5 32.87-7.43 55.17l-.66 6.65a842.28 842.28 0 0 1 26.64-.42c9 0 17.83.14 26.22.41l-.67-6.27-2.7-25.4zm-14.58-40a5.88 5.88 0 0 1-2.39-1.17c.7 2.78 1.21 5.61 1.93 8.44a45.15 45.15 0 0 1 1.22 7.43c.31 3.86-2.22 7.19-5.39 7.19h-7.47c-3.17 0-5.7-3.33-5.39-7.19a46.23 46.23 0 0 1 1.22-7.43c.66-2.61 1.17-5.23 1.81-7.8a8.31 8.31 0 0 1-3.84 1 44.4 44.4 0 0 1-6.91-.45 4.78 4.78 0 0 1-4-3.37c-.59-1.69-1-3.45-1.59-5.14a14.16 14.16 0 0 0-1.21-2.09 2.41 2.41 0 0 1-.42-.89v-3.86a38.54 38.54 0 0 1 12.17-1.54 27 27 0 0 1 8.8 1.61 10.09 10.09 0 0 0 7.18 0 30 30 0 0 1 13.6-1.44c2.45.22 4.87.76 7.35 1.16v4.08c0 .29-.13.74-.33.82-1 .45-1.11 1.36-1.35 2.25a84.99 84.99 0 0 1-1.41 4.71 5.44 5.44 0 0 1-4.38 3.79 20.06 20.06 0 0 1-9.2-.1zm-29.92 15.64a5.53 5.53 0 0 1-1.13.36 16.1 16.1 0 0 1-7.34-.08 4.61 4.61 0 0 1-1.92-.94l1.55 6.77a36.86 36.86 0 0 1 1 6c.24 3.1-1.78 5.77-4.33 5.77h-6c-2.55 0-4.58-2.67-4.33-5.77a36.86 36.86 0 0 1 1-6l1.45-6.26a6.48 6.48 0 0 1-3.08.79A34.29 34.29 0 0 1 86 68a3.85 3.85 0 0 1-3.22-2.71l-1.28-4.12a10.81 10.81 0 0 0-1-1.67 1.73 1.73 0 0 1-.33-.72v-3.09a30.28 30.28 0 0 1 9.75-1.23 21.25 21.25 0 0 1 7.06 1.29 8.11 8.11 0 0 0 5.76 0 19.87 19.87 0 0 1 2.74-.81 6.47 6.47 0 0 1-.79-7.68 9.11 9.11 0 0 1-2.18-11 5.59 5.59 0 0 0-3-.86 5.7 5.7 0 0 0-5.64 4.88 3.58 3.58 0 0 0-6.37 1.62c-1.35-4.48-1.4-8.41-1.77-13.64-.12-1.7-2-3.21-4-.71-4.75 6.08-4.54 15.53-3.2 17.63a5.86 5.86 0 0 0 2.3 2A3.58 3.58 0 0 0 78 53a5.69 5.69 0 0 0-1.4 10.82 3.59 3.59 0 0 0 2.19 6.56h.17a6 6 0 0 0 .09.86h0A5.69 5.69 0 0 0 84.66 76h0c-1.58 6.82-4 26.36-6 44.24l-.53 5.38c9-.74 18.89-1.31 29.48-1.68l.69-6.94 2.87-24.67 3.47-24.6z'/%3E%3Cpath d='M199.39 58.62a5.7 5.7 0 0 0-4.83-5.62 3.58 3.58 0 0 0-2.75-5.79 5.86 5.86 0 0 0 2.3-2c1.34-2.1 1.55-11.55-3.2-17.63-2-2.5-3.87-1-4 .71-.37 5.23-.41 9.16-1.77 13.64a3.59 3.59 0 0 0-6.36-1.64 5.69 5.69 0 0 0-8.78-3.92 9.1 9.1 0 0 1-2.23 10.9 6.44 6.44 0 0 1-.66 7.54 21.08 21.08 0 0 1 3.54 1 8.11 8.11 0 0 0 5.76 0 24.1 24.1 0 0 1 10.91-1.15c2 .17 3.91.6 5.9.93v3.27c0 .23-.11.59-.26.66-.82.36-.9 1.09-1.09 1.8a63 63 0 0 1-1.13 3.78 4.35 4.35 0 0 1-3.51 3 16.1 16.1 0 0 1-7.34-.08 4.67 4.67 0 0 1-1.92-.94c.56 2.23 1 4.5 1.55 6.77a36.86 36.86 0 0 1 1 6c.25 3.1-1.78 5.77-4.33 5.77h-6c-2.55 0-4.57-2.67-4.33-5.77a36.86 36.86 0 0 1 1-6c.53-2.09.94-4.19 1.45-6.26a6.48 6.48 0 0 1-3.08.79 34.29 34.29 0 0 1-5.54-.35 3.78 3.78 0 0 1-1.84-.74c1.52 9.16 3.53 25.4 6.18 50.05l.29 2.62q.19 1.9.42 3.93c10.62.37 20.57.94 29.58 1.68l-.55-5.07-2.17-20.41c-1.33-10.67-2.6-19.88-3.6-24.15a5.7 5.7 0 0 0 5.51-4.68h0a6.69 6.69 0 0 0 .09-.9h.17a3.59 3.59 0 0 0 2.23-6.54 5.69 5.69 0 0 0 3.39-5.2zm-36.87 65.19h.15q-.33-3.09-.67-6.27l-2.86-25.45 2.71 25.45.67 6.27z'/%3E%3Cpath d='M154.77 62c1.22 5.32 2.78 16.8 4.37 30.1-1.39-12.56-2.82-23.86-4.37-30.1zm-44.45 55.17l-.72 6.65h.06l.66-6.65z'/%3E%3C/g%3E%3C/svg%3E"); } -/* On wide screens (desktop), remove text content for icon-only display */ +/* Desktop: icon-only — centered in button */ @media (min-width: 1401px) { .header-roadmap-link:before, .header-discord-link:before, .header-github-link:before, .header-discourse-link:before { - content: ""; - padding-left: 0.7rem; - padding-right: 0.8rem; - background-position: center; - background-size: 80%; + content: "" !important; + padding: 0 !important; + width: 18px; + height: 18px; + display: inline-block; + background-position: center !important; + background-size: 100% !important; } } @@ -144,19 +177,41 @@ html[data-theme="light"] .navbar-sidebar__item > .menu__list .menu__caret::befor } .navbar__items--right .navbar__link:hover { - @apply opacity-60; + opacity: 0.7; } .navbar { - @apply whitespace-nowrap; + white-space: nowrap; +} + +/* Right icon buttons: centered, equal padding */ +.navbar__items--right .navbar__link { + padding: 5px 7px !important; + display: inline-flex !important; + align-items: center; + justify-content: center; + height: 32px; } .navbar__items--right .navbar__link:before { - @apply px-3 h-6; + /* Below 1401px: icon + text label */ + padding-right: 0 !important; + height: 18px; + display: inline-flex; + align-items: center; + background-size: contain !important; } -.navbar__items--right .navbar__link { - @apply pl-4 pr-0; +/* Ensure left nav items are vertically centered */ +.navbar__items--left .navbar__link, +.navbar__items--left .navbar__item { + display: inline-flex; + align-items: center; +} + +/* Tighten gap around separator */ +.navbar-separator { + margin: 0 6px; } @media (max-width: 1400px) { diff --git a/crowdsec-docs/src/pages/index.tsx b/crowdsec-docs/src/pages/index.tsx index 7adcde781..7ce87ef2a 100644 --- a/crowdsec-docs/src/pages/index.tsx +++ b/crowdsec-docs/src/pages/index.tsx @@ -17,39 +17,39 @@ const CS_BLUE = 'var(--cs-blue)'; /* ── Small inline icons for the RunningStrip */ function IconCompass() { - return ; + return ; } function IconShield() { - return ; + return ; } function IconGauge() { - return ; + return ; } function IconPulse() { - return ; + return ; } function IconFeed() { - return ; + return ; } function IconGlobe() { return ; } const securityEngineSteps = [ - { title: 'Install the Security Engine', desc: 'Runs on your server, detects attack patterns in real time. Immediately protected with the CrowdSec Community Blocklist.' }, - { title: 'Activate the WAF module', desc: 'Layer in the AppSec component to inspect HTTP traffic and block web exploits before they reach your app.' }, - { title: 'Subscribe to blocklists', desc: 'Add a selection of extra blocklists on top of the built-in detection & community blocklist.' }, - { title: 'Craft your own rules', desc: 'Write custom scenarios for your stack, then share them back with the community on the Hub.' }, + { title: 'Install the Security Engine', desc: 'Runs on your server, detects attack patterns in real time. Immediately protected with the Community Blocklist.' }, + { title: 'Activate the WAF module', hint: 'RECOMMENDED' as const, desc: 'Layer in the AppSec component to inspect HTTP traffic and block web exploits.' }, + { title: 'Subscribe to blocklists', hint: 'OPTIONAL' as const, desc: 'Add extra curated feeds on top of the built-in detection & community blocklist.' }, + { title: 'Craft your own rules', hint: 'OPTIONAL' as const, desc: 'Write custom scenarios for your stack, then share them on the Hub.' }, ]; const blocklistSteps = [ - { title: 'Create a blocklist integration endpoint', desc: 'Generates a dedicated URL and credentials to serve blocklists to your perimeter devices.' }, - { title: 'Choose which blocklists to serve', desc: 'Select from curated feeds by threat category: scanners, bots, TOR exits, exploits, and more.' }, - { title: 'Plug it in as an external threat feed', desc: 'Point your firewall, CDN, or WAF at the endpoint. Use the feed to protect your infrastructure.' }, + { title: 'Create an integration endpoint', desc: 'Generates a dedicated URL and credentials to serve blocklists to your perimeter devices.' }, + { title: 'Choose blocklists to serve', desc: 'Select from curated feeds: scanners, bots, TOR exits, exploits, and more.' }, + { title: 'Plug in as a threat feed', desc: 'Point your firewall, CDN, or WAF at the endpoint. No agent to install.' }, ]; const ctiSteps = [ - { title: 'Look up any IP in the Console', desc: 'Search instantly from our Web UI. Get reputation score, behaviors, attack history, and CVE links.' }, - { title: 'Generate a CTI API key', desc: 'Unlock programmatic access to 30+ data points on IPs detected by CrowdSec Network.' }, - { title: 'Connect to your SIEM/SOAR/TIP', desc: 'Native integrations for Splunk, Sentinel, QRadar, TheHive, OpenCTI, MISP, and more.' }, + { title: 'Look up any IP in the Console', desc: 'Get reputation score, behaviors, attack history, and CVE links instantly.' }, + { title: 'Generate a CTI API key', hint: 'OPTIONAL' as const, desc: 'Unlock programmatic access to 30+ data points per IP detected by the CrowdSec network.' }, + { title: 'Connect to your SIEM/SOAR', hint: 'OPTIONAL' as const, desc: 'Native integrations for Splunk, Sentinel, QRadar, TheHive, MISP, and more.' }, ]; const alreadyRunningLinks = [ @@ -153,7 +153,7 @@ export default function HomePage() {
-
+
{/* ── "I want to…" eyebrow ── */}
{label} {isPremium && ( - - - Premium - - + Premium )} diff --git a/crowdsec-docs/src/theme/DocSidebarItem/Link/index.tsx b/crowdsec-docs/src/theme/DocSidebarItem/Link/index.tsx index 34dc5f5b2..db6ab0825 100644 --- a/crowdsec-docs/src/theme/DocSidebarItem/Link/index.tsx +++ b/crowdsec-docs/src/theme/DocSidebarItem/Link/index.tsx @@ -2,12 +2,25 @@ import isInternalUrl from "@docusaurus/isInternalUrl"; import Link from "@docusaurus/Link"; import { isActiveSidebarItem } from "@docusaurus/plugin-content-docs/client"; import { ThemeClassNames } from "@docusaurus/theme-common"; -import { Badge } from "@site/src/ui/badge"; import type { Props } from "@theme/DocSidebarItem/Link"; import clsx from "clsx"; import { ExternalLinkIcon, Signpost } from "lucide-react"; import React from "react"; +const premiumBadge: React.CSSProperties = { + fontFamily: 'var(--cs-font-mono)', + fontSize: 9.5, + letterSpacing: '0.08em', + textTransform: 'uppercase', + padding: '2px 6px', + borderRadius: 4, + background: 'color-mix(in srgb, var(--cs-orange) 14%, transparent)', + color: 'var(--cs-orange)', + fontWeight: 600, + flexShrink: 0, + marginLeft: 'auto', +}; + export default function DocSidebarItemLink({ item, onItemClick, activePath, level, index, ...props }: Readonly): React.JSX.Element { const { href, label, className, autoAddBaseUrl, customProps } = item; const isActive = isActiveSidebarItem(item, activePath); @@ -31,7 +44,7 @@ export default function DocSidebarItemLink({ item, onItemClick, activePath, leve { "menu__link--active": isActive, }, - tag === "premium" ? "flex items-center justify-between" : "flex items-center" + "flex items-center" )} autoAddBaseUrl={autoAddBaseUrl} aria-current={isActive ? "page" : undefined} @@ -43,9 +56,7 @@ export default function DocSidebarItemLink({ item, onItemClick, activePath, leve > {label} {tag === "premium" && ( - - Premium - + Premium )} {tag === "otherSection" && } {!isInternalLink && } diff --git a/crowdsec-docs/src/theme/Footer/Layout/index.tsx b/crowdsec-docs/src/theme/Footer/Layout/index.tsx index e24fa9391..4f771221d 100644 --- a/crowdsec-docs/src/theme/Footer/Layout/index.tsx +++ b/crowdsec-docs/src/theme/Footer/Layout/index.tsx @@ -3,23 +3,142 @@ import React from "react"; export default function FooterLayout({ links, logo, copyright }: Readonly): React.JSX.Element { return ( -