diff --git a/.gitignore b/.gitignore
index 0242b2f..69ad8a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,4 @@ cypress/videos
.idea
.claude
+.serena
\ No newline at end of file
diff --git a/components/shared/scroll-icons.tsx b/components/shared/scroll-icons.tsx
index 533528b..d910b66 100644
--- a/components/shared/scroll-icons.tsx
+++ b/components/shared/scroll-icons.tsx
@@ -49,35 +49,45 @@ function ScrollIcons() {
Trusted by teams and individuals from
- {logos.map((item, index) => (
-
-

{
+ // Extract logo name from path (e.g., '/images/google.svg' -> 'google')
+ const logoName = item.logo.replace('/images/', '').replace('.svg', '');
+
+ return (
+
-
- ))}
+ className={'logo opacity-100'}
+ >
+
+
+ );
+ })}
diff --git a/dev.env b/dev.env
new file mode 100644
index 0000000..f0df815
--- /dev/null
+++ b/dev.env
@@ -0,0 +1,4 @@
+ENVIRONMENT=test
+NEXT_PUBLIC_API_KEY=REQUIRED
+NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
+NEXT_PUBLIC_SITE_BASE_URL=http://localhost:3000
diff --git a/next.config.mjs b/next.config.mjs
index bc82ec7..48c1501 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -121,6 +121,16 @@ const nextConfig = {
source: '/(.*)',
headers: securityHeaders,
},
+ {
+ // Cache SVG sprite sheet for 1 year (immutable)
+ source: '/images/company-logos-sprite.svg',
+ headers: [
+ {
+ key: 'Cache-Control',
+ value: 'public, max-age=31536000, immutable',
+ },
+ ],
+ },
{
source: '/:all*(docx)',
headers: [
diff --git a/package.json b/package.json
index 7d45e26..d6009e1 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,8 @@
"format": "prettier \"**/*.{css,js,json,jsx,ts,tsx}\"",
"test": "NODE_ENV=test cypress open",
"test:headless": "NODE_ENV=test cypress run --browser chrome",
- "generate-sitemap": "node scripts/generate-sitemap.js"
+ "generate-sitemap": "node scripts/generate-sitemap.js",
+ "generate-sprite": "node scripts/generate-sprite.js"
},
"devDependencies": {
"@types/lodash-es": "^4.17.8",
diff --git a/public/images/company-logos-sprite.svg b/public/images/company-logos-sprite.svg
new file mode 100644
index 0000000..3b76f09
--- /dev/null
+++ b/public/images/company-logos-sprite.svg
@@ -0,0 +1,709 @@
+
\ No newline at end of file
diff --git a/scripts/generate-sprite.js b/scripts/generate-sprite.js
new file mode 100644
index 0000000..8c70d01
--- /dev/null
+++ b/scripts/generate-sprite.js
@@ -0,0 +1,97 @@
+const fs = require('fs');
+const path = require('path');
+
+const imagesDir = path.join(__dirname, '../public/images');
+const outputFile = path.join(__dirname, '../public/images/company-logos-sprite.svg');
+const pagesConfigPath = path.join(__dirname, '../lib/config/pages.tsx');
+
+const extractLogoFilesFromConfig = () => {
+ if (!fs.existsSync(pagesConfigPath)) {
+ console.warn(`Warning: pages config not found at ${pagesConfigPath}`);
+ return [];
+ }
+
+ const configContent = fs.readFileSync(pagesConfigPath, 'utf8');
+ const developersMatch = configContent.match(/developers:\s*{[\s\S]*?logos:\s*\[([\s\S]*?)\]\s*,\s*}/);
+
+ if (!developersMatch) {
+ console.warn('Warning: Unable to locate developers logos in config; falling back to empty list.');
+ return [];
+ }
+
+ const logosSection = developersMatch[1];
+ const matches = logosSection.matchAll(/logo:\s*['"]([^'"]+)['"]/g);
+
+ const seen = new Set();
+ const logos = [];
+
+ for (const match of matches) {
+ const logoPath = match[1];
+ if (!logoPath.startsWith('/images/')) {
+ continue;
+ }
+
+ const fileName = path.basename(logoPath);
+
+ if (!fileName.toLowerCase().endsWith('.svg') || seen.has(fileName)) {
+ continue;
+ }
+
+ seen.add(fileName);
+ logos.push(fileName);
+ }
+
+ return logos;
+};
+
+// Derive the logo list from the single source of truth in pages config
+const logoFiles = extractLogoFilesFromConfig();
+
+if (!logoFiles.length) {
+ console.warn('No logos discovered from config; sprite generation will not output any symbols.');
+}
+
+console.log('Generating SVG sprite sheet...');
+
+let symbols = [];
+
+logoFiles.forEach((file) => {
+ const filePath = path.join(imagesDir, file);
+
+ if (!fs.existsSync(filePath)) {
+ console.warn(`Warning: ${file} not found, skipping...`);
+ return;
+ }
+
+ const svgContent = fs.readFileSync(filePath, 'utf8');
+
+ // Extract viewBox and paths from the SVG
+ const viewBoxMatch = svgContent.match(/viewBox="([^"]*)"/);
+ const viewBox = viewBoxMatch ? viewBoxMatch[1] : '0 0 100 100';
+
+ // Extract everything between tags
+ const contentMatch = svgContent.match(/