Configuration and branding
Portfoliable is designed so most day-to-day customization happens in a few config files instead of scattered CSS edits. The main runtime config is the design config file generated in your project.
Where configuration lives
In a generated project, the core config files are usually:
configs/portfoliable.design.config.js— app shell behavior, home view, visibility, theme overrides, header behaviorconfigs/i18n/i18n.config.js— available locales, labels, and locale metadataconfigs/i18n/i18n.labels.js— translated UI labels used by the appsrc/content/about/ABOUTME.md— About page metadata and actionssrc/content/cases/**/case.md— per-case metadata, summary, and article content
You do not need to edit all of them for every change. Most common work happens in the design config and the case files.
What you can customize
Home view
The home view controls the landing experience of your portfolio.
Typical settings include:
itemCount— how many cards appear in the initial galleryengine— rendering engine modeshowBreadcrumb— breadcrumb visibility in the shellshowLanguageMenu— whether language selection is visiblegalleryoverrides — home-specific spacing and height tweaks
Example:
export default {
homeView: {
itemCount: 8,
engine: 'minimal',
showBreadcrumb: false,
showLanguageMenu: true,
gallery: {
'--ds-gallery-height': '52vh'
}
}
};Visibility and indexing
Portfoliable supports visibility controls for web navigation, crawlers, and AI indexing. These are important for public-facing portfolios and for controlling what gets indexed.
export default {
visibility: {
web: true,
crawlers: true,
ai: true,
locales: {}
}
};Use these settings to decide whether a case or About page should appear in app navigation, robots metadata, and AI/crawler metadata flows.
Protection
The protection config defines how protected cases are unlocked.
export default {
protection: {
unlockEndpoint: '/api/unlock-case.php'
}
};This is the default endpoint pattern used by secure unlock flows. Keep it in your config rather than hardcoding the value into markdown files.
Password-protected cases
Use protected cases when a project should remain hidden until the user enters the correct passcode. The important rule is that the real password never belongs in the case markdown file.
Recommended flow:
- Mark the case as protected in the case metadata:
{
id: 'mobile-product-launch',
isProtected: true
}- Keep the server endpoint configured in your app config:
export default {
protection: {
unlockEndpoint: '/api/unlock-case.php'
}
};- Copy the example backend config and add a hash for each protected case:
cp public/api/password.config.example.json public/api/password.config.jsonThen add entries like:
{
"cases": {
"mobile-product-launch": {
"hash": "$argon2id$v=19$m=65536,t=4,p=1$...generated-hash..."
}
}
}- Generate a safe server-side hash:
npm run password:hash -- --case-id mobile-product-launch --password "your-secret"- Keep
public/api/password.config.jsonoutside git and block direct access with the rules inpublic/api/.htaccess.
Operational rules:
- never store raw passwords in markdown or frontmatter
- always verify via the PHP unlock endpoint
- return
Cache-Control: no-storefrom unlock responses - do not rely on
isProtectedalone for real security; it is only the client-side lock state
Header and navigation settings
The app shell exposes header-level contract overrides for breadcrumbs, language menu, navigation region, and About button options. These are usually used when you want a very specific branded shell behavior.
Design tokens and component overrides
The config surface allows token overrides for Valence components through the components section. In practice, start small and keep styling consistent.
Example:
export default {
components: {
atoms: {
button: {
'--ds-button-bg': '#111827',
'--ds-button-hover-bg': '#1f2937',
'--ds-button-radius': '999px'
}
}
}
};Do not treat this as a free-for-all CSS override system. Use it to adjust product-level brand primitives rather than to fight the component library with ad hoc styles.
Theme strategy
A strong default workflow is:
- set a few core brand tokens once
- keep them consistent across the portfolio
- let the app shell and components consume them everywhere
- avoid duplicating color overrides at too many layers
Good examples:
- background tone
- accent color
- typography family
- button radius or density adjustments
Bad examples:
- custom CSS that duplicates Valence component defaults in many places
- setting visual values on individual cases instead of respecting the shared config
Localization configuration
Language behavior is not just a case body feature. It is also a runtime concern.
Your locale setup usually lives in:
configs/i18n/i18n.config.jsconfigs/i18n/i18n.labels.js
Use the CLI instead of editing everything by hand when possible:
npm run add:language -- --code es --name Español --html-lang es-ES
npm run delete:language -- --code es
npm run sync:localesThis keeps the locale config, labels, and case metadata aligned.
Common customization workflows
Change the portfolio title and footer
Update the home config values in your design config so the landing page reflects your brand.
Change a case’s default metadata
Edit the case header block in the markdown file. This is where you set:
idslugByLocaletitleshortDescthumbSrcand thumbnail model metadata- visibility and protection flags
Add a new locale
npm run add:language -- --code fr --name Français --html-lang fr-FRThen translate the generated placeholders and run validation again.
Modify social/SEO output
The case and About page metadata drive share cards and social metadata. Update the localized metadata there instead of trying to patch the rendered output at runtime.
Edge cases and mistakes to avoid
Editing too much in generated output
If you can adjust a brand or behavior in config, prefer that over rewriting the generated shell or custom component logic.
Missing locale keys
If a locale exists in the config but missing in case metadata, the page can fall back awkwardly or render incomplete translations.
Hiding content without understanding visibility rules
web, crawlers, and ai are separate concerns. A case can be visible in the app but still suppressed from crawl-oriented metadata if you configure it that way.
Moving IDs or slug values too casually
Changing a case id or locale slug can break deep links and cached route behavior. Keep these stable when possible.
Recommended workflow
- set homepage metadata and theme tokens
- tune the app shell and header behavior
- add or update case metadata
- add languages where needed
- run validation and build
- preview locally before publishing
Example flow:
npm run portfoliable
npm run validate:content
npm run build
npm run preview