Changelog - 2025-12-25 (#1)
Dark Matter Site: Portfolio Page v0.0.1 with NocoDB Integration
Overview
Refactored the public portfolio page to dynamically fetch portfolio companies from NocoDB while maintaining a static JSON fallback. Implemented sophisticated logo handling that supports light mode, dark mode, and vibrant mode with automatic switching—plus special CSS filter treatment for transparent SVG logos.
Key deliverables:
- Dynamic Data Fetching: Portfolio page now pulls from NocoDB at build time
- Graceful Fallback: Falls back to static JSON when NocoDB not configured
- Multi-Mode Logo Switching: Separate logos for light, dark, and vibrant themes
- Transparent Logo Treatment: CSS filters colorize monochrome transparent SVGs
- 19 Portfolio Company Logos: Added trademark assets for portfolio companies
Architecture
┌─────────────────────────────────────────────────────────────────────────────┐
│ Portfolio Page Data Flow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Build Time │ │
│ ├─────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ isNocoDBConfigured()? │ │
│ │ │ │ │
│ │ ├──── YES ──▶ getPortfolioCompanies() ──▶ NocoDB API │ │
│ │ │ │ │ │
│ │ │ ▼ │ │
│ │ │ transformToPortfolio() │ │
│ │ │ │ │ │
│ │ └──── NO ───▶ import staticPortfolioData ──────┤ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ portfolioData[] │ │
│ │ │ │ │
│ └────────────────────────────────────────────────────────┼─────────────┘ │
│ │ │
│ ┌────────────────────────────────────────────────────────┼─────────────┐ │
│ │ Render Time │ │ │
│ ├────────────────────────────────────────────────────────┼─────────────┤ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌────────────────┐ │ │
│ │ │ directInvestments│ │ lpCommitments │ │ Company Card │ │ │
│ │ │ .filter() │ │ .filter() │───▶│ with Logos │ │ │
│ │ └─────────────────┘ └─────────────────┘ └────────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Changes by Area
1. Dynamic Data Fetching (src/pages/portfolio/index.astro)
Replaced static import with conditional NocoDB fetch:
Before:
---
import portfolioData from '@content/portfolio/portfolio-companies.json';
---
After:
---
import { getPortfolioCompanies, isNocoDBConfigured } from '@lib/nocodb';
import staticPortfolioData from '@content/portfolio/portfolio-companies.json';
export const prerender = true;
let portfolioData;
if (isNocoDBConfigured()) {
console.log('[portfolio] Fetching from NocoDB...');
portfolioData = await getPortfolioCompanies();
console.log(`[portfolio] Loaded ${portfolioData.length} companies from NocoDB`);
} else {
console.log('[portfolio] NocoDB not configured, using static data');
portfolioData = staticPortfolioData;
}
---
Benefits:
- Single source of truth in NocoDB for portfolio data
- Non-technical team members can update via NocoDB UI
- Static fallback ensures builds work without API access
- Console logging aids debugging
2. Multi-Mode Logo System
Implemented logo switching across three theme modes using CSS classes:
HTML Structure:
<!-- Logo for dark/vibrant modes -->
<img
src={company.logoDarkMode || company.logoLightMode}
alt={company.conventionalName}
class:list={[
"h-10 w-auto object-contain logo-for-dark",
company.logoIsTransparent && "logo-transparent"
]}
loading="lazy"
/>
<!-- Logo for light mode -->
<img
src={company.logoLightMode}
alt={company.conventionalName}
class:list={[
"h-10 w-auto object-contain logo-for-light",
company.logoIsTransparent && "logo-transparent"
]}
loading="lazy"
/>
CSS Mode Switching:
/* Light mode: show light-mode logos, hide dark-mode logos */
:global([data-mode="light"]) .logo-for-dark {
display: none !important;
}
:global([data-mode="light"]) .logo-for-light {
display: block !important;
}
/* Dark mode: show dark-mode logos, hide light-mode logos */
:global([data-mode="dark"]) .logo-for-light {
display: none !important;
}
:global([data-mode="dark"]) .logo-for-dark {
display: block !important;
}
/* Vibrant mode: treat same as dark mode */
:global([data-mode="vibrant"]) .logo-for-light {
display: none !important;
}
:global([data-mode="vibrant"]) .logo-for-dark {
display: block !important;
}
3. Transparent SVG Color Treatment
For logos that are transparent (monochrome with no background), apply CSS filters to match the theme:
/* Transparent logo color treatment */
.logo-transparent {
transition: filter 0.2s ease;
}
/* Light mode: render as black */
:global([data-mode="light"]) .logo-transparent {
filter: brightness(0);
}
/* Dark mode: render as white */
:global([data-mode="dark"]) .logo-transparent {
filter: brightness(0) invert(1);
}
/* Vibrant mode: render as white */
:global([data-mode="vibrant"]) .logo-transparent {
filter: brightness(0) invert(1);
}
How it works:
┌──────────────────────────────────────────────────────────────────────────┐
│ CSS Filter Pipeline for Transparent Logos │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ Original SVG brightness(0) invert(1) │
│ (any color) (force black) (flip to white) │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 🔵🟢🔴 │ ─────────▶ │ ⬛⬛⬛ │ ────────▶ │ ⬜⬜⬜ │ │
│ │ Logo │ Step 1 │ Black │ Step 2 │ White │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Light Mode: brightness(0) only → Black logo │
│ Dark Mode: brightness(0) + invert(1) → White logo │
│ │
└──────────────────────────────────────────────────────────────────────────┘
4. Portfolio Logo Assets
Added 19 trademark/logo files for portfolio companies:
| Company | Variants | Format |
|---|---|---|
| AmberCycle | Dark-Mode, Transparent | SVG |
| Diadem Biotheraputics | Dark-Mode, Light-Mode | WebP |
| Encellin | Dark-Mode | PNG |
| ExoLux | Dark-Mode, Light-Mode, Transparent | SVG |
| ImYoo | Brand | AVIF |
| Inaru | Dark-Mode, Light-Mode | WebP, PNG |
| JungleTea | Transparent | SVG |
| MiniCircle | Transparent | SVG |
| Mudita | Transparent | SVG |
| Napigen | Brand | WebP |
| ParallelBio | Brand | SVG |
| Petri | Dark-Mode, Full | WebP, PNG |
| Trilobio | Dark-Mode, Light-Mode | SVG |
| Venice Beach Wine Club | Brand | WebP |
Naming Convention:
trademark__{CompanyName}--{Variant}.{ext}
Variants:
- Dark-Mode : Designed for dark backgrounds
- Light-Mode : Designed for light backgrounds
- Transparent : Monochrome, needs CSS color treatment
- Brand : Original brand colors (any background)
5. Company Category Separation
The page separates companies into two sections based on investment type:
// In NocoDB transformation (src/lib/nocodb.ts)
const entityType = fields['Entity-Type'] || null;
let category: 'direct' | 'lp' = 'direct';
if (entityType === 'LP') {
category = 'lp';
}
<!-- In portfolio page -->
const directInvestments = portfolioData.filter(c => c.category === 'direct');
const lpCommitments = portfolioData.filter(c => c.category === 'lp');
Page Structure:
Portfolio
├── Direct Investments (C-Corp, LLC entities)
│ └── Grid of company cards
├── LP Commitments (LP/Fund entities)
│ └── Grid of company cards
└── Confidential Access CTA
└── Link to /portfolio-gate
Files Changed Summary
New
Logo Assets (public/portfolio/logos/):
trademark__AmberCycle--Dark-Mode.svgtrademark__AmberCycle--Transparent.svgtrademark__Diadem-Biotheraputics--Dark-Mode.webptrademark__Diadem-Biotheraputics--Light-Mode.webptrademark__Encellin--Dark-Mode.pngtrademark__ExoLux--Dark-Mode.svgtrademark__ExoLux--Light-Mode.svgtrademark__ExoLux--Transparent.svgtrademark__ImYoo--Brand.aviftrademark__Inaru--Dark-Mode.webptrademark__Inaru--Light-Mode.pngtrademark__JungleTea--Transparent.svgtrademark__MiniCircle--Transparent.svgtrademark__Mudita--Transparent.svgtrademark__Napigen--Brand.webptrademark__ParallelBio--Brand.svgtrademark__Petri--Dark-Mode.webptrademark__Petri.pngtrademark__Trilobio--Dark-Mode.svgtrademark__Trilobio--Light-Mode.svgtrademark__Venice-Beach-Wine-Club--Brand.webp
Modified
src/pages/portfolio/index.astro— Added NocoDB integration, multi-mode logo CSS
Technical Details
Prerender Compatibility
The page uses export const prerender = true for static generation. NocoDB fetch happens at build time:
// This runs during build, not on each request
if (isNocoDBConfigured()) {
portfolioData = await getPortfolioCompanies();
}
Logo Fallback Chain
logoDarkMode ─┬─ exists? ──▶ Use dark mode logo
│
└─ missing? ──▶ Fall back to logoLightMode ─┬─ exists? ──▶ Use light logo
│
└─ missing? ──▶ placeholder-dark.svg
Transparent Detection
The NocoDB transformation detects transparent logos by filename convention:
const logoIsTransparent = Boolean(
trademarks.lightMode?.includes('--Transparent') ||
trademarks.darkMode?.includes('--Transparent')
);
This flag triggers the .logo-transparent class application.
Theme Mode Detection
The site uses data-mode attribute on a parent element:
<html data-mode="dark">
<!-- or "light" or "vibrant" -->
</html>
CSS selectors use :global() to escape Astro's scoped styles:
:global([data-mode="dark"]) .logo-for-light {
display: none !important;
}
Visual Reference
Company Card Layout
┌─────────────────────────────────────────┐
│ │
│ ┌─────────────────────────────────┐ │
│ │ │ │
│ │ [Company Logo] │ │
│ │ h-10 w-auto │ │
│ │ │ │
│ └─────────────────────────────────┘ │
│ │
│ Company Name │
│ text-sm font-medium │
│ │
│ Short description of what the │
│ company does, limited to 2 lines... │
│ text-xs text-foreground/50 │
│ │
└─────────────────────────────────────────┘
↑
└── Hover: border-primary/50, lift effect
Grid Layout
Desktop (lg): 4 columns
┌─────┬─────┬─────┬─────┐
│ │ │ │ │
│ │ │ │ │
└─────┴─────┴─────┴─────┘
Tablet (md): 3 columns
┌─────┬─────┬─────┐
│ │ │ │
└─────┴─────┴─────┘
Mobile: 2 columns
┌─────┬─────┐
│ │ │
└─────┴─────┘
Notes / Follow-Ups
-
Missing Logos: Some companies may still use placeholder logos. Run NocoDB query to identify companies without
trademarksSlugspopulated. -
Logo Optimization: Consider adding image optimization pipeline for WebP/AVIF conversion of PNG logos.
-
Vibrant Mode Logos: The
logoVibrantModefield is supported in the data model but not yet used in the UI. Could show brand-colored logos in vibrant mode. -
Logo Size Consistency: Some logos may appear larger/smaller due to aspect ratios. Consider adding width constraints or aspect-ratio containers.
-
Lazy Loading: All logos use
loading="lazy"for performance. Consider intersection observer for more control. -
Access at:
/portfolio