Configuration for a user's public Portfolio site (e.g. alice.zeens.app). One per user, keyed 'self'. A portfolio is a curated list of the user's COLLECTIONS, displayed beautifully: it holds the subdomain, a theme + layout/columns/gap applied to every collection, the ordered list of collection AT-URIs to show, and an optional home collection. Collections own their own title/subtitle/description/photos/slug, which the portfolio reuses; the site header comes from the user's profile. Stored on the user's own PDS so the site is portable and user-owned; PocketBase only mirrors {subdomain -> did} for host routing + uniqueness. Rendered publicly only when `enabled` is true.
literal:self
Fixed literal value
Properties
about
ref
#aboutPage
Optional
Optional About page (alice.zeens.app/about). Absent, or present with enabled=false, means the portfolio has no About page and no About entry in its rail.
app.zeens.v
integer
Optional
Zeens marker so the firehose / lua filters can identify our records.
appearance
string
Optional
Light/dark appearance. 'auto' (default) follows the visitor's system preference; 'light'/'dark' force the theme's respective palette.
auto, light, darkcollections
array
of
ref
#collectionRef
Optional
Ordered list of the collections shown in the portfolio menu. Each entry references an app.zeens.collection record on the same repo. Array order is the menu order.
maxLength: 50 itemscolumns
integer
Optional
Column count for grid/masonry layouts. Allowed 2-4. Default 3.
minimum: 2maximum: 4createdAt
string
datetime
Required
An RFC 3339 formatted timestamp.
enabled
boolean
Required
Whether the public site is live. When false, the subdomain 404s even if reserved.
featured
ref
#featuredBlock
Optional
Hand-picked photos under a heading the owner chooses, shown on the home page below the hero. Independent of any collection. Absent means no section.
gap
integer
Optional
Gap in pixels between photos. Allowed 0, 4, 8, 16, 24, 32. Default 8.
minimum: 0maximum: 32hero
ref
#heroBlock
Optional
The home page's hero — the owner's own photos across the top. ABSENT DOES NOT MEAN NO HERO: the render path falls back to the owner's 6 most-liked photos as a square grid, so a portfolio has a hero without its owner picking one. Present means they chose the photos themselves.
home
ref
#homePage
Optional
Optional plain-text heading + paragraph shown ABOVE the home page's existing content (the collection card grid, or homeCollection's photos when one is set). Absent means the home page renders exactly as it did before this field existed, so no portfolio changes without its owner acting.
homeCollection
string
at-uri
Optional
DEPRECATED. AT-URI of a collection rendered inline as the portfolio's home page. Superseded by the portfolio's own home page (see `home`): the editor no longer offers this, and new records should not set it. Still honoured on read so existing sites don't change appearance, and the editor lets an owner who has it set turn it off.
layout
string
Optional
Photo layout applied to every collection shown in the portfolio. 'single' = one full-width column; 'grid' = uniform columns; 'masonry' = variable-height columns.
single, grid, masonrymenu
ref
#menuSettings
Optional
How the navigation menu renders. Absent means the default: the home page shows the owner's collections.
palette
string
Optional
Id of the chosen color palette (each has a light+dark variant, defined app-side, e.g. 'mono'). Independent of typography. Unknown/absent falls back to the default at render time. (Legacy records may carry a bundled 'theme' field instead; read paths migrate it.)
maxLength: 64 bytessocials
ref
#socialsVisibility
Optional
Which of the owner's profile social links the public site shows. ABSENT MEANS SHOW ALL — every portfolio created before this field keeps showing every link with no migration.
subdomain
string
Optional
The chosen subdomain slug (the 'alice' in alice.zeens.app). Lowercase DNS-safe label; uniqueness + reserved-word validation happen at write time and are mirrored into the PB portfolio_slugs table. Absent until the user picks one.
maxLength: 63 bytestagline
string
Optional
One-line tagline for the site. Mirrored onto site.standard.publication.description, since the portfolio IS the publication — this is the line other standard.site readers show under the name.
maxLength: 2000 bytesmaxGraphemes: 200 graphemestitle
string
Optional
Custom site title shown in the portfolio rail (free text, e.g. 'Ro', 'Alice Photography'). Falls back to the user's profile display name when absent.
maxLength: 120 bytestypography
string
Optional
Id of the chosen typography pairing (font choice, defined app-side, e.g. 'space-grotesk'). Independent of palette. Unknown/absent falls back to the default at render time.
maxLength: 64 bytesView raw schema
{
"description": "Configuration for a user's public Portfolio site (e.g. alice.zeens.app). One per user, keyed 'self'. A portfolio is a curated list of the user's COLLECTIONS, displayed beautifully: it holds the subdomain, a theme + layout/columns/gap applied to every collection, the ordered list of collection AT-URIs to show, and an optional home collection. Collections own their own title/subtitle/description/photos/slug, which the portfolio reuses; the site header comes from the user's profile. Stored on the user's own PDS so the site is portable and user-owned; PocketBase only mirrors {subdomain -> did} for host routing + uniqueness. Rendered publicly only when `enabled` is true.",
"key": "literal:self",
"record": {
"properties": {
"about": {
"description": "Optional About page (alice.zeens.app/about). Absent, or present with enabled=false, means the portfolio has no About page and no About entry in its rail.",
"ref": "#aboutPage",
"type": "ref"
},
"app.zeens.v": {
"description": "Zeens marker so the firehose / lua filters can identify our records.",
"type": "integer"
},
"appearance": {
"description": "Light/dark appearance. 'auto' (default) follows the visitor's system preference; 'light'/'dark' force the theme's respective palette.",
"knownValues": [
"auto",
"light",
"dark"
],
"type": "string"
},
"collections": {
"description": "Ordered list of the collections shown in the portfolio menu. Each entry references an app.zeens.collection record on the same repo. Array order is the menu order.",
"items": {
"ref": "#collectionRef",
"type": "ref"
},
"maxLength": 50,
"type": "array"
},
"columns": {
"description": "Column count for grid/masonry layouts. Allowed 2-4. Default 3.",
"maximum": 4,
"minimum": 2,
"type": "integer"
},
"createdAt": {
"format": "datetime",
"type": "string"
},
"enabled": {
"description": "Whether the public site is live. When false, the subdomain 404s even if reserved.",
"type": "boolean"
},
"featured": {
"description": "Hand-picked photos under a heading the owner chooses, shown on the home page below the hero. Independent of any collection. Absent means no section.",
"ref": "#featuredBlock",
"type": "ref"
},
"gap": {
"description": "Gap in pixels between photos. Allowed 0, 4, 8, 16, 24, 32. Default 8.",
"maximum": 32,
"minimum": 0,
"type": "integer"
},
"hero": {
"description": "The home page's hero — the owner's own photos across the top. ABSENT DOES NOT MEAN NO HERO: the render path falls back to the owner's 6 most-liked photos as a square grid, so a portfolio has a hero without its owner picking one. Present means they chose the photos themselves.",
"ref": "#heroBlock",
"type": "ref"
},
"home": {
"description": "Optional plain-text heading + paragraph shown ABOVE the home page's existing content (the collection card grid, or homeCollection's photos when one is set). Absent means the home page renders exactly as it did before this field existed, so no portfolio changes without its owner acting.",
"ref": "#homePage",
"type": "ref"
},
"homeCollection": {
"description": "DEPRECATED. AT-URI of a collection rendered inline as the portfolio's home page. Superseded by the portfolio's own home page (see `home`): the editor no longer offers this, and new records should not set it. Still honoured on read so existing sites don't change appearance, and the editor lets an owner who has it set turn it off.",
"format": "at-uri",
"type": "string"
},
"layout": {
"description": "Photo layout applied to every collection shown in the portfolio. 'single' = one full-width column; 'grid' = uniform columns; 'masonry' = variable-height columns.",
"knownValues": [
"single",
"grid",
"masonry"
],
"type": "string"
},
"menu": {
"description": "How the navigation menu renders. Absent means the default: the home page shows the owner's collections.",
"ref": "#menuSettings",
"type": "ref"
},
"palette": {
"description": "Id of the chosen color palette (each has a light+dark variant, defined app-side, e.g. 'mono'). Independent of typography. Unknown/absent falls back to the default at render time. (Legacy records may carry a bundled 'theme' field instead; read paths migrate it.)",
"maxLength": 64,
"type": "string"
},
"socials": {
"description": "Which of the owner's profile social links the public site shows. ABSENT MEANS SHOW ALL — every portfolio created before this field keeps showing every link with no migration.",
"ref": "#socialsVisibility",
"type": "ref"
},
"subdomain": {
"description": "The chosen subdomain slug (the 'alice' in alice.zeens.app). Lowercase DNS-safe label; uniqueness + reserved-word validation happen at write time and are mirrored into the PB portfolio_slugs table. Absent until the user picks one.",
"maxLength": 63,
"type": "string"
},
"tagline": {
"description": "One-line tagline for the site. Mirrored onto site.standard.publication.description, since the portfolio IS the publication — this is the line other standard.site readers show under the name.",
"maxGraphemes": 200,
"maxLength": 2000,
"type": "string"
},
"title": {
"description": "Custom site title shown in the portfolio rail (free text, e.g. 'Ro', 'Alice Photography'). Falls back to the user's profile display name when absent.",
"maxLength": 120,
"type": "string"
},
"typography": {
"description": "Id of the chosen typography pairing (font choice, defined app-side, e.g. 'space-grotesk'). Independent of palette. Unknown/absent falls back to the default at render time.",
"maxLength": 64,
"type": "string"
}
},
"required": [
"enabled",
"createdAt"
],
"type": "object"
},
"type": "record"
}