Drawn photos
Photographs drawn in SVG for image slots. Dishes, drinks, places, houses, rooms, portraits, covers and brand boards in natural colours, plus tonal scenes that follow the theme.
One of 5 components in Graphics
Variants
Details
What Drawn photos brings along, read from the registry and the source.
- Category
- Graphics
- Area
- Visuals
- Variants
- 4
- Version
- 1.0.0 · since 1.0.0
- File
- components/ui/photos.tsx
- Brings along
- utilskiticon
- npm packages
- None
- Languages
- Texts come through props
- Studio
- Adjust in code
- Build with AI
- Over MCP in Claude, Cursor, Copilot and Codex
Accessibility
Read from the source of the component.
- Needs no client state. The server writes it in full, so it reads without JavaScript too.
Motion
- No animations, it stands still from the start.
Source
The file aiw add puts into your project. From then on it is yours.
Show the file, 2,098 lines
'use client';
import { useId as useContentId } from 'react';
import { cn, round } from '@/lib/kit';
import { Icon } from '@/components/ui/icon';
/*
* Photos: drawn photographs for image slots. Tonal scenes (Scene, Portrait,
* RoomScene, Landscape) follow the theme and carry the accent. The colour
* photographs (Photo*) have fixed natural colours, like real photos, and
* look the same in both themes.
*/
export const mix = (a: string, pct: number, b = 'var(--background)') => `color-mix(in oklch, ${a} ${pct}%, ${b})`;
/** A drawn landscape that fills its box: sky, sun and layered hills, tinted by the accent */
export function Landscape({ className }: { className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const sky = `sky${id}`;
const sun = `sun${id}`;
return (
<svg viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden>
<defs>
<linearGradient id={sky} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" style={{ stopColor: mix('var(--primary)', 16) }} />
<stop offset="0.7" style={{ stopColor: mix('var(--primary)', 5) }} />
</linearGradient>
<radialGradient id={sun}>
<stop offset="0" style={{ stopColor: mix('var(--primary)', 38), stopOpacity: 0.9 }} />
<stop offset="1" style={{ stopColor: mix('var(--primary)', 10), stopOpacity: 0 }} />
</radialGradient>
</defs>
<rect width="1600" height="900" fill={`url(#${sky})`} />
<circle cx="1090" cy="380" r="300" fill={`url(#${sun})`} />
<circle cx="1090" cy="380" r="92" style={{ fill: mix('var(--primary)', 30) }} />
<path d="M0 560C180 470 330 430 520 470s330 120 520 70 380-170 560-120V900H0Z" style={{ fill: mix('var(--foreground)', 11, mix('var(--primary)', 12)) }} />
<path d="M0 640c210-70 380-60 560-10s340 90 540 30 330-120 500-90V900H0Z" style={{ fill: mix('var(--foreground)', 20, mix('var(--primary)', 10)) }} />
<path d="M0 720c160-40 330-60 520-20s400 70 600 20 330-70 480-50V900H0Z" style={{ fill: mix('var(--foreground)', 32, mix('var(--primary)', 8)) }} />
<path d="M0 800c260-50 520-40 800-5s540 25 800-15V900H0Z" style={{ fill: mix('var(--foreground)', 46, mix('var(--primary)', 6)) }} />
<g style={{ fill: mix('var(--foreground)', 30, 'transparent') }}>
{[[180, 140], [320, 220], [520, 120], [700, 190], [1380, 150], [1480, 260], [860, 90], [260, 300]].map(([x, y]) => (
<circle key={`${x}-${y}`} cx={x} cy={y} r="1.8" />
))}
</g>
</svg>
);
}
/** A still from a product film with a quiet player bar, for video dialogs */
export function VideoScene({ title, time = '0:32', duration = '1:30', className }: { title: string; time?: string; duration?: string; className?: string }) {
return (
<div className={cn('relative aspect-video overflow-hidden bg-surface-2', className)}>
<Landscape className="absolute inset-0" />
<div className="absolute inset-x-0 top-[34%] text-center">
<p className="text-balance px-6 text-[clamp(1.25rem,3vw,2.25rem)] font-semibold tracking-[-0.03em] text-foreground">{title}</p>
</div>
<div className="absolute inset-x-0 bottom-0 flex items-center gap-3 bg-gradient-to-t from-background/90 to-transparent px-5 pb-4 pt-10 text-[12px] tabular-nums text-foreground">
<Icon name="pause" size={16} strokeWidth={2.2} />
<span>{time}</span>
<span className="relative h-1 flex-1 overflow-hidden rounded-full bg-[color-mix(in_oklch,var(--foreground)_18%,transparent)]">
<span className="absolute inset-y-0 left-0 w-[36%] rounded-full bg-primary" />
</span>
<span className="text-muted-foreground">{duration}</span>
</div>
</div>
);
}
/* ------------------------------------------------------------------ */
/* Content motifs: photographs drawn as tonal scenes, portraits, maps */
/* (blog, gallery, team, about, content, contact) */
/* ------------------------------------------------------------------ */
/*
* A tonal ramp that behaves like a photograph: the light and dark relations
* stay the same in both themes, dark mode only dims them. --pha and --phb
* carry the accent into the picture.
*/
export const PHOTO =
'[--ph0:color-mix(in_oklab,var(--foreground)_3%,var(--background))] [--ph1:color-mix(in_oklab,var(--foreground)_9%,var(--background))] [--ph2:color-mix(in_oklab,var(--foreground)_19%,var(--background))] [--ph3:color-mix(in_oklab,var(--foreground)_36%,var(--background))] [--ph4:color-mix(in_oklab,var(--foreground)_58%,var(--background))] [--ph5:color-mix(in_oklab,var(--foreground)_80%,var(--background))] [--pha:color-mix(in_oklab,var(--primary)_55%,var(--ph1))] [--phb:color-mix(in_oklab,var(--primary)_16%,var(--ph0))] ' +
'dark:[--ph0:color-mix(in_oklab,var(--foreground)_44%,var(--background))] dark:[--ph1:color-mix(in_oklab,var(--foreground)_35%,var(--background))] dark:[--ph2:color-mix(in_oklab,var(--foreground)_26%,var(--background))] dark:[--ph3:color-mix(in_oklab,var(--foreground)_18%,var(--background))] dark:[--ph4:color-mix(in_oklab,var(--foreground)_11%,var(--background))] dark:[--ph5:color-mix(in_oklab,var(--foreground)_5%,var(--background))] dark:[--pha:color-mix(in_oklab,var(--primary)_42%,var(--ph2))] dark:[--phb:color-mix(in_oklab,var(--primary)_14%,var(--ph2))]';
export const tone = (v: string, opacity?: number): React.CSSProperties => ({ fill: `var(--${v})`, ...(opacity !== undefined ? { opacity } : {}) });
export const r01 = (seed: number, n: number) => round(Math.abs((Math.sin(seed * 12.9898 + n * 78.233) * 43758.5453) % 1), 4);
/** A soft ridge line, used for hills */
function ridge(base: number, amp: number, freq: number, phase: number, w = 400, h = 300) {
const pts: string[] = [];
for (let x = 0; x <= w; x += 10) {
const t = (x / w) * Math.PI * 2;
const y = base + amp * Math.sin(t * freq + phase) + amp * 0.35 * Math.sin(t * freq * 2.7 + phase * 1.9);
pts.push(`${x} ${round(y, 1)}`);
}
return `M0 ${h} L${pts.join(' L')} L${w} ${h} Z`;
}
export type SceneKind = 'hills' | 'architecture' | 'interior' | 'still-life' | 'city' | 'sea' | 'desk' | 'abstract' | 'plant';
/**
* A drawn photograph for image slots. Same kind and seed, same picture.
* Size it with className (aspect ratio or height).
*/
export function Scene({ kind = 'hills', seed = 1, className }: { kind?: SceneKind; seed?: number; className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const s = seed;
const sky = `sky${id}`;
const shade = `shade${id}`;
const art = (() => {
switch (kind) {
case 'hills': {
const sunX = round(90 + r01(s, 1) * 220);
const sunY = round(70 + r01(s, 2) * 30);
return (
<>
<rect width="400" height="300" fill={`url(#${sky})`} />
<circle cx={sunX} cy={sunY} r="54" style={tone('pha', 0.14)} />
<circle cx={sunX} cy={sunY} r="30" style={tone('pha', 0.9)} />
<path d={ridge(150, 16, 1.4, s)} style={tone('ph2')} />
<path d={ridge(185, 14, 1.9, s * 2.1)} style={tone('ph3')} />
<path d={ridge(222, 12, 1.2, s * 3.3)} style={tone('ph4')} />
<path d={ridge(262, 8, 2.4, s * 0.7)} style={tone('ph5')} />
</>
);
}
case 'sea': {
return (
<>
<rect width="400" height="300" fill={`url(#${sky})`} />
<circle cx={round(120 + r01(s, 3) * 160)} cy="150" r="38" style={tone('pha', 0.85)} />
<path d="M250 168 C 272 150, 300 146, 330 168 Z" style={tone('ph3')} />
<rect y="168" width="400" height="132" style={tone('ph2')} />
<rect y="168" width="400" height="132" fill={`url(#${shade})`} />
{[186, 204, 224, 248, 276].map((y, i) => (
<path key={y} d={`M${round(20 + r01(s, i) * 60)} ${y} h${round(80 + i * 30)} M${round(220 + r01(s, i + 9) * 60)} ${y + 4} h${round(50 + i * 22)}`} style={{ stroke: 'var(--ph0)', strokeWidth: 1.4 + i * 0.4, opacity: 0.55, strokeLinecap: 'round' }} />
))}
<path d="M92 196 l18 0 l-3 6 l-12 0 Z M100 196 v-22 l11 20 Z" style={tone('ph5')} />
</>
);
}
case 'architecture': {
return (
<>
<rect width="400" height="300" fill={`url(#${sky})`} />
<path d="M60 300 V96 L232 60 V300 Z" style={tone('ph0')} />
<path d="M232 60 L344 92 V300 H232 Z" style={tone('ph3')} />
{Array.from({ length: 7 }, (_, row) =>
Array.from({ length: 6 }, (_, c) => (
<rect key={`${row}-${c}`} x={74 + c * 26} y={round(118 + row * 26 - c * 5.4, 1)} width="14" height="16" rx="1" style={tone(r01(s, row * 7 + c) > 0.82 ? 'pha' : 'ph2')} />
)),
)}
{Array.from({ length: 7 }, (_, row) => (
<path key={row} d={`M240 ${round(104 + row * 26, 1)} L336 ${round(130 + row * 26, 1)}`} style={{ stroke: 'var(--ph4)', strokeWidth: 3 }} />
))}
<path d="M60 96 L232 60 L344 92" style={{ fill: 'none', stroke: 'var(--ph5)', strokeWidth: 2 }} />
<rect y="282" width="400" height="18" style={tone('ph4')} />
</>
);
}
case 'interior': {
return (
<>
<rect width="400" height="300" style={tone('ph0')} />
<rect y="212" width="400" height="88" style={tone('ph2')} />
<rect x="214" y="44" width="128" height="132" rx="3" style={tone('ph1')} />
<path d="M214 44 h128 v132 h-128 Z M278 44 v132 M214 110 h128" style={{ fill: 'none', stroke: 'var(--ph3)', strokeWidth: 4 }} />
<path d="M214 176 L150 300 H262 L342 176 Z" style={tone('ph0', 0.55)} />
<rect x="40" y="168" width="168" height="54" rx="14" style={tone('ph4')} />
<rect x="52" y="140" width="144" height="42" rx="12" style={tone('ph4')} />
<rect x="40" y="180" width="30" height="42" rx="10" style={tone('ph5')} />
<rect x="178" y="180" width="30" height="42" rx="10" style={tone('ph5')} />
<rect x="96" y="150" width="34" height="28" rx="6" style={tone('pha')} />
<ellipse cx="200" cy="262" rx="150" ry="16" style={tone('pha', 0.35)} />
<path d="M362 222 v-108 M362 114 l-20 -10 h40 Z" style={{ fill: 'var(--ph4)', stroke: 'var(--ph4)', strokeWidth: 2 }} />
<path d="M24 222 h26 l-4 -26 h-18 Z" style={tone('ph3')} />
<path d="M37 196 C 20 170, 26 140, 37 128 C 48 140, 54 170, 37 196" style={tone('ph5')} />
<path d="M37 196 C 22 182, 10 176, 6 164 M37 196 C 52 182, 66 178, 70 166" style={{ fill: 'none', stroke: 'var(--ph5)', strokeWidth: 3, strokeLinecap: 'round' }} />
</>
);
}
case 'still-life': {
return (
<>
<rect width="400" height="300" fill={`url(#${sky})`} />
<rect y="206" width="400" height="94" style={tone('ph2')} />
<ellipse cx="146" cy="226" rx="54" ry="8" style={tone('ph4', 0.35)} />
<path d="M120 226 C 104 196, 106 150, 128 128 C 132 116, 132 104, 128 94 H164 C 160 104, 160 116, 164 128 C 186 150, 188 196, 172 226 Z" style={tone('ph4')} />
<path d="M134 94 C 136 70, 132 48, 120 30 M156 94 C 158 70, 170 54, 186 44" style={{ fill: 'none', stroke: 'var(--ph5)', strokeWidth: 2.5 }} />
<ellipse cx="120" cy="30" rx="12" ry="7" style={tone('pha')} transform="rotate(-30 120 30)" />
<ellipse cx="186" cy="44" rx="13" ry="7" style={tone('pha')} transform="rotate(20 186 44)" />
<ellipse cx="268" cy="228" rx="62" ry="7" style={tone('ph4', 0.3)} />
<circle cx="248" cy="178" r="20" style={tone('ph3')} />
<circle cx="284" cy="176" r="17" style={tone('ph1')} />
<path d="M212 190 H324 C 320 214, 300 228, 268 228 C 236 228, 216 214, 212 190 Z" style={tone('pha')} />
</>
);
}
case 'city': {
const buildings = Array.from({ length: 11 }, (_, i) => ({ x: i * 38 - 8, w: 30 + round(r01(s, i) * 16), h: 70 + round(r01(s, i + 20) * 130) }));
return (
<>
<rect width="400" height="300" fill={`url(#${sky})`} />
<circle cx={round(80 + r01(s, 5) * 240)} cy="96" r="22" style={tone('pha', 0.9)} />
{buildings.map((b, i) => (
<g key={i}>
<rect x={b.x} y={300 - b.h} width={b.w} height={b.h} style={tone(i % 3 === 0 ? 'ph5' : 'ph4')} />
{Array.from({ length: Math.floor(b.h / 18) }, (_, k) =>
[0, 1].map((c) =>
r01(s, i * 31 + k * 3 + c) > 0.55 ? <rect key={`${k}-${c}`} x={round(b.x + 6 + c * (b.w / 2 - 2))} y={300 - b.h + 10 + k * 18} width="6" height="7" style={tone(r01(s, k + i) > 0.8 ? 'pha' : 'ph1', 0.85)} /> : null,
),
)}
</g>
))}
</>
);
}
case 'desk': {
return (
<>
<rect width="400" height="300" style={tone('ph1')} />
<g transform="rotate(-8 200 150)">
<rect x="96" y="70" width="190" height="124" rx="8" style={tone('ph4')} />
<rect x="104" y="78" width="174" height="108" rx="3" style={tone('ph5')} />
<rect x="116" y="92" width="70" height="6" rx="3" style={tone('pha')} />
<rect x="116" y="106" width="120" height="4" rx="2" style={tone('ph3')} />
<rect x="116" y="116" width="96" height="4" rx="2" style={tone('ph3')} />
<rect x="116" y="136" width="150" height="38" rx="3" style={tone('ph4')} />
<rect x="86" y="194" width="210" height="16" rx="5" style={tone('ph3')} />
</g>
<g transform="rotate(10 320 220)">
<rect x="268" y="182" width="96" height="120" rx="4" style={tone('ph0')} />
{[204, 218, 232, 246, 260].map((y) => (
<path key={y} d={`M282 ${y} h${y === 260 ? 40 : 68}`} style={{ stroke: 'var(--ph2)', strokeWidth: 2.5, strokeLinecap: 'round' }} />
))}
</g>
<circle cx="56" cy="226" r="30" style={tone('ph0')} />
<circle cx="56" cy="226" r="22" style={tone('ph4')} />
<path d="M84 226 h16" style={{ stroke: 'var(--ph0)', strokeWidth: 8, strokeLinecap: 'round' }} />
<circle cx="350" cy="60" r="34" style={tone('ph3')} />
{[0, 60, 120, 180, 240, 300].map((a) => (
<ellipse key={a} cx="350" cy="38" rx="9" ry="22" style={tone('pha')} transform={`rotate(${a} 350 60)`} />
))}
</>
);
}
case 'abstract': {
return (
<>
<rect width="400" height="300" style={tone('ph1')} />
<circle cx={round(130 + r01(s, 1) * 40)} cy="140" r="92" style={tone('pha')} />
<path d="M200 300 V170 A100 100 0 0 1 400 170 V300 Z" style={tone('ph4')} />
<rect x="248" y="40" width="96" height="96" style={tone('ph0')} />
<path d="M40 260 L340 30" style={{ stroke: 'var(--ph5)', strokeWidth: 3 }} />
<circle cx="300" cy="230" r="26" style={tone('ph1')} />
</>
);
}
case 'plant': {
const leaves: [number, number, string][] = [
[-58, 150, 'ph4'],
[-24, 170, 'ph5'],
[14, 160, 'ph4'],
[46, 140, 'ph5'],
[-4, 120, 'pha'],
];
return (
<>
<rect width="400" height="300" fill={`url(#${sky})`} />
<rect y="236" width="400" height="64" style={tone('ph2')} />
<ellipse cx="200" cy="270" rx="70" ry="8" style={tone('ph4', 0.3)} />
{leaves.map(([a, len, c], i) => (
<g key={i} transform={`rotate(${a} 200 214)`}>
<path d={`M200 214 V${round(214 - len * 0.55)}`} style={{ stroke: 'var(--ph5)', strokeWidth: 3 }} />
<path d={`M200 ${round(214 - len * 0.45)} C 160 ${round(214 - len * 0.7)}, 170 ${214 - len}, 200 ${round(214 - len * 1.08)} C 230 ${214 - len}, 240 ${round(214 - len * 0.7)}, 200 ${round(214 - len * 0.45)} Z`} style={tone(c)} />
</g>
))}
<path d="M160 206 H240 L230 268 H170 Z" style={tone('ph3')} />
<rect x="156" y="200" width="88" height="14" rx="3" style={tone('ph4')} />
</>
);
}
}
})();
return (
<div className={cn('relative overflow-hidden bg-[var(--ph1)]', PHOTO, className)} aria-hidden>
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" className="absolute inset-0 size-full">
<defs>
<linearGradient id={sky} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="var(--ph0)" />
<stop offset="1" stopColor="var(--phb)" />
</linearGradient>
<linearGradient id={shade} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="var(--ph3)" stopOpacity="0" />
<stop offset="1" stopColor="var(--ph4)" stopOpacity="0.8" />
</linearGradient>
</defs>
{art}
</svg>
</div>
);
}
/** A room before and after, for comparison sliders. Same frame, same perspective. */
export function RoomScene({ finished = false, className }: { finished?: boolean; className?: string }) {
return (
<div className={cn('relative overflow-hidden', PHOTO, className)} aria-hidden>
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" className="absolute inset-0 size-full">
<rect width="400" height="300" style={tone(finished ? 'ph0' : 'ph2')} />
<path d="M0 220 L400 220 V300 H0 Z" style={tone(finished ? 'ph2' : 'ph3')} />
<rect x="236" y="40" width="112" height="140" rx="2" style={tone('ph1')} />
<path d="M236 40 h112 v140 h-112 Z M292 40 v140" style={{ fill: 'none', stroke: `var(--${finished ? 'ph3' : 'ph4'})`, strokeWidth: 5 }} />
{finished ? (
<>
<path d="M236 180 L176 300 H284 L348 180 Z" style={tone('ph0', 0.6)} />
<rect x="34" y="176" width="176" height="52" rx="14" style={tone('ph4')} />
<rect x="46" y="146" width="152" height="44" rx="12" style={tone('ph4')} />
<rect x="92" y="156" width="36" height="28" rx="6" style={tone('pha')} />
<ellipse cx="190" cy="266" rx="150" ry="16" style={tone('pha', 0.4)} />
<rect x="60" y="56" width="70" height="52" rx="2" style={tone('phb')} />
<path d="M60 56 h70 v52 h-70 Z" style={{ fill: 'none', stroke: 'var(--ph4)', strokeWidth: 3 }} />
<path d="M74 98 l16 -20 l12 12 l8 -8 l12 16 Z" style={tone('pha')} />
<path d="M370 220 v-100 M370 120 l-18 -10 h36 Z" style={{ fill: 'var(--ph4)', stroke: 'var(--ph4)', strokeWidth: 2 }} />
<path d="M20 220 h24 l-3 -22 h-18 Z" style={tone('ph3')} />
<path d="M32 198 C 18 176, 22 150, 32 138 C 42 150, 46 176, 32 198" style={tone('ph5')} />
</>
) : (
<>
<path d="M40 120 l30 -6 M300 236 l40 6 M120 250 l26 -4" style={{ stroke: 'var(--ph4)', strokeWidth: 2, opacity: 0.6 }} />
<rect x="60" y="176" width="64" height="44" rx="2" style={tone('ph4')} />
<rect x="70" y="150" width="44" height="26" rx="2" style={tone('ph3')} />
<path d="M150 220 v-60 l26 -20 l26 20 v60" style={{ fill: 'none', stroke: 'var(--ph4)', strokeWidth: 3 }} />
<path d="M170 76 v40" style={{ stroke: 'var(--ph4)', strokeWidth: 2 }} />
<circle cx="170" cy="120" r="6" style={tone('ph1')} />
</>
)}
</svg>
</div>
);
}
/**
* A drawn portrait, head and shoulders. Seeds pick hair, clothes,
* glasses and the background tint, so a team looks like a team.
*/
export function Portrait({ seed = 0, className }: { seed?: number; className?: string }) {
const hair = seed % 6;
const cloth = ['ph4', 'pha', 'ph5', 'ph3', 'ph4', 'pha'][seed % 6];
const bg = ['phb', 'ph1', 'ph2', 'phb', 'ph1', 'ph2'][(seed + 2) % 6];
const hairTone = seed % 4 === 1 ? 'ph3' : 'ph5';
const glasses = seed % 5 === 2 || seed % 7 === 4;
const collar = seed % 3;
return (
<div className={cn('relative overflow-hidden', PHOTO, className)} aria-hidden>
<svg viewBox="0 0 400 500" preserveAspectRatio="xMidYMid slice" className="absolute inset-0 size-full">
<rect width="400" height="500" style={tone(bg)} />
<circle cx="290" cy="120" r="150" style={tone('ph0', 0.35)} />
<g transform="translate(200 520) scale(1.16) translate(-200 -500)">
{hair === 1 && <path d="M122 236 C 112 150, 160 110, 202 110 C 248 110, 290 150, 280 236 C 286 300, 300 360, 294 400 H108 C 102 360, 116 300, 122 236 Z" style={tone(hairTone)} />}
{hair === 4 && <path d="M130 230 C 124 160, 160 118, 202 118 C 246 118, 278 160, 272 230 L 282 330 H120 Z" style={tone(hairTone)} />}
<path d="M40 500 C 44 420, 92 384, 160 372 L 240 372 C 308 384, 356 420, 360 500 Z" style={tone(cloth)} />
<path d="M170 290 h60 v76 c0 20 -60 20 -60 0 Z" style={tone('ph2')} />
{collar === 0 && <path d="M160 372 L200 430 L240 372" style={{ fill: 'none', stroke: 'var(--ph0)', strokeWidth: 6, strokeLinejoin: 'round', opacity: 0.7 }} />}
{collar === 1 && <path d="M160 374 C 176 404, 224 404, 240 374" style={{ fill: 'none', stroke: 'var(--ph1)', strokeWidth: 8, opacity: 0.7 }} />}
{collar === 2 && <path d="M168 372 L200 418 L232 372 L246 376 L214 460 H186 L154 376 Z" style={tone('ph0', 0.85)} />}
<ellipse cx="141" cy="246" rx="11" ry="18" style={tone('ph1')} />
<ellipse cx="259" cy="246" rx="11" ry="18" style={tone('ph1')} />
<ellipse cx="200" cy="232" rx="60" ry="76" style={tone('ph1')} />
<path d="M150 268 C 166 304, 234 304, 250 268 C 240 300, 220 312, 200 312 C 180 312, 160 300, 150 268 Z" style={tone('ph2', 0.6)} />
{hair === 0 && <path d="M138 226 C 132 170, 168 142, 204 142 C 246 142, 270 170, 262 226 C 254 196, 236 180, 206 178 C 180 178, 150 190, 138 226 Z" style={tone(hairTone)} />}
{hair === 1 && <path d="M140 230 C 138 176, 170 150, 204 150 C 238 150, 264 178, 262 230 C 248 196, 222 182, 198 184 C 176 186, 152 200, 140 230 Z" style={tone(hairTone)} />}
{hair === 2 && (
<>
<circle cx="200" cy="128" r="30" style={tone(hairTone)} />
<path d="M140 222 C 140 176, 168 152, 200 152 C 236 152, 262 178, 260 222 C 246 192, 226 180, 200 180 C 174 180, 152 192, 140 222 Z" style={tone(hairTone)} />
</>
)}
{hair === 3 && [0, 1, 2, 3, 4, 5, 6, 7, 8].map((i) => <circle key={i} cx={round(200 + Math.cos((i / 8) * Math.PI) * 58)} cy={round(206 - Math.sin((i / 8) * Math.PI) * 50)} r="22" style={tone(hairTone)} />)}
{hair === 4 && <path d="M140 220 C 146 170, 176 150, 206 152 C 236 154, 256 170, 262 210 C 236 196, 206 170, 196 166 C 182 186, 164 204, 140 220 Z" style={tone(hairTone)} />}
{hair === 5 && (
<>
<path d="M142 214 C 146 172, 174 156, 204 156 C 236 156, 258 174, 258 214 C 246 196, 228 190, 200 190 C 174 190, 154 196, 142 214 Z" style={tone(hairTone, 0.85)} />
<path d="M152 262 C 156 300, 180 318, 200 318 C 220 318, 244 300, 248 262 C 236 280, 222 286, 200 286 C 178 286, 164 280, 152 262 Z" style={tone(hairTone, 0.85)} />
</>
)}
<circle cx="178" cy="240" r="4" style={tone('ph5', 0.75)} />
<circle cx="222" cy="240" r="4" style={tone('ph5', 0.75)} />
<path d="M188 280 C 196 286, 204 286, 212 280" style={{ fill: 'none', stroke: 'var(--ph4)', strokeWidth: 3, strokeLinecap: 'round', opacity: 0.7 }} />
{glasses && <path d="M156 228 h40 v22 a8 8 0 0 1 -8 8 h-24 a8 8 0 0 1 -8 -8 Z M204 228 h40 v22 a8 8 0 0 1 -8 8 h-24 a8 8 0 0 1 -8 -8 Z M196 236 h8" style={{ fill: 'none', stroke: 'var(--ph5)', strokeWidth: 3.5, strokeLinejoin: 'round' }} />}
</g>
</svg>
</div>
);
}
/* ------------------------------------------------------------------ */
/* Colour photographs (fixed colours, like real photos in both themes) */
/* For hero slots where a tonal scene would read grey with a neutral */
/* accent: a dusk valley, a café in morning light and a workshop. */
/* ------------------------------------------------------------------ */
const pine = (x: number, base: number, h: number) => {
const w = h * 0.3;
return `M${x} ${base - h} L${round(x + w * 0.62)} ${round(base - h * 0.56)} L${round(x + w * 0.36)} ${round(base - h * 0.56)} L${round(x + w)} ${round(base - h * 0.12)} L${round(x + w * 0.12)} ${round(base - h * 0.12)} L${round(x + w * 0.12)} ${base} L${round(x - w * 0.12)} ${base} L${round(x - w * 0.12)} ${round(base - h * 0.12)} L${round(x - w)} ${round(base - h * 0.12)} L${round(x - w * 0.36)} ${round(base - h * 0.56)} L${round(x - w * 0.62)} ${round(base - h * 0.56)} Z`;
};
/** A valley at dusk: layered ridges, a lake with the reflection of the sun and a lit cabin on the shore */
export function PhotoValley({ className }: { className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const g = (n: string) => `${n}${id}`;
const u = (n: string) => `url(#${n}${id})`;
const stars = [[120, 90], [260, 160], [410, 70], [560, 210], [700, 120], [820, 60], [1010, 150], [1160, 80], [1290, 190], [1430, 110], [1540, 230], [340, 260], [1220, 280], [640, 300], [90, 300], [1480, 40]];
return (
<svg viewBox="0 0 1600 1000" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden data-clip-ok>
<defs>
<linearGradient id={g('sky')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#090d2a" />
<stop offset="0.3" stopColor="#221f52" />
<stop offset="0.5" stopColor="#5a3a6c" />
<stop offset="0.62" stopColor="#b5626a" />
<stop offset="0.72" stopColor="#f09a68" />
</linearGradient>
<radialGradient id={g('sun')}>
<stop offset="0" stopColor="#ffe3b0" stopOpacity="0.95" />
<stop offset="0.22" stopColor="#ffbb7a" stopOpacity="0.55" />
<stop offset="1" stopColor="#f0805a" stopOpacity="0" />
</radialGradient>
<linearGradient id={g('far')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#7e5784" />
<stop offset="1" stopColor="#a9687a" />
</linearGradient>
<linearGradient id={g('mid')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#433764" />
<stop offset="1" stopColor="#5d4670" />
</linearGradient>
<linearGradient id={g('near')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#1d1d40" />
<stop offset="1" stopColor="#29284b" />
</linearGradient>
<linearGradient id={g('lake')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#b9706c" />
<stop offset="0.1" stopColor="#4b3a63" />
<stop offset="0.55" stopColor="#1a1a3a" />
<stop offset="1" stopColor="#0a0b20" />
</linearGradient>
<linearGradient id={g('mist')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#ffcfae" stopOpacity="0" />
<stop offset="0.5" stopColor="#ffcfae" stopOpacity="0.3" />
<stop offset="1" stopColor="#ffcfae" stopOpacity="0" />
</linearGradient>
<radialGradient id={g('lamp')}>
<stop offset="0" stopColor="#ffc56e" stopOpacity="0.55" />
<stop offset="1" stopColor="#ffc56e" stopOpacity="0" />
</radialGradient>
</defs>
<rect width="1600" height="1000" fill={u('sky')} />
<g fill="#ffffff">
{stars.map(([x, y], i) => (
<circle key={i} cx={x} cy={y} r={i % 3 === 0 ? 2 : 1.3} opacity={round(0.35 + (i % 4) * 0.12)} />
))}
</g>
<g fill="#f6a983" opacity="0.38">
<ellipse cx="420" cy="430" rx="260" ry="9" />
<ellipse cx="640" cy="462" rx="180" ry="6" />
<ellipse cx="1280" cy="420" rx="230" ry="8" />
<ellipse cx="1120" cy="470" rx="140" ry="5" />
</g>
<g fill="#8d6aa8" opacity="0.32">
<ellipse cx="300" cy="330" rx="220" ry="7" />
<ellipse cx="1360" cy="320" rx="200" ry="6" />
</g>
<circle cx="960" cy="600" r="420" fill={u('sun')} />
<circle cx="960" cy="606" r="56" fill="#ffe9c4" />
<path d="M0 560 L140 520 L260 548 L380 470 L500 520 L620 455 L760 540 L860 600 L960 652 L1060 598 L1160 520 L1280 468 L1400 515 L1500 470 L1600 500 V790 H0 Z" fill={u('far')} />
<rect y="600" width="1600" height="150" fill={u('mist')} />
<path d="M0 640 L120 600 L240 630 L360 575 L480 620 L600 592 L720 650 L840 690 L960 712 L1080 684 L1200 630 L1320 600 L1440 640 L1600 592 V790 H0 Z" fill={u('mid')} />
<path d="M0 702 L160 660 L300 700 L420 682 L560 722 L700 736 L960 750 L1200 740 L1340 702 L1480 672 L1600 690 V790 H0 Z" fill={u('near')} />
<rect y="748" width="1600" height="252" fill={u('lake')} />
<g fill="#ffd9a6">
{[180, 150, 128, 104, 84, 66, 50, 36, 24, 14].map((w, i) => (
<rect key={i} x={960 - w / 2 + (i % 2 ? 6 : -4)} y={758 + i * 22} width={w} height={i < 3 ? 5 : 3} rx="2" opacity={round(0.7 - i * 0.06)} />
))}
</g>
<path d="M0 772 C 160 768, 330 780, 520 812 L 600 1000 H0 Z" fill="#0b0c1e" />
<path d="M1000 1000 L1070 806 C 1240 784, 1420 772, 1600 768 V1000 Z" fill="#0b0c1e" />
<g fill="#0b0c1e">
{[[40, 790, 150], [110, 786, 210], [190, 792, 170], [262, 798, 240], [340, 804, 180], [420, 812, 130], [1460, 776, 230], [1530, 774, 170], [1580, 772, 250]].map(([x, b, h]) => (
<path key={x} d={pine(x, b, h)} />
))}
</g>
<g transform="translate(-120 18)">
<circle cx="1300" cy="770" r="120" fill={u('lamp')} />
<path d="M1238 790 V746 L1300 704 L1362 746 V790 Z" fill="#141329" />
<path d="M1226 750 L1300 698 L1374 750" fill="none" stroke="#0a0a1c" strokeWidth="10" strokeLinejoin="round" />
<rect x="1256" y="752" width="18" height="16" rx="1.5" fill="#ffc977" />
<rect x="1326" y="752" width="18" height="16" rx="1.5" fill="#ffc977" />
<rect x="1291" y="760" width="18" height="30" rx="1.5" fill="#f4a65a" />
</g>
</svg>
);
}
/** A café in morning light: a garden window, a pendant lamp, a coffee and a croissant on a wooden table */
export function PhotoCafe({ className }: { className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const g = (n: string) => `${n}${id}`;
const u = (n: string) => `url(#${n}${id})`;
return (
<svg viewBox="0 0 800 1000" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden data-clip-ok>
<defs>
<linearGradient id={g('wall')} x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stopColor="#efe2cf" />
<stop offset="1" stopColor="#d6bf9f" />
</linearGradient>
<linearGradient id={g('garden')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#e6efe2" />
<stop offset="0.55" stopColor="#b9d0ad" />
<stop offset="1" stopColor="#7f9f73" />
</linearGradient>
<linearGradient id={g('beam')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#fff6df" stopOpacity="0.55" />
<stop offset="1" stopColor="#fff6df" stopOpacity="0" />
</linearGradient>
<linearGradient id={g('table')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#b47a4c" />
<stop offset="1" stopColor="#8c5733" />
</linearGradient>
<linearGradient id={g('floor')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#a87d58" />
<stop offset="1" stopColor="#6e4c33" />
</linearGradient>
<radialGradient id={g('glow')}>
<stop offset="0" stopColor="#ffd88f" stopOpacity="0.8" />
<stop offset="1" stopColor="#ffd88f" stopOpacity="0" />
</radialGradient>
<linearGradient id={g('cup')} x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stopColor="#ffffff" />
<stop offset="1" stopColor="#ddd3c6" />
</linearGradient>
</defs>
<rect width="800" height="1000" fill={u('wall')} />
<rect x="350" y="90" width="400" height="560" rx="6" fill={u('garden')} />
<g fill="#6f9466">
<circle cx="420" cy="520" r="90" />
<circle cx="540" cy="560" r="110" fill="#5f8758" />
<circle cx="690" cy="500" r="120" />
<circle cx="610" cy="430" r="70" fill="#86ab7a" />
<circle cx="430" cy="410" r="50" fill="#9dbd8f" />
</g>
<path d="M350 90 h400 v560 h-400 Z M550 90 v560 M350 360 h400" fill="none" stroke="#3a2a20" strokeWidth="14" />
<rect x="336" y="646" width="428" height="18" rx="3" fill="#4a3427" />
<path d="M350 650 L120 1000 H560 L750 650 Z" fill={u('beam')} />
<rect y="760" width="800" height="240" fill={u('floor')} />
<path d="M0 820 H800 M0 890 H800 M0 960 H800" stroke="#5b3e2a" strokeWidth="2" opacity="0.35" />
<path d="M190 0 V200" stroke="#2c2622" strokeWidth="3" />
<circle cx="190" cy="250" r="150" fill={u('glow')} />
<path d="M130 250 C 130 214, 156 196, 190 196 C 224 196, 250 214, 250 250 Z" fill="#2f4a3b" />
<ellipse cx="190" cy="252" rx="20" ry="8" fill="#ffe2a6" />
<rect x="40" y="380" width="230" height="14" rx="3" fill="#7a5034" />
<path d="M62 380 v-58 h36 v58 Z" fill="#c9714a" />
<path d="M112 380 c-8 -30, 4 -56, 22 -60 c18 4, 30 30, 22 60 Z" fill="#efe6d8" />
<path d="M170 380 v-40 h70 v40 Z" fill="#9aac8e" />
<path d="M180 340 c4 -26, 16 -40, 26 -46 M206 340 c6 -20, 18 -30, 30 -32" fill="none" stroke="#4e7a55" strokeWidth="4" strokeLinecap="round" />
<path d="M118 760 L104 940 M362 760 L376 940 M240 760 V940" stroke="#5a3a24" strokeWidth="12" strokeLinecap="round" />
<ellipse cx="240" cy="742" rx="250" ry="40" fill="#6c4329" />
<ellipse cx="240" cy="732" rx="250" ry="40" fill={u('table')} />
<ellipse cx="160" cy="726" rx="60" ry="14" fill="#c9bfb2" />
<ellipse cx="160" cy="722" rx="58" ry="12" fill="#f7f2ea" />
<path d="M128 680 h64 c0 30, -10 44, -32 44 c-22 0, -32 -14, -32 -44 Z" fill={u('cup')} />
<path d="M190 690 c18 -2, 22 18, 2 24" fill="none" stroke="#e7ded2" strokeWidth="6" />
<ellipse cx="160" cy="681" rx="32" ry="8" fill="#5a3322" />
<ellipse cx="160" cy="681" rx="22" ry="5" fill="#b27e52" opacity="0.8" />
<ellipse cx="318" cy="742" rx="54" ry="12" fill="#c9bfb2" />
<ellipse cx="318" cy="738" rx="52" ry="10" fill="#f7f2ea" />
<path d="M278 738 c0 -26, 22 -40, 40 -40 c20 0, 40 14, 40 40 c-10 6, -70 6, -80 0 Z" fill="#d79a52" />
<path d="M296 706 c4 10, 4 22, 0 32 M318 700 c4 12, 4 26, 0 38 M340 706 c4 10, 4 22, 0 32" fill="none" stroke="#a96b30" strokeWidth="4" strokeLinecap="round" />
<path d="M402 724 h26 l-4 -46 h-18 Z" fill="#efe6d8" />
<path d="M415 678 c-2 -30, 6 -50, 16 -62 M415 678 c-8 -20, -20 -30, -30 -32" fill="none" stroke="#4e7a55" strokeWidth="3" strokeLinecap="round" />
<circle cx="431" cy="612" r="9" fill="#e9885d" />
<circle cx="384" cy="644" r="7" fill="#f2b45c" />
<path d="M600 1000 v-120 c0 -40, 20 -60, 60 -60 h40 v180" fill="#2c2019" opacity="0.9" />
<path d="M560 1000 h170 v-14 h-170 Z" fill="#20160f" />
<path d="M690 980 c-20 -80, -10 -150, 40 -200" fill="none" stroke="#2c2019" strokeWidth="10" strokeLinecap="round" />
</svg>
);
}
/** A workshop bench with ceramics in window light, for editorial covers */
export function PhotoWorkshop({ className }: { className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const g = (n: string) => `${n}${id}`;
const u = (n: string) => `url(#${n}${id})`;
const vessels: { d: string; fill: string; cx: number; rx: number }[] = [
{ d: 'M430 640 c-40 -60, -44 -170, -6 -230 c10 -16, 10 -40, 2 -56 h76 c-8 16, -8 40, 2 56 c38 60, 34 170, -6 230 Z', fill: '#c2653c', cx: 466, rx: 80 },
{ d: 'M600 640 c-70 -10, -96 -60, -96 -112 h232 c0 52, -26 102, -96 112 Z', fill: '#efe4d3', cx: 620, rx: 120 },
{ d: 'M800 640 c-30 -30, -34 -120, -10 -170 c12 -22, 14 -60, 6 -90 h52 c-8 30, -6 68, 6 90 c24 50, 20 140, -10 170 Z', fill: '#93a587', cx: 822, rx: 60 },
{ d: 'M950 640 c-24 -12, -30 -60, -26 -96 h112 c4 36, -2 84, -26 96 Z', fill: '#2f2b29', cx: 980, rx: 64 },
{ d: 'M1130 640 c-56 -20, -70 -110, -40 -170 c20 -40, 70 -56, 110 -40 c50 22, 66 100, 40 170 c-14 40, -60 52, -110 40 Z', fill: '#d9a46e', cx: 1170, rx: 100 },
];
return (
<svg viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden data-clip-ok>
<defs>
<linearGradient id={g('wall')} x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stopColor="#ece4d8" />
<stop offset="1" stopColor="#cfc2b0" />
</linearGradient>
<linearGradient id={g('beam')} x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stopColor="#fff7e6" stopOpacity="0.7" />
<stop offset="1" stopColor="#fff7e6" stopOpacity="0" />
</linearGradient>
<linearGradient id={g('shade')} x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stopColor="#ffffff" stopOpacity="0.32" />
<stop offset="0.45" stopColor="#ffffff" stopOpacity="0" />
<stop offset="1" stopColor="#000000" stopOpacity="0.28" />
</linearGradient>
<linearGradient id={g('bench')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#a8774f" />
<stop offset="1" stopColor="#7c5236" />
</linearGradient>
</defs>
<rect width="1600" height="900" fill={u('wall')} />
<path d="M0 0 H520 L1180 900 H260 Z" fill={u('beam')} />
<rect x="1240" y="120" width="260" height="340" rx="4" fill="#d9cdbb" />
<path d="M1240 120 h260 v340 h-260 Z M1370 120 v340 M1240 290 h260" fill="none" stroke="#7c6a58" strokeWidth="8" />
<rect x="1180" y="560" width="300" height="12" rx="3" fill="#8a5d3d" />
<path d="M1220 560 v-60 h40 v60 Z M1290 560 c-10 -34, 4 -58, 24 -62 c20 4, 34 28, 24 62 Z M1380 560 v-30 h70 v30 Z" fill="#b9ab98" />
<rect x="120" y="160" width="140" height="12" rx="4" fill="#8a5d3d" />
<g stroke="#5c4a3a" strokeWidth="6" strokeLinecap="round">
<path d="M150 172 v118 M190 172 v148 M230 172 v98" />
</g>
<g fill="#3f342c">
<rect x="140" y="290" width="20" height="54" rx="6" />
<rect x="180" y="320" width="20" height="44" rx="6" />
<rect x="220" y="270" width="20" height="60" rx="6" />
</g>
<g opacity="0.22" fill="#3b2a1e">
{vessels.map((v) => (
<ellipse key={v.cx} cx={v.cx} cy="642" rx={v.rx} ry="10" />
))}
</g>
{vessels.map((v) => (
<g key={v.cx}>
<path d={v.d} fill={v.fill} />
<path d={v.d} fill={u('shade')} />
</g>
))}
<path d="M428 354 h76" stroke="#9c4c2b" strokeWidth="6" strokeLinecap="round" opacity="0.6" />
<path d="M836 380 c-6 -60, 8 -110, 40 -150 M836 380 c-24 -40, -56 -60, -90 -64" fill="none" stroke="#5c6d4f" strokeWidth="5" strokeLinecap="round" />
<ellipse cx="876" cy="226" rx="20" ry="10" fill="#7e9467" transform="rotate(-40 876 226)" />
<ellipse cx="744" cy="318" rx="20" ry="9" fill="#7e9467" transform="rotate(20 744 318)" />
<rect y="640" width="1600" height="40" fill={u('bench')} />
<rect y="680" width="1600" height="220" fill="#5d3e2a" />
<path d="M0 680 H1600" stroke="#3e2819" strokeWidth="4" />
<path d="M0 700 H1600" stroke="#7c5236" strokeWidth="2" opacity="0.5" />
</svg>
);
}
/**
* A colour portrait like a studio photo: soft backdrop, skin, hair and clothes in
* fixed natural colours, so a face looks the same in both themes and never grey.
* Seeds pick backdrop, skin tone, hair style and colour, clothes and glasses.
*/
export function PhotoPortrait({ seed = 0, className }: { seed?: number; className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const BACK = [['#f3d9c4', '#e7b998'], ['#d5e3ec', '#a9c3d6'], ['#e2e6d2', '#bccaa2'], ['#ecd6e0', '#d3a8bc'], ['#efe3c5', '#dcc28a'], ['#d9dbe9', '#aeb2d1']];
const SKIN = [['#f1c9a8', '#dca784'], ['#d9a27a', '#c2865d'], ['#a8714e', '#8e5b3c'], ['#f5d6bd', '#e3b596'], ['#7b4f35', '#653f29'], ['#e6b48f', '#cf9670']];
const HAIR = ['#2b2420', '#5a3a26', '#a0522d', '#d8b06a', '#1d1a19', '#8c8580'];
const CLOTH = ['#2f4a6b', '#c96f4a', '#3f6b57', '#ebe5da', '#4b3f6b', '#b8463f'];
const back = BACK[seed % BACK.length];
const [skin, shade] = SKIN[(seed * 5 + 1) % SKIN.length];
const hair = HAIR[(seed * 7 + 2) % HAIR.length];
const cloth = CLOTH[(seed * 5 + 1) % CLOTH.length];
const style = seed % 5;
const glasses = seed % 4 === 2;
return (
<div className={cn('relative overflow-hidden', className)} aria-hidden>
<svg viewBox="0 0 400 500" preserveAspectRatio="xMidYMid slice" className="absolute inset-0 size-full" data-clip-ok>
<defs>
<radialGradient id={`bg${id}`} cx="0.3" cy="0.25" r="0.9">
<stop offset="0" stopColor={back[0]} />
<stop offset="1" stopColor={back[1]} />
</radialGradient>
<linearGradient id={`lit${id}`} x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stopColor="#ffffff" stopOpacity="0.22" />
<stop offset="0.5" stopColor="#ffffff" stopOpacity="0" />
<stop offset="1" stopColor="#000000" stopOpacity="0.14" />
</linearGradient>
</defs>
<rect width="400" height="500" fill={`url(#bg${id})`} />
<circle cx="96" cy="110" r="120" fill="#ffffff" opacity="0.18" />
{/* hair behind the head */}
{style === 1 && <path d="M118 250 C 104 150, 156 104, 204 104 C 252 104, 300 150, 284 250 C 296 330, 306 390, 300 430 H102 C 96 390, 106 330, 118 250 Z" fill={hair} />}
{style === 3 && <path d="M126 236 C 118 156, 160 112, 204 112 C 250 112, 288 156, 278 236 L 290 340 H112 Z" fill={hair} />}
{/* shoulders and clothes */}
<path d="M30 500 C 36 418, 90 382, 160 370 L 240 370 C 310 382, 364 418, 370 500 Z" fill={cloth} />
<path d="M30 500 C 36 418, 90 382, 160 370 L 240 370 C 310 382, 364 418, 370 500 Z" fill={`url(#lit${id})`} />
{/* neck */}
<path d="M168 290 h64 v74 c0 26 -64 26 -64 0 Z" fill={shade} />
<path d="M160 372 C 176 400, 224 400, 240 372" fill="none" stroke={skin} strokeWidth="14" />
{/* ears and face */}
<ellipse cx="139" cy="246" rx="12" ry="19" fill={shade} />
<ellipse cx="261" cy="246" rx="12" ry="19" fill={shade} />
<ellipse cx="200" cy="232" rx="62" ry="78" fill={skin} />
<path d="M200 154 C 236 154, 262 190, 262 232 C 262 280, 236 310, 200 310 C 226 296, 244 270, 244 232 C 244 196, 228 168, 200 154 Z" fill={shade} opacity="0.35" />
{/* hair on top */}
{style === 0 && <path d="M136 226 C 128 168, 166 138, 204 138 C 248 138, 274 168, 264 226 C 254 192, 234 176, 206 176 C 178 176, 150 190, 136 226 Z" fill={hair} />}
{style === 1 && <path d="M138 232 C 136 176, 168 146, 204 146 C 240 146, 266 176, 264 232 C 248 194, 222 180, 196 182 C 172 184, 150 200, 138 232 Z" fill={hair} />}
{style === 2 && (
<>
<circle cx="200" cy="124" r="30" fill={hair} />
<path d="M138 222 C 138 174, 166 150, 200 150 C 238 150, 264 176, 262 222 C 246 190, 226 178, 200 178 C 172 178, 150 190, 138 222 Z" fill={hair} />
</>
)}
{style === 3 && <path d="M138 220 C 144 168, 176 146, 206 148 C 238 150, 260 168, 264 210 C 236 194, 206 168, 196 162 C 182 184, 162 204, 138 220 Z" fill={hair} />}
{style === 4 && [0, 1, 2, 3, 4, 5, 6, 7, 8].map((i) => <circle key={i} cx={round(200 + Math.cos((i / 8) * Math.PI) * 60)} cy={round(204 - Math.sin((i / 8) * Math.PI) * 52)} r="24" fill={hair} />)}
{/* face */}
<path d="M170 222 q8 -6 16 0 M214 222 q8 -6 16 0" fill="none" stroke={hair} strokeWidth="4" strokeLinecap="round" opacity="0.8" />
<circle cx="178" cy="242" r="4.2" fill="#2a211d" />
<circle cx="222" cy="242" r="4.2" fill="#2a211d" />
<path d="M198 252 c-3 12 -4 18 4 20" fill="none" stroke={shade} strokeWidth="3" strokeLinecap="round" />
<path d="M184 284 C 194 292, 208 292, 218 284" fill="none" stroke="#a5523f" strokeWidth="4" strokeLinecap="round" />
<ellipse cx="166" cy="268" rx="12" ry="7" fill="#e08a7a" opacity="0.22" />
<ellipse cx="234" cy="268" rx="12" ry="7" fill="#e08a7a" opacity="0.22" />
{glasses && <path d="M154 230 h40 v20 a9 9 0 0 1 -9 9 h-22 a9 9 0 0 1 -9 -9 Z M206 230 h40 v20 a9 9 0 0 1 -9 9 h-22 a9 9 0 0 1 -9 -9 Z M194 238 h12" fill="none" stroke="#2a211d" strokeWidth="4" strokeLinejoin="round" />}
</svg>
</div>
);
}
/* ------------------------------------------------------------------ */
/* Industry motifs: dishes, drinks, houses, rooms, studios, covers */
/* (menu, appointments, listings, courses, rooms, fitness) */
/* ------------------------------------------------------------------ */
export type DishKind = 'pasta' | 'salad' | 'steak' | 'soup' | 'dessert' | 'fish' | 'risotto' | 'bowl';
/** A dish from above on a table, with a napkin and cutlery. Same kind, same picture. */
export function PhotoDish({ dish = 'pasta', table = 0, className }: { dish?: DishKind; table?: number; className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const g = (n: string) => `${n}${id}`;
const u = (n: string) => `url(#${n}${id})`;
const TABLES = [
['#c99a6e', '#a8774c', '#8d6040'],
['#d9d4cc', '#bfb8ae', '#a39b90'],
['#4a4643', '#3a3634', '#2c2927'],
['#e7dccb', '#d4c4ab', '#bba987'],
];
const [t0, t1, t2] = TABLES[table % TABLES.length];
const cx = 400;
const cy = 300;
const food = (() => {
switch (dish) {
case 'pasta':
return (
<g>
{Array.from({ length: 22 }, (_, i) => {
const a = (i / 22) * Math.PI * 2;
const r = 60 + (i % 5) * 9;
const x1 = round(cx + Math.cos(a) * r);
const y1 = round(cy + Math.sin(a) * r * 0.9);
const x2 = round(cx + Math.cos(a + 2.3) * (r - 30));
const y2 = round(cy + Math.sin(a + 2.3) * (r - 30));
return <path key={i} d={`M${x1} ${y1} Q ${round(cx + Math.cos(a + 1.2) * 20)} ${round(cy + Math.sin(a + 1.2) * 20)} ${x2} ${y2}`} fill="none" stroke={i % 3 ? '#efc86a' : '#e2b453'} strokeWidth="11" strokeLinecap="round" />;
})}
{Array.from({ length: 14 }, (_, i) => {
const a = (i / 14) * Math.PI * 2 + 0.4;
return <path key={i} d={`M${round(cx + Math.cos(a) * 30)} ${round(cy + Math.sin(a) * 30)} q ${round(Math.cos(a + 1) * 40)} ${round(Math.sin(a + 1) * 40)} ${round(Math.cos(a) * 70)} ${round(Math.sin(a) * 60)}`} fill="none" stroke="#f6d98c" strokeWidth="7" strokeLinecap="round" opacity="0.9" />;
})}
<circle cx="372" cy="282" r="13" fill="#d8402e" />
<circle cx="430" cy="318" r="12" fill="#c8361f" />
<circle cx="408" cy="262" r="10" fill="#e0533a" />
{[[356, 318, -30], [448, 276, 40], [392, 336, 10]].map(([x, y, r], i) => (
<ellipse key={i} cx={x} cy={y} rx="16" ry="8" fill="#3f7a3a" transform={`rotate(${r} ${x} ${y})`} />
))}
{[[386, 300], [420, 290], [400, 320], [370, 300]].map(([x, y], i) => (
<rect key={i} x={x} y={y} width="12" height="4" rx="1" fill="#fbf3dc" transform={`rotate(${i * 40} ${x} ${y})`} />
))}
</g>
);
case 'salad':
return (
<g>
{Array.from({ length: 26 }, (_, i) => {
const a = r01(3, i) * Math.PI * 2;
const r = r01(5, i) * 92;
const x = round(cx + Math.cos(a) * r);
const y = round(cy + Math.sin(a) * r);
const greens = ['#5f9a3f', '#7db552', '#477d34', '#93c068', '#6b2f4a'];
return <ellipse key={i} cx={x} cy={y} rx={round(24 + r01(7, i) * 16)} ry={round(12 + r01(9, i) * 8)} fill={greens[i % greens.length]} transform={`rotate(${round(r01(11, i) * 180)} ${x} ${y})`} />;
})}
{[[350, 270], [440, 300], [390, 350], [420, 240]].map(([x, y], i) => (
<g key={i}>
<circle cx={x} cy={y} r="17" fill="#e1442f" />
<path d={`M${x - 10} ${y} h20`} stroke="#f39a7e" strokeWidth="3" />
</g>
))}
{[[380, 300], [455, 340], [330, 320], [410, 280]].map(([x, y], i) => (
<rect key={i} x={x} y={y} width="18" height="18" rx="3" fill="#f6efe0" transform={`rotate(${i * 22} ${x} ${y})`} />
))}
<circle cx="365" cy="240" r="7" fill="#2b2b2b" />
<circle cx="452" cy="262" r="7" fill="#2b2b2b" />
</g>
);
case 'steak':
return (
<g>
{[0, 1, 2, 3, 4].map((i) => (
<g key={i} transform={`translate(${330 + i * 30} ${250 + i * 6}) rotate(14)`}>
<rect width="34" height="110" rx="8" fill="#5a2e1d" />
<rect x="5" y="6" width="24" height="98" rx="6" fill="#c25a52" />
<rect x="9" y="12" width="16" height="86" rx="5" fill="#d9776c" />
</g>
))}
{[[300, 360], [330, 384], [470, 230], [496, 262]].map(([x, y], i) => (
<ellipse key={i} cx={x} cy={y} rx="26" ry="20" fill="#e3b45f" stroke="#c48f3b" strokeWidth="3" />
))}
{Array.from({ length: 8 }, (_, i) => (
<ellipse key={i} cx={round(470 + (i % 4) * 14)} cy={round(340 + Math.floor(i / 4) * 18)} rx="14" ry="7" fill={i % 2 ? '#4f8b3b' : '#6aa64a'} transform={`rotate(${i * 30} ${470 + (i % 4) * 14} ${340 + Math.floor(i / 4) * 18})`} />
))}
<circle cx="420" cy="232" r="4" fill="#f5efe2" />
<circle cx="380" cy="380" r="4" fill="#f5efe2" />
</g>
);
case 'soup':
return (
<g>
<circle cx={cx} cy={cy} r="104" fill="#f3efe8" />
<circle cx={cx} cy={cy} r="88" fill={u('soup')} />
<path d="M350 300 c 20 -30, 60 -30, 80 0 c 10 20, -20 40, -40 24 c -14 -12, 4 -26, 14 -16" fill="none" stroke="#fbf1de" strokeWidth="7" strokeLinecap="round" />
{[[430, 260], [370, 330], [440, 330], [360, 268]].map(([x, y], i) => (
<ellipse key={i} cx={x} cy={y} rx="6" ry="3" fill="#3f7a3a" transform={`rotate(${i * 45} ${x} ${y})`} />
))}
{[[392, 252], [414, 346], [342, 300]].map(([x, y], i) => (
<circle key={i} cx={x} cy={y} r="5" fill="#7a5a2c" />
))}
</g>
);
case 'dessert':
return (
<g>
<path d="M340 340 L 470 300 L 420 220 Z" fill="#c98b4a" />
<path d="M348 334 L 462 300 L 418 230 Z" fill="#f6e2b6" />
<path d="M348 334 L 462 300 L 456 290 L 352 322 Z" fill="#e7b46a" />
{[[380, 290], [420, 270], [430, 296], [400, 300], [440, 284]].map(([x, y], i) => (
<circle key={i} cx={x} cy={y} r="9" fill={i % 2 ? '#a3243b' : '#4a2d6b'} />
))}
<path d="M480 236 c -20 -10, -30 10, -16 20 c -10 14, 10 26, 22 16 c 14 6, 26 -14, 12 -24 c 6 -16, -14 -24, -18 -12 Z" fill="#fbf6ec" />
{[[300, 250], [312, 238], [520, 340], [508, 356], [296, 374]].map(([x, y], i) => (
<circle key={i} cx={x} cy={y} r="7" fill={i % 2 ? '#c2223c' : '#6a2c74'} />
))}
<path d="M300 270 c 60 -14, 140 -14, 200 6" fill="none" stroke="#7a3b1e" strokeWidth="4" strokeLinecap="round" opacity="0.6" />
<ellipse cx="350" cy="240" rx="9" ry="5" fill="#4f8b3b" transform="rotate(-30 350 240)" />
</g>
);
case 'fish':
return (
<g>
<path d="M320 300 c 30 -50, 140 -60, 180 -10 c -20 40, -140 60, -180 10 Z" fill="#f2c9a7" />
<path d="M330 300 c 40 -36, 130 -40, 160 -8" fill="none" stroke="#e6a77e" strokeWidth="6" strokeLinecap="round" />
<path d="M336 310 c 40 -20, 110 -24, 150 -4" fill="none" stroke="#fbe2cc" strokeWidth="5" strokeLinecap="round" />
<path d="M330 296 c 20 -40, 120 -50, 160 -14" fill="none" stroke="#b5642f" strokeWidth="3" opacity="0.5" />
<path d="M480 360 a 34 34 0 0 1 -60 -20 z" fill="#f4d64a" />
<path d="M480 360 a 28 28 0 0 1 -52 -18" fill="none" stroke="#fff6b0" strokeWidth="4" />
{Array.from({ length: 10 }, (_, i) => (
<ellipse key={i} cx={round(330 + (i % 5) * 18)} cy={round(352 + Math.floor(i / 5) * 16)} rx="13" ry="6" fill={i % 2 ? '#4f8b3b' : '#77ad55'} transform={`rotate(${i * 36} ${330 + (i % 5) * 18} ${352 + Math.floor(i / 5) * 16})`} />
))}
<circle cx="458" cy="246" r="5" fill="#2b2b2b" />
<circle cx="470" cy="256" r="4" fill="#2b2b2b" />
</g>
);
case 'risotto':
return (
<g>
<circle cx={cx} cy={cy} r="94" fill="#f1e2bd" />
{Array.from({ length: 60 }, (_, i) => {
const a = r01(2, i) * Math.PI * 2;
const r = r01(4, i) * 86;
const x = round(cx + Math.cos(a) * r);
const y = round(cy + Math.sin(a) * r);
return <ellipse key={i} cx={x} cy={y} rx="6" ry="3" fill={i % 3 ? '#fbf1d6' : '#e9d29c'} transform={`rotate(${round(r01(6, i) * 180)} ${x} ${y})`} />;
})}
{[[370, 280], [420, 320], [436, 266], [360, 330]].map(([x, y], i) => (
<g key={i} transform={`rotate(${i * 50} ${x} ${y})`}>
<path d={`M${x - 18} ${y} a 18 14 0 0 1 36 0 Z`} fill="#8b6643" />
<rect x={x - 5} y={y} width="10" height="14" rx="3" fill="#d8bf98" />
</g>
))}
{[[400, 290], [390, 310], [412, 302]].map(([x, y], i) => (
<rect key={i} x={x} y={y} width="22" height="5" rx="1" fill="#fffaf0" transform={`rotate(${i * 30 - 20} ${x} ${y})`} />
))}
<ellipse cx="380" cy="302" rx="8" ry="4" fill="#3f7a3a" />
</g>
);
case 'bowl':
default:
return (
<g>
<circle cx={cx} cy={cy} r="96" fill="#efe4c8" />
<path d={`M${cx} ${cy} L${cx} 204 A 96 96 0 0 1 483 252 Z`} fill="#e86a3e" />
<path d={`M${cx} ${cy} L483 252 A 96 96 0 0 1 470 365 Z`} fill="#6fae58" />
<path d={`M${cx} ${cy} L470 365 A 96 96 0 0 1 360 388 Z`} fill="#f2d35c" />
<path d={`M${cx} ${cy} L360 388 A 96 96 0 0 1 305 290 Z`} fill="#7c3c63" />
<path d={`M${cx} ${cy} L305 290 A 96 96 0 0 1 ${cx} 204 Z`} fill="#c9a77a" />
<circle cx={cx} cy={cy} r="30" fill="#f6efe2" />
<circle cx={cx} cy={cy} r="18" fill="#efa63a" />
{[[440, 236], [452, 250], [430, 248]].map(([x, y], i) => (
<circle key={i} cx={x} cy={y} r="4" fill="#f6efe2" />
))}
</g>
);
}
})();
const plateColor = dish === 'soup' ? null : table === 2 ? '#2e2c2b' : '#fbfaf7';
return (
<svg viewBox="150 70 500 460" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden data-clip-ok>
<defs>
<linearGradient id={g('table')} x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stopColor={t0} />
<stop offset="1" stopColor={t1} />
</linearGradient>
<radialGradient id={g('light')} cx="0.3" cy="0.2" r="0.9">
<stop offset="0" stopColor="#ffffff" stopOpacity="0.22" />
<stop offset="1" stopColor="#000000" stopOpacity="0.18" />
</radialGradient>
<radialGradient id={g('shadow')}>
<stop offset="0.7" stopColor="#000000" stopOpacity="0.28" />
<stop offset="1" stopColor="#000000" stopOpacity="0" />
</radialGradient>
<radialGradient id={g('plate')} cx="0.4" cy="0.35" r="0.7">
<stop offset="0" stopColor="#ffffff" stopOpacity="0.5" />
<stop offset="1" stopColor="#000000" stopOpacity="0.08" />
</radialGradient>
<radialGradient id={g('soup')} cx="0.45" cy="0.4" r="0.7">
<stop offset="0" stopColor="#f0a24a" />
<stop offset="1" stopColor="#c8642a" />
</radialGradient>
</defs>
<rect width="800" height="600" fill={u('table')} />
{table % 2 === 0
? Array.from({ length: 9 }, (_, i) => <path key={i} d={`M-10 ${40 + i * 70} C 200 ${30 + i * 70}, 500 ${60 + i * 70}, 810 ${44 + i * 70}`} fill="none" stroke={t2} strokeWidth="2" opacity="0.35" />)
: Array.from({ length: 30 }, (_, i) => <circle key={i} cx={round(r01(8, i) * 800)} cy={round(r01(9, i) * 600)} r={round(2 + r01(10, i) * 6)} fill={t2} opacity="0.25" />)}
<path d="M560 80 L 760 60 L 790 300 L 590 330 Z" fill="#f4efe6" />
<path d="M560 80 L 760 60 L 790 300 L 590 330 Z" fill="none" stroke="#e2d9c9" strokeWidth="3" />
<path d="M572 112 L 766 92" stroke="#e2d9c9" strokeWidth="2" />
<rect x="622" y="120" width="10" height="190" rx="5" fill="#b9b5ae" transform="rotate(-6 627 215)" />
<path d="M668 116 h16 v90 h-16 Z" fill="#c7c3bc" transform="rotate(-6 676 160)" />
<rect x="672" y="200" width="8" height="110" rx="4" fill="#b9b5ae" transform="rotate(-6 676 255)" />
<ellipse cx={cx + 14} cy={cy + 20} rx="170" ry="164" fill={u('shadow')} />
{plateColor && (
<>
<circle cx={cx} cy={cy} r="150" fill={plateColor} />
<circle cx={cx} cy={cy} r="118" fill="none" stroke={table === 2 ? '#3a3735' : '#ece8e1'} strokeWidth="3" />
<circle cx={cx} cy={cy} r="150" fill={u('plate')} />
</>
)}
{!plateColor && (
<>
<circle cx={cx} cy={cy} r="150" fill="#2e4a63" />
<circle cx={cx} cy={cy} r="150" fill={u('plate')} />
</>
)}
{food}
<path d="M120 470 a 60 60 0 1 0 0.1 0 Z" fill="#f4efe6" opacity="0.92" />
<circle cx="120" cy="530" r="40" fill={dish === 'dessert' ? '#6b3b22' : '#d8c9a8'} />
<circle cx="120" cy="530" r="40" fill={u('plate')} />
<circle cx="96" cy="120" r="8" fill="#3f7a3a" opacity="0.8" />
<ellipse cx="116" cy="108" rx="16" ry="8" fill="#4f8b3b" transform="rotate(-30 116 108)" opacity="0.85" />
<rect width="800" height="600" fill={u('light')} />
</svg>
);
}
export type DrinkKind = 'wine' | 'red' | 'cocktail' | 'beer' | 'spritz' | 'coffee' | 'lemonade';
/** A drink on a bar counter at night, warm lights out of focus behind it */
export function PhotoDrink({ kind = 'wine', className }: { kind?: DrinkKind; className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const g = (n: string) => `${n}${id}`;
const u = (n: string) => `url(#${n}${id})`;
const LIQ: Record<DrinkKind, [string, string]> = {
wine: ['#f3e3a0', '#d9c06a'],
red: ['#8e1f33', '#4f0d1c'],
cocktail: ['#e88a3a', '#b0471d'],
beer: ['#f2b53c', '#c9801c'],
spritz: ['#ff7a3d', '#e04e1f'],
coffee: ['#7a4a2a', '#4a2a16'],
lemonade: ['#f7f1b0', '#e6dc78'],
};
const [l0, l1] = LIQ[kind];
const bokeh = [[90, 120, 40, '#ffb35c'], [210, 70, 26, '#ffd28a'], [640, 110, 46, '#ff9a4d'], [720, 220, 30, '#ffd28a'], [520, 60, 22, '#ffe2a8'], [150, 260, 30, '#ff8f52'], [700, 40, 18, '#ffe2a8'], [40, 340, 22, '#ffd28a'], [600, 300, 26, '#ffb35c']] as const;
const glass = (() => {
switch (kind) {
case 'wine':
case 'red':
return (
<g>
<path d="M330 150 C 320 260, 340 320, 400 330 C 460 320, 480 260, 470 150 Z" fill="#ffffff" opacity="0.16" />
<path d="M334 230 C 336 290, 356 322, 400 328 C 444 322, 464 290, 466 230 Z" fill={u('liq')} />
<ellipse cx="400" cy="230" rx="66" ry="8" fill={l0} opacity="0.85" />
<rect x="396" y="328" width="8" height="140" fill="#ffffff" opacity="0.35" />
<ellipse cx="400" cy="470" rx="60" ry="10" fill="#ffffff" opacity="0.3" />
<path d="M344 170 C 340 230, 348 280, 372 306" fill="none" stroke="#ffffff" strokeWidth="5" strokeLinecap="round" opacity="0.5" />
</g>
);
case 'beer':
return (
<g>
<path d="M330 140 L 344 470 H 456 L 470 140 Z" fill={u('liq')} />
<path d="M326 100 L 330 160 H 470 L 474 100 Z" fill="#fbf4e4" />
<ellipse cx="400" cy="100" rx="74" ry="12" fill="#fffaf0" />
{Array.from({ length: 16 }, (_, i) => <circle key={i} cx={round(350 + r01(4, i) * 100)} cy={round(190 + r01(5, i) * 260)} r={round(1.5 + r01(6, i) * 2.5)} fill="#fff4c4" opacity="0.7" />)}
<path d="M344 170 L 354 450" stroke="#ffffff" strokeWidth="6" strokeLinecap="round" opacity="0.35" />
</g>
);
case 'coffee':
return (
<g>
<ellipse cx="400" cy="440" rx="140" ry="26" fill="#efe8dd" />
<path d="M310 300 h180 c 0 90, -30 140, -90 140 c -60 0, -90 -50, -90 -140 Z" fill="#f7f2ea" />
<path d="M490 330 c 50 0, 50 70, 0 80" fill="none" stroke="#f7f2ea" strokeWidth="14" />
<ellipse cx="400" cy="300" rx="90" ry="18" fill="#e7dfd2" />
<ellipse cx="400" cy="302" rx="80" ry="14" fill={l1} />
<path d="M380 300 c 10 -8, 30 -8, 40 0 c -10 8, -30 8, -40 0 Z" fill="#f4e3c6" />
<path d="M376 250 c -10 -20, 10 -30, 0 -50 M 408 250 c -10 -20, 10 -30, 0 -50 M 438 250 c -10 -20, 10 -30, 0 -50" fill="none" stroke="#ffffff" strokeWidth="4" strokeLinecap="round" opacity="0.35" />
</g>
);
default:
return (
<g>
<path d="M326 180 L 340 460 H 460 L 474 180 Z" fill="#ffffff" opacity="0.16" />
<path d="M330 230 L 341 456 H 459 L 470 230 Z" fill={u('liq')} />
{[[350, 250], [400, 270], [430, 240], [370, 300]].map(([x, y], i) => (
<rect key={i} x={x} y={y} width="40" height="40" rx="6" fill="#ffffff" opacity="0.28" transform={`rotate(${i * 17} ${x + 20} ${y + 20})`} />
))}
{kind === 'lemonade' || kind === 'cocktail' ? <circle cx="458" cy="196" r="34" fill="#f4d64a" stroke="#fff6b0" strokeWidth="4" /> : <circle cx="458" cy="200" r="30" fill="#ff9a4d" stroke="#ffd28a" strokeWidth="4" />}
{kind !== 'cocktail' && <rect x="420" y="100" width="8" height="200" rx="4" fill="#ffffff" opacity="0.7" transform="rotate(12 424 200)" />}
<path d="M344 200 L 354 440" stroke="#ffffff" strokeWidth="6" strokeLinecap="round" opacity="0.35" />
{kind === 'cocktail' && <ellipse cx="380" cy="226" rx="18" ry="7" fill="#3f7a3a" transform="rotate(-20 380 226)" />}
</g>
);
}
})();
return (
<svg viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden data-clip-ok>
<defs>
<linearGradient id={g('bg')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#1f1712" />
<stop offset="1" stopColor="#3a2619" />
</linearGradient>
<linearGradient id={g('bar')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#6b4329" />
<stop offset="1" stopColor="#2a1a10" />
</linearGradient>
<linearGradient id={g('liq')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor={l0} />
<stop offset="1" stopColor={l1} />
</linearGradient>
<radialGradient id={g('bokeh')}>
<stop offset="0" stopColor="#ffffff" stopOpacity="0.9" />
<stop offset="1" stopColor="#ffffff" stopOpacity="0" />
</radialGradient>
</defs>
<rect width="800" height="600" fill={u('bg')} />
{bokeh.map(([x, y, r, c], i) => (
<g key={i} opacity={round(0.35 + (i % 3) * 0.15)}>
<circle cx={x} cy={y} r={r} fill={c} />
<circle cx={x} cy={y} r={r} fill={u('bokeh')} opacity="0.4" />
</g>
))}
<rect y="440" width="800" height="160" fill={u('bar')} />
<rect y="436" width="800" height="8" fill="#8a5a38" />
<ellipse cx="400" cy="474" rx="110" ry="12" fill="#000000" opacity="0.35" />
{glass}
<rect x="560" y="300" width="56" height="150" rx="10" fill="#2b4a3a" opacity="0.85" />
<rect x="574" y="250" width="28" height="60" rx="6" fill="#2b4a3a" opacity="0.85" />
<rect x="566" y="350" width="44" height="50" rx="3" fill="#e9dcc0" opacity="0.8" />
</svg>
);
}
export type HouseKind = 'villa' | 'apartment' | 'townhouse' | 'cottage' | 'loft';
/** A property seen from outside in late daylight: sky, building, trees and lawn */
export function PhotoHouse({ kind = 'villa', className }: { kind?: HouseKind; className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const g = (n: string) => `${n}${id}`;
const u = (n: string) => `url(#${n}${id})`;
const win = (x: number, y: number, w: number, h: number, lit = false, key?: string | number) => (
<g key={key}>
<rect x={x} y={y} width={w} height={h} fill={lit ? '#ffd58a' : u('glass')} />
<path d={`M${x} ${y + h} L${x + w * 0.6} ${y}`} stroke="#ffffff" strokeWidth={round(w * 0.12)} opacity="0.18" />
<rect x={x} y={y} width={w} height={h} fill="none" stroke="#2c2a28" strokeWidth="3" />
</g>
);
const building = (() => {
switch (kind) {
case 'apartment':
return (
<g>
<rect x="200" y="90" width="400" height="390" fill="#e8e1d6" />
<rect x="200" y="90" width="400" height="16" fill="#cfc6b8" />
{[0, 1, 2, 3, 4].map((r) =>
[0, 1, 2, 3].map((c) => (
<g key={`${r}-${c}`}>
{win(228 + c * 92, 124 + r * 70, 56, 46, (r + c) % 5 === 1)}
<rect x={222 + c * 92} y={172 + r * 70} width="68" height="6" fill="#5b5752" />
<path d={`M${222 + c * 92} ${172 + r * 70} v-14 h68 v14`} fill="none" stroke="#5b5752" strokeWidth="2" />
</g>
)),
)}
<rect x="370" y="430" width="60" height="50" fill="#3a3632" />
</g>
);
case 'townhouse':
return (
<g>
{[0, 1, 2].map((i) => {
const x = 150 + i * 170;
const c = ['#b8664a', '#e4d7c2', '#6f7d86'][i];
return (
<g key={i}>
<rect x={x} y="140" width="170" height="340" fill={c} />
<path d={`M${x - 6} 146 L${x + 85} 70 L${x + 176} 146 Z`} fill="#3d3836" />
{win(x + 24, 180, 46, 64, i === 1)}
{win(x + 100, 180, 46, 64)}
{win(x + 24, 280, 46, 64)}
{win(x + 100, 280, 46, 64, i === 2)}
<rect x={x + 60} y="390" width="50" height="90" fill="#2c2a28" />
<rect x={x + 64} y="394" width="42" height="40" fill={u('glass')} />
</g>
);
})}
</g>
);
case 'cottage':
return (
<g>
<rect x="230" y="250" width="340" height="230" fill="#efe6d4" />
<path d="M200 262 L 400 110 L 600 262 Z" fill="#7d4a33" />
<path d="M200 262 L 400 110 L 600 262" fill="none" stroke="#5a3424" strokeWidth="8" />
<rect x="460" y="120" width="40" height="80" fill="#8a6a58" />
{win(262, 300, 70, 80, true)}
{win(468, 300, 70, 80)}
{win(372, 180, 56, 50)}
<rect x="366" y="370" width="68" height="110" fill="#3d5a4a" />
<circle cx="424" cy="428" r="4" fill="#e4c47a" />
</g>
);
case 'loft':
return (
<g>
<rect x="140" y="120" width="520" height="360" fill="#a8553a" />
{Array.from({ length: 18 }, (_, i) => <path key={i} d={`M140 ${130 + i * 20} H660`} stroke="#8e442d" strokeWidth="2" opacity="0.6" />)}
{[0, 1, 2, 3].map((c) => (
<g key={c}>
{win(172 + c * 124, 150, 84, 120, c === 2)}
<path d={`M${172 + c * 124} 210 h84 M${214 + c * 124} 150 v120`} stroke="#2c2a28" strokeWidth="3" />
{win(172 + c * 124, 300, 84, 120, c === 0)}
<path d={`M${172 + c * 124} 360 h84 M${214 + c * 124} 300 v120`} stroke="#2c2a28" strokeWidth="3" />
</g>
))}
<rect x="140" y="440" width="520" height="40" fill="#3a2f2a" />
</g>
);
case 'villa':
default:
return (
<g>
<rect x="150" y="250" width="500" height="230" fill="#f4f1ec" />
<rect x="300" y="140" width="320" height="120" fill="#f4f1ec" />
<rect x="290" y="130" width="340" height="14" fill="#2e2c2a" />
<rect x="140" y="240" width="520" height="14" fill="#2e2c2a" />
<rect x="320" y="160" width="280" height="80" fill={u('glass')} />
<path d="M390 160 v80 M460 160 v80 M530 160 v80" stroke="#2e2c2a" strokeWidth="3" />
<rect x="170" y="290" width="250" height="170" fill={u('glass')} />
<rect x="200" y="320" width="80" height="140" fill="#ffd58a" opacity="0.75" />
<path d="M233 290 v170 M296 290 v170 M358 290 v170" stroke="#2e2c2a" strokeWidth="3" />
<rect x="450" y="290" width="180" height="190" fill="#9a7a5e" />
{Array.from({ length: 12 }, (_, i) => <path key={i} d={`M${456 + i * 15} 290 V480`} stroke="#86684e" strokeWidth="3" />)}
<rect x="140" y="470" width="520" height="12" fill="#cfc9c0" />
</g>
);
}
})();
return (
<svg viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden data-clip-ok>
<defs>
<linearGradient id={g('sky')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#8fb6d9" />
<stop offset="0.7" stopColor="#d9e6ef" />
<stop offset="1" stopColor="#f6e6cf" />
</linearGradient>
<linearGradient id={g('glass')} x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stopColor="#a9c4d8" />
<stop offset="0.5" stopColor="#5f7f97" />
<stop offset="1" stopColor="#38505f" />
</linearGradient>
<linearGradient id={g('lawn')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#87a86a" />
<stop offset="1" stopColor="#5d7f48" />
</linearGradient>
<linearGradient id={g('sun')} x1="1" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#fff2d6" stopOpacity="0.45" />
<stop offset="1" stopColor="#fff2d6" stopOpacity="0" />
</linearGradient>
</defs>
<rect width="800" height="600" fill={u('sky')} />
<g fill="#ffffff" opacity="0.7">
<ellipse cx="160" cy="90" rx="70" ry="16" />
<ellipse cx="210" cy="78" rx="50" ry="18" />
<ellipse cx="640" cy="60" rx="80" ry="14" />
</g>
<path d="M0 420 C 120 380, 240 400, 400 390 S 680 380, 800 410 V600 H0 Z" fill="#7d9a68" opacity="0.6" />
{building}
<rect y="478" width="800" height="122" fill={u('lawn')} />
<path d="M330 600 L 380 480 H 420 L 470 600 Z" fill="#d9d2c4" opacity="0.85" />
{[[70, 470, 90], [700, 470, 110], [620, 480, 60]].map(([x, b, r], i) => (
<g key={i}>
<rect x={x - 6} y={b - 40} width="12" height="40" fill="#5a4030" />
<circle cx={x} cy={b - r * 0.8} r={r * 0.6} fill="#46693a" />
<circle cx={x - r * 0.3} cy={b - r * 0.6} r={r * 0.45} fill="#557d45" />
<circle cx={x + r * 0.25} cy={b - r} r={r * 0.4} fill="#6a9254" />
</g>
))}
{[[160, 490], [240, 494], [560, 492]].map(([x, y], i) => (
<ellipse key={i} cx={x} cy={y} rx="40" ry="16" fill="#4f7340" />
))}
<rect width="800" height="600" fill={u('sun')} />
</svg>
);
}
export type InteriorKind = 'living' | 'bedroom' | 'kitchen' | 'bath' | 'suite';
export type InteriorView = 'garden' | 'sea' | 'city' | 'mountains';
/** An interior in daylight, with a window to a view. Used for rooms, flats and stays. */
export function PhotoInterior({ kind = 'living', view = 'garden', className }: { kind?: InteriorKind; view?: InteriorView; className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const g = (n: string) => `${n}${id}`;
const u = (n: string) => `url(#${n}${id})`;
const outside = (() => {
switch (view) {
case 'sea':
return (
<g>
<rect x="420" y="80" width="300" height="140" fill="#bcd9ea" />
<rect x="420" y="200" width="300" height="140" fill="#3f7fa6" />
<path d="M420 200 h300" stroke="#ffffff" strokeWidth="3" opacity="0.6" />
{[230, 260, 300].map((y, i) => <path key={i} d={`M${440 + i * 30} ${y} h${80 - i * 10}`} stroke="#ffffff" strokeWidth="2" opacity="0.4" />)}
</g>
);
case 'city':
return (
<g>
<rect x="420" y="80" width="300" height="260" fill="#cfdde8" />
{[[430, 180, 50], [490, 130, 40], [540, 200, 60], [610, 150, 44], [660, 210, 60]].map(([x, y, w], i) => (
<rect key={i} x={x} y={y} width={w} height={340 - y} fill={i % 2 ? '#8c9dad' : '#a7b5c2'} />
))}
</g>
);
case 'mountains':
return (
<g>
<rect x="420" y="80" width="300" height="260" fill="#d6e5ef" />
<path d="M420 260 L 500 150 L 560 220 L 630 120 L 720 240 V340 H420 Z" fill="#8aa0b4" />
<path d="M610 148 L 630 120 L 650 150 Z M488 166 L500 150 L512 166 Z" fill="#ffffff" />
<path d="M420 290 C 520 260, 600 300, 720 280 V340 H420 Z" fill="#6f8f5c" />
</g>
);
case 'garden':
default:
return (
<g>
<rect x="420" y="80" width="300" height="260" fill="#dbe9d4" />
<circle cx="480" cy="250" r="70" fill="#7fa868" />
<circle cx="600" cy="230" r="90" fill="#6a9656" />
<circle cx="690" cy="270" r="60" fill="#8db676" />
</g>
);
}
})();
const furniture = (() => {
switch (kind) {
case 'bedroom':
case 'suite':
return (
<g>
<rect x="60" y="250" width="320" height="90" rx="6" fill="#7b6a5c" />
<rect x="40" y="330" width="380" height="110" rx="10" fill="#f7f4ef" />
<rect x="40" y="400" width="380" height="60" rx="6" fill="#d9cfc2" />
<rect x="70" y="300" width="130" height="50" rx="14" fill="#ffffff" />
<rect x="220" y="300" width="130" height="50" rx="14" fill="#ffffff" />
<path d="M40 380 h380 v40 h-380 Z" fill={kind === 'suite' ? '#5a6f8a' : '#b98f6a'} opacity="0.85" />
<rect x="436" y="380" width="70" height="80" rx="4" fill="#9b7a5e" />
<path d="M470 380 v-50 M450 330 h40 l-8 -26 h-24 Z" fill="#efe3c6" stroke="#5a4636" strokeWidth="3" />
{kind === 'suite' && <rect x="560" y="390" width="160" height="70" rx="20" fill="#c7b9a5" />}
</g>
);
case 'kitchen':
return (
<g>
<rect x="0" y="330" width="800" height="130" fill="#e7e1d8" />
<rect x="0" y="322" width="800" height="12" fill="#bdb4a8" />
{[0, 1, 2, 3, 4, 5, 6].map((i) => <rect key={i} x={i * 116 + 8} y="350" width="104" height="104" rx="4" fill="#d9d1c5" stroke="#cbc2b4" />)}
<rect x="40" y="70" width="300" height="110" fill="#ece6dc" />
<path d="M140 70 v110 M240 70 v110" stroke="#cbc2b4" strokeWidth="3" />
<rect x="140" y="270" width="40" height="52" rx="6" fill="#4f6b5a" />
<circle cx="260" cy="300" r="22" fill="#e9b04a" />
<circle cx="290" cy="306" r="16" fill="#d55a3a" />
<path d="M560 320 v-70 h50 v70" fill="none" stroke="#9b9187" strokeWidth="4" />
</g>
);
case 'bath':
return (
<g>
<rect x="0" y="0" width="400" height="600" fill="#e6e2dc" />
{Array.from({ length: 10 }, (_, i) => <path key={i} d={`M0 ${i * 60} H400`} stroke="#d6d0c7" strokeWidth="2" />)}
<rect x="40" y="350" width="340" height="110" rx="50" fill="#ffffff" />
<rect x="40" y="350" width="340" height="30" rx="15" fill="#f3f1ee" />
<path d="M340 350 v-60 h-26" fill="none" stroke="#b8b2aa" strokeWidth="6" strokeLinecap="round" />
<rect x="480" y="300" width="160" height="160" rx="6" fill="#9b7a5e" />
<ellipse cx="560" cy="300" rx="70" ry="14" fill="#ffffff" />
<rect x="500" y="120" width="120" height="150" rx="60" fill="#c8d6df" stroke="#a89e92" strokeWidth="6" />
</g>
);
case 'living':
default:
return (
<g>
<rect x="40" y="320" width="380" height="80" rx="18" fill="#8c9a86" />
<rect x="60" y="280" width="340" height="70" rx="16" fill="#9eac97" />
<rect x="30" y="300" width="50" height="110" rx="14" fill="#7d8b77" />
<rect x="380" y="300" width="50" height="110" rx="14" fill="#7d8b77" />
<rect x="120" y="296" width="60" height="44" rx="10" fill="#e6c79a" />
<rect x="460" y="400" width="200" height="16" rx="6" fill="#8a6a50" />
<path d="M480 416 v40 M640 416 v40" stroke="#6b513c" strokeWidth="6" />
<circle cx="530" cy="392" r="12" fill="#e8e1d6" />
<rect x="574" y="380" width="40" height="20" rx="3" fill="#c96f4a" />
<path d="M760 460 v-160 M760 300 l-30 -16 h60 Z" fill="#2e2c2a" stroke="#2e2c2a" strokeWidth="3" />
</g>
);
}
})();
return (
<svg viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden data-clip-ok>
<defs>
<linearGradient id={g('wall')} x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stopColor="#ece6dc" />
<stop offset="1" stopColor="#f8f5f0" />
</linearGradient>
<linearGradient id={g('floor')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#c9a983" />
<stop offset="1" stopColor="#a8845e" />
</linearGradient>
<linearGradient id={g('beam')} x1="1" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#fff7e6" stopOpacity="0.6" />
<stop offset="1" stopColor="#fff7e6" stopOpacity="0" />
</linearGradient>
</defs>
<rect width="800" height="600" fill={u('wall')} />
{outside}
<path d="M420 80 h300 v260 h-300 Z M570 80 v260 M420 210 h300" fill="none" stroke="#3a3632" strokeWidth="8" />
<rect x="400" y="60" width="16" height="300" fill="#d9cdb9" />
<rect x="724" y="60" width="16" height="300" fill="#d9cdb9" />
<rect y="460" width="800" height="140" fill={u('floor')} />
{Array.from({ length: 6 }, (_, i) => <path key={i} d={`M${i * 160 - 40} 600 L ${i * 140 + 40} 460`} stroke="#9a7752" strokeWidth="2" opacity="0.5" />)}
<path d="M420 340 L 300 600 H 620 L 720 340 Z" fill={u('beam')} />
<rect x="120" y="140" width="160" height="100" fill="#d9c8ae" stroke="#2e2c2a" strokeWidth="4" />
<path d="M130 230 L 180 180 L 210 210 L 240 170 L 270 230 Z" fill="#a37a55" />
{furniture}
<g transform="translate(700 400)">
<path d="M-18 60 h36 l-6 -40 h-24 Z" fill="#c7a684" />
<path d="M0 20 C -20 -10, -30 -40, -10 -60 M0 20 C 10 -20, 30 -30, 34 -56 M0 20 C 0 -10, 4 -40, 0 -70" fill="none" stroke="#4e7a55" strokeWidth="6" strokeLinecap="round" />
</g>
</svg>
);
}
export type StudioKind = 'yoga' | 'strength' | 'cycle' | 'boxing' | 'pilates';
/** A training studio with equipment, warm floor and strip lights */
export function PhotoStudio({ kind = 'yoga', className }: { kind?: StudioKind; className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const g = (n: string) => `${n}${id}`;
const u = (n: string) => `url(#${n}${id})`;
const dark = kind === 'strength' || kind === 'boxing' || kind === 'cycle';
const gear = (() => {
switch (kind) {
case 'strength':
return (
<g>
<rect x="120" y="360" width="560" height="10" rx="5" fill="#b9bcc0" />
{[140, 170, 630, 660].map((x, i) => <rect key={i} x={x - 10} y={i % 3 === 0 ? 290 : 300} width="22" height={i % 3 === 0 ? 150 : 130} rx="6" fill="#1b1c1e" stroke="#3a3c40" strokeWidth="3" />)}
<rect x="200" y="430" width="400" height="20" rx="4" fill="#2a2b2e" />
{[0, 1, 2, 3, 4, 5].map((i) => (
<g key={i}>
<rect x={80 + i * 26} y="470" width="18" height="40" rx="4" fill="#26272a" />
<rect x={600 + i * 26} y="470" width="18" height="40" rx="4" fill="#26272a" />
</g>
))}
<path d="M80 466 h160 M600 466 h160" stroke="#d4d6d9" strokeWidth="4" />
</g>
);
case 'cycle':
return (
<g>
{[0, 1, 2, 3].map((i) => {
const x = 120 + i * 170;
const s = 1 - i * 0.04;
return (
<g key={i} transform={`translate(${x} 330) scale(${round(s)})`}>
<circle cx="40" cy="150" r="44" fill="none" stroke="#2b2c2f" strokeWidth="10" />
<circle cx="40" cy="150" r="14" fill="#3b3d42" />
<path d="M40 150 L 70 40 L 110 40 M70 40 L 100 150 M60 20 h30" fill="none" stroke="#1d1e21" strokeWidth="10" strokeLinecap="round" />
<path d="M0 194 h120" stroke="#1d1e21" strokeWidth="10" strokeLinecap="round" />
<rect x="96" y="36" width="34" height="8" rx="4" fill="#ff5a3c" />
</g>
);
})}
</g>
);
case 'boxing':
return (
<g>
{[220, 400, 580].map((x, i) => (
<g key={i}>
<path d={`M${x} 0 V120`} stroke="#3a3c40" strokeWidth="4" />
<rect x={x - 38} y="120" width="76" height="230" rx="34" fill={['#8d2a22', '#1f2023', '#8d2a22'][i]} />
<rect x={x - 38} y="170" width="76" height="12" fill="#000000" opacity="0.2" />
<rect x={x - 38} y="290" width="76" height="12" fill="#000000" opacity="0.2" />
</g>
))}
<ellipse cx="320" cy="470" rx="34" ry="20" fill="#c62f25" />
<ellipse cx="360" cy="478" rx="34" ry="20" fill="#a82319" />
</g>
);
case 'pilates':
return (
<g>
{[0, 1].map((i) => (
<g key={i} transform={`translate(${110 + i * 320} 380)`}>
<rect width="260" height="40" rx="8" fill="#e7ded0" />
<rect y="40" width="260" height="20" fill="#a98c6c" />
<rect x="20" y="-10" width="70" height="14" rx="7" fill="#3f3a35" />
<path d="M240 0 v-80 M230 -80 h20" stroke="#8c7b69" strokeWidth="8" strokeLinecap="round" />
<path d="M10 60 v30 M250 60 v30" stroke="#6b5a48" strokeWidth="8" />
</g>
))}
</g>
);
case 'yoga':
default:
return (
<g>
{[0, 1, 2, 3].map((i) => (
<g key={i}>
<path d={`M${110 + i * 160} 520 L ${170 + i * 150} 420 h 70 L ${200 + i * 160} 520 Z`} fill={['#8fa58a', '#d1a07a', '#9aa7c2', '#c99a9a'][i]} />
</g>
))}
{[0, 1, 2].map((i) => <rect key={i} x={620 + i * 40} y="380" width="30" height="50" rx="6" fill="#c8b59a" />)}
<circle cx="120" cy="400" r="30" fill="#cfd8c6" />
</g>
);
}
})();
return (
<svg viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden data-clip-ok>
<defs>
<linearGradient id={g('wall')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor={dark ? '#2a2b2f' : '#efe8dd'} />
<stop offset="1" stopColor={dark ? '#3a3b40' : '#e1d5c3'} />
</linearGradient>
<linearGradient id={g('floor')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor={dark ? '#1c1d20' : '#c9a882'} />
<stop offset="1" stopColor={dark ? '#0f1012' : '#a7845e'} />
</linearGradient>
<linearGradient id={g('light')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor={dark ? '#ffd9a8' : '#fff6e2'} stopOpacity={dark ? '0.18' : '0.5'} />
<stop offset="1" stopColor="#ffffff" stopOpacity="0" />
</linearGradient>
</defs>
<rect width="800" height="600" fill={u('wall')} />
{dark
? [120, 300, 480, 660].map((x, i) => <rect key={i} x={x} y="40" width="40" height="6" rx="3" fill="#fff1d6" opacity="0.9" />)
: [
<rect key="w1" x="80" y="70" width="180" height="220" fill="#dce6d6" stroke="#bfae96" strokeWidth="8" />,
<rect key="w2" x="310" y="70" width="180" height="220" fill="#dce6d6" stroke="#bfae96" strokeWidth="8" />,
<rect key="w3" x="540" y="70" width="180" height="220" fill="#dce6d6" stroke="#bfae96" strokeWidth="8" />,
<circle key="t1" cx="180" cy="250" r="60" fill="#9dbb88" />,
<circle key="t2" cx="620" cy="240" r="70" fill="#86a874" />,
]}
{dark && <rect x="60" y="120" width="680" height="200" rx="6" fill="#ffffff" opacity="0.04" />}
<rect y="400" width="800" height="200" fill={u('floor')} />
<path d="M0 400 h800" stroke={dark ? '#4a4b50' : '#b8a184'} strokeWidth="3" />
<rect width="800" height="600" fill={u('light')} />
{gear}
</svg>
);
}
export type TreatmentKind = 'salon' | 'practice' | 'spa';
/** A treatment room: a salon chair and mirror, a calm practice or a spa with towels and stones */
export function PhotoTreatment({ kind = 'salon', className }: { kind?: TreatmentKind; className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const g = (n: string) => `${n}${id}`;
const u = (n: string) => `url(#${n}${id})`;
const WALL: Record<TreatmentKind, [string, string]> = { salon: ['#e9dccd', '#d7c2ab'], practice: ['#e6ecef', '#cfdbe1'], spa: ['#d9cdbd', '#b9a68f'] };
const [w0, w1] = WALL[kind];
return (
<svg viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden data-clip-ok>
<defs>
<linearGradient id={g('wall')} x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stopColor={w0} />
<stop offset="1" stopColor={w1} />
</linearGradient>
<radialGradient id={g('lamp')}>
<stop offset="0" stopColor="#fff1cc" stopOpacity="0.8" />
<stop offset="1" stopColor="#fff1cc" stopOpacity="0" />
</radialGradient>
</defs>
<rect width="800" height="600" fill={u('wall')} />
<rect y="470" width="800" height="130" fill={kind === 'practice' ? '#b9c3c7' : '#9c7b5d'} />
{kind === 'salon' && (
<g>
<rect x="250" y="70" width="300" height="300" rx="150" fill="#c6d3d8" stroke="#b08d57" strokeWidth="10" />
<path d="M290 300 C 340 200, 420 180, 500 140" stroke="#ffffff" strokeWidth="14" opacity="0.25" strokeLinecap="round" />
<rect x="230" y="370" width="340" height="16" rx="4" fill="#b08d57" />
<path d="M320 470 v-40 h160 v40" fill="none" stroke="#2c2a28" strokeWidth="10" />
<rect x="310" y="330" width="180" height="100" rx="30" fill="#1f3a34" />
<rect x="300" y="420" width="200" height="30" rx="12" fill="#26473f" />
<path d="M400 450 v60 M350 520 h100" stroke="#8a8f94" strokeWidth="10" strokeLinecap="round" />
<circle cx="400" cy="40" r="120" fill={u('lamp')} />
<path d="M120 470 v-140 M680 470 v-140" stroke="#8a6a50" strokeWidth="6" />
<path d="M100 330 h40 M660 330 h40" stroke="#8a6a50" strokeWidth="6" />
<path d="M680 330 c-30 -40, -10 -90, 0 -100 c10 10, 30 60, 0 100" fill="#5f8b5a" />
</g>
)}
{kind === 'practice' && (
<g>
<rect x="420" y="80" width="300" height="220" fill="#eaf2f5" stroke="#ffffff" strokeWidth="10" />
<path d="M570 80 v220" stroke="#ffffff" strokeWidth="6" />
<circle cx="500" cy="250" r="60" fill="#a9c79c" />
<circle cx="640" cy="240" r="70" fill="#95b889" />
<rect x="80" y="360" width="360" height="40" rx="18" fill="#f4f7f8" />
<rect x="80" y="352" width="120" height="30" rx="14" fill="#ffffff" />
<path d="M120 400 v70 M400 400 v70" stroke="#8a969c" strokeWidth="10" strokeLinecap="round" />
<rect x="520" y="390" width="160" height="80" rx="6" fill="#ffffff" stroke="#d6e0e4" strokeWidth="3" />
<path d="M540 410 h120 M540 430 h80" stroke="#c6d2d7" strokeWidth="6" strokeLinecap="round" />
<circle cx="160" cy="120" r="40" fill="#ffffff" stroke="#c6d2d7" strokeWidth="6" />
<path d="M160 96 v24 l16 10" fill="none" stroke="#5c6a70" strokeWidth="5" strokeLinecap="round" />
</g>
)}
{kind === 'spa' && (
<g>
<rect x="80" y="390" width="640" height="40" rx="10" fill="#efe7da" />
<rect x="100" y="430" width="600" height="20" fill="#8c6f53" />
{[0, 1, 2].map((i) => <rect key={i} x={140 + i * 70} y={340 - i * 4} width="110" height={36 + i * 4} rx="18" fill={['#f5efe6', '#e5d8c5', '#ffffff'][i]} />)}
{[0, 1, 2, 3].map((i) => <ellipse key={i} cx={520 + i * 14} cy={370 - i * 22} rx={46 - i * 8} ry="12" fill={['#3b3836', '#4c4845', '#5d5955', '#6e6a66'][i]} />)}
<path d="M600 330 c -10 -40, 20 -60, 40 -64 c 0 30, -20 60, -40 64 Z" fill="#7a9a6a" />
<circle cx="680" cy="372" r="8" fill="#ffe2a6" />
<circle cx="680" cy="372" r="40" fill={u('lamp')} />
<rect x="300" y="80" width="200" height="200" rx="100" fill="#cdbba4" opacity="0.6" />
</g>
)}
</svg>
);
}
/** An editorial course cover: soft color field, a big shape and a small object. seed picks the palette. */
export function CourseCover({ seed = 0, className }: { seed?: number; className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const g = (n: string) => `${n}${id}`;
const u = (n: string) => `url(#${n}${id})`;
const PAL = [
['#f1e6d6', '#e4c9a5', '#c2653c', '#2f2b29'],
['#dfe7ef', '#b8cbe0', '#2f4a6b', '#f2b45c'],
['#e3ecdf', '#bcd2b0', '#3f6b57', '#e9885d'],
['#efe1ea', '#d9b6c8', '#6b2c50', '#f4d64a'],
['#f3edd8', '#e6d39c', '#8a5a1e', '#2f4a6b'],
['#e2e2ec', '#c3c3dc', '#4b3f6b', '#ff9a4d'],
];
const [a, b, c, d] = PAL[seed % PAL.length];
const motif = seed % 6;
return (
<svg viewBox="0 0 800 500" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden data-clip-ok>
<defs>
<linearGradient id={g('bg')} x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stopColor={a} />
<stop offset="1" stopColor={b} />
</linearGradient>
<radialGradient id={g('shade')} cx="0.7" cy="0.3" r="0.8">
<stop offset="0" stopColor="#ffffff" stopOpacity="0.35" />
<stop offset="1" stopColor="#000000" stopOpacity="0.1" />
</radialGradient>
</defs>
<rect width="800" height="500" fill={u('bg')} />
{motif === 0 && (
<g>
<circle cx="520" cy="250" r="170" fill={c} />
<rect x="250" y="160" width="200" height="200" rx="16" fill={d} />
<path d="M290 330 L 350 230 L 400 300 L 420 270 L 440 330 Z" fill={a} />
</g>
)}
{motif === 1 && (
<g>
<rect x="200" y="120" width="400" height="250" rx="16" fill={c} />
<rect x="220" y="140" width="360" height="190" rx="6" fill={a} />
{[0, 1, 2, 3].map((i) => <rect key={i} x="250" y={170 + i * 36} width={round(120 + r01(seed, i) * 160)} height="14" rx="7" fill={i === 1 ? d : b} />)}
<rect x="160" y="370" width="480" height="18" rx="9" fill={c} />
</g>
)}
{motif === 2 && (
<g>
<path d="M140 420 C 220 200, 380 140, 660 120" fill="none" stroke={c} strokeWidth="40" strokeLinecap="round" />
<circle cx="660" cy="120" r="44" fill={d} />
<circle cx="140" cy="420" r="30" fill={a} stroke={c} strokeWidth="10" />
</g>
)}
{motif === 3 && (
<g>
{[0, 1, 2, 3, 4].map((i) => <rect key={i} x={200 + i * 84} y={380 - (i + 1) * 46} width="60" height={(i + 1) * 46} rx="8" fill={i === 4 ? d : c} opacity={round(0.55 + i * 0.1)} />)}
<path d="M180 380 h460" stroke={c} strokeWidth="6" strokeLinecap="round" />
</g>
)}
{motif === 4 && (
<g>
<rect x="280" y="80" width="240" height="340" rx="22" fill={c} />
<rect x="296" y="110" width="208" height="280" rx="6" fill={a} />
<circle cx="400" cy="230" r="60" fill={d} />
<path d="M386 206 L 426 230 L 386 254 Z" fill={a} />
</g>
)}
{motif === 5 && (
<g>
<circle cx="330" cy="250" r="140" fill="none" stroke={c} strokeWidth="34" />
<circle cx="470" cy="250" r="140" fill="none" stroke={d} strokeWidth="34" opacity="0.9" />
</g>
)}
<rect width="800" height="500" fill={u('shade')} />
</svg>
);
}
/* ------------------------------------------------------------------ */
/* Portfolio and events motifs (careers, case studies, portfolio, */
/* events, help center) */
/* ------------------------------------------------------------------ */
/**
* A brand identity board, as studios photograph it: a monogram card,
* colour swatches, a type specimen and a tilted business card. Seeds pick
* palette, letter and layout. Fixed colours, like a photograph.
*/
export function BrandBoard({ seed = 0, letter, className }: { seed?: number; letter?: string; className?: string }) {
const PALETTES = [
['#efe8dc', '#1f3a2e', '#c8794a', '#e9c46a', '#2b2a27'],
['#e9edf2', '#14213d', '#fca311', '#8aa1b8', '#0b0f1a'],
['#f4e4e1', '#6b2737', '#e0a39a', '#3c3c3c', '#1c1416'],
['#e6efe9', '#0f5257', '#f2b134', '#a5c4b5', '#132226'],
['#ecebf4', '#3a2e6b', '#ff7a59', '#c8c3e6', '#17142b'],
['#f1ede4', '#22313f', '#d1495b', '#edae49', '#1b1b1e'],
];
const [bg, main, accent, soft, ink] = PALETTES[seed % PALETTES.length];
const ch = letter ?? 'ANMKLS'[seed % 6];
const flip = seed % 2 === 1;
const x0 = flip ? 420 : 70;
const x1 = flip ? 70 : 530;
return (
<svg viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden data-clip-ok>
<rect width="800" height="600" fill={bg} />
<rect x={x0 + 8} y="88" width="310" height="390" rx="14" fill="#000" opacity="0.08" />
<rect x={x0} y="78" width="310" height="390" rx="14" fill={main} />
<text x={x0 + 155} y="330" textAnchor="middle" fontSize="230" fontWeight="700" fill={bg} fontFamily="inherit" letterSpacing="-10">{ch}</text>
<circle cx={x0 + 248} cy="138" r="16" fill={accent} />
<rect x={x0 + 28} y="420" width="120" height="8" rx="4" fill={bg} opacity="0.55" />
{[accent, soft, ink].map((c, i) => (
<g key={c}>
<rect x={x1} y={78 + i * 96} width="200" height="80" rx="10" fill={c} />
<rect x={x1 + 14} y={78 + i * 96 + 56} width="54" height="6" rx="3" fill={i === 1 ? ink : bg} opacity="0.6" />
</g>
))}
<text x={x1} y="400" fontSize="64" fontWeight="600" fill={ink} fontFamily="inherit" letterSpacing="-3">Aa</text>
<rect x={x1 + 100} y="370" width="100" height="6" rx="3" fill={ink} opacity="0.35" />
<rect x={x1 + 100} y="386" width="70" height="6" rx="3" fill={ink} opacity="0.2" />
<g transform={`rotate(${flip ? 8 : -7} ${x1 + 100} 500)`}>
<rect x={x1 - 10} y="452" width="220" height="124" rx="8" fill="#000" opacity="0.1" transform="translate(6 8)" />
<rect x={x1 - 10} y="452" width="220" height="124" rx="8" fill="#fbfaf7" />
<circle cx={x1 + 22} cy="486" r="12" fill={main} />
<rect x={x1 + 10} y="530" width="110" height="7" rx="3.5" fill={ink} opacity="0.7" />
<rect x={x1 + 10} y="546" width="76" height="6" rx="3" fill={ink} opacity="0.3" />
</g>
</svg>
);
}
/**
* A conference stage seen from the back rows: a large screen with a slide,
* a speaker at the lectern and the heads of the audience. The slide glows
* in the accent colour, the room stays dark in both themes.
*/
export function StagePhoto({ title = 'Keynote', seed = 0, anchor = 'xMidYMid', className }: { title?: string; seed?: number; /** preserveAspectRatio anchor, xMidYMin keeps the screen in wide frames */ anchor?: string; className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const heads: [number, number, number][] = [];
for (let row = 0; row < 3; row++) {
for (let i = 0; i < 9; i++) {
const x = round(-20 + i * 105 + (row % 2) * 52 + r01(seed + row, i) * 20);
heads.push([x, 520 + row * 46, round(22 + row * 6 + r01(seed, i + row * 9) * 4)]);
}
}
return (
<svg viewBox="0 0 800 600" preserveAspectRatio={`${anchor} slice`} className={cn('size-full', className)} aria-hidden data-clip-ok>
<defs>
<radialGradient id={`stg${id}`} cx="50%" cy="30%" r="65%">
<stop offset="0" stopColor="color-mix(in oklab, var(--primary) 45%, #1d1e26)" />
<stop offset="1" stopColor="#0c0d12" />
</radialGradient>
<linearGradient id={`scr${id}`} x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stopColor="color-mix(in oklab, var(--primary) 48%, #232532)" />
<stop offset="1" stopColor="color-mix(in oklab, var(--primary) 28%, #0e0f16)" />
</linearGradient>
<linearGradient id={`beam${id}`} x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stopColor="#ffffff" stopOpacity="0.16" />
<stop offset="1" stopColor="#ffffff" stopOpacity="0" />
</linearGradient>
</defs>
<rect width="800" height="600" fill={`url(#stg${id})`} />
<path d="M150 0 L250 0 L330 420 L120 420Z" fill={`url(#beam${id})`} />
<path d="M550 0 L650 0 L680 420 L470 420Z" fill={`url(#beam${id})`} />
<rect x="170" y="70" width="460" height="260" rx="6" fill={`url(#scr${id})`} />
<rect x="170" y="70" width="460" height="260" rx="6" fill="none" stroke="#ffffff" strokeOpacity="0.25" />
<text x="400" y="158" textAnchor="middle" fontSize="40" fontWeight="650" fill="#ffffff" fontFamily="inherit" letterSpacing="-1.5">{title}</text>
<rect x="330" y="180" width="140" height="7" rx="3.5" fill="#ffffff" opacity="0.55" />
<rect x="0" y="420" width="800" height="24" fill="#191a22" />
<rect x="0" y="444" width="800" height="160" fill="#0b0c10" />
<rect x="560" y="352" width="56" height="70" rx="4" fill="#23242e" />
<circle cx="540" cy="318" r="15" fill="#2c2d38" />
<path d="M520 420 V352 a20 20 0 0 1 40 0 V420Z" fill="#2c2d38" />
{heads.map(([x, y, r], i) => (
<g key={i} fill="#050508">
<circle cx={x} cy={y} r={r} />
<rect x={x - r * 1.9} y={y + r * 0.7} width={r * 3.8} height="80" rx={r} />
</g>
))}
</svg>
);
}
/* ------------------------------------------------------------------ */
/* Industry photographs added for the placeholders: an office for law */
/* and consulting, a boutique for retail and a coast for travel. */
/* ------------------------------------------------------------------ */
/**
* A quiet office in afternoon light: a wall of books, a walnut desk with a
* green reading lamp, files and a fountain pen, a tall window and a rug.
* seed changes the book spines.
*/
export function PhotoOffice({ seed = 0, className }: { seed?: number; className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const g = (n: string) => `${n}${id}`;
const u = (n: string) => `url(#${n}${id})`;
const SPINES = ['#6e2a2a', '#1f3350', '#2f4c3a', '#8a6a3a', '#e7dcc6', '#4a2c22', '#7b3b2e', '#26303f', '#b28a4c', '#3d4a3a'];
const shelves = [118, 214, 310, 406];
const books: { x: number; y: number; w: number; h: number; c: string; band: boolean; lean: number }[] = [];
shelves.forEach((top, row) => {
let x = 52;
let k = 0;
while (x < 300) {
const n = row * 40 + k;
const w = round(12 + r01(seed + 3, n) * 14);
const h = round(62 + r01(seed + 7, n) * 26);
const gap = r01(seed + 11, n) > 0.86 ? 18 : 1.5;
if (x + w > 304) break;
books.push({ x, y: top + 92 - h, w, h, c: SPINES[Math.floor(r01(seed + 13, n) * SPINES.length)], band: r01(seed + 17, n) > 0.35, lean: gap > 2 && x + w + 30 < 300 ? -9 : 0 });
x += w + gap;
k++;
}
});
return (
<svg viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden data-clip-ok>
<defs>
<linearGradient id={g('wall')} x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stopColor="#c9c2b1" />
<stop offset="0.6" stopColor="#ddd6c6" />
<stop offset="1" stopColor="#e8e2d4" />
</linearGradient>
<linearGradient id={g('sky')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#dfe8ee" />
<stop offset="1" stopColor="#f4efe4" />
</linearGradient>
<linearGradient id={g('beam')} x1="1" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#fff7e2" stopOpacity="0.55" />
<stop offset="1" stopColor="#fff7e2" stopOpacity="0" />
</linearGradient>
<linearGradient id={g('desk')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#7a4a2c" />
<stop offset="1" stopColor="#4f2e1b" />
</linearGradient>
<linearGradient id={g('floor')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#8c6748" />
<stop offset="1" stopColor="#5e4330" />
</linearGradient>
<radialGradient id={g('lamp')}>
<stop offset="0" stopColor="#ffd98c" stopOpacity="0.75" />
<stop offset="1" stopColor="#ffd98c" stopOpacity="0" />
</radialGradient>
<linearGradient id={g('spine')} x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stopColor="#ffffff" stopOpacity="0.16" />
<stop offset="0.5" stopColor="#ffffff" stopOpacity="0" />
<stop offset="1" stopColor="#000000" stopOpacity="0.22" />
</linearGradient>
</defs>
<rect width="800" height="600" fill={u('wall')} />
{/* window */}
<rect x="520" y="60" width="210" height="330" rx="3" fill={u('sky')} />
<g fill="#b8c6cf" opacity="0.8">
<rect x="540" y="250" width="40" height="140" />
<rect x="586" y="210" width="54" height="180" />
<rect x="646" y="270" width="34" height="120" />
<rect x="684" y="230" width="46" height="160" />
</g>
<path d="M520 60h210v330h-210Z M625 60v330 M520 225h210" fill="none" stroke="#f6f2ea" strokeWidth="10" />
<rect x="508" y="388" width="234" height="14" rx="2" fill="#efe9dd" />
<path d="M730 60 C 700 140, 708 260, 744 396 H 770 V 50 Z" fill="#9a3d34" opacity="0.92" />
<path d="M730 60 C 720 160, 730 280, 752 396" stroke="#7d2f28" strokeWidth="3" fill="none" />
<path d="M520 400 L 330 600 H 640 L 742 400 Z" fill={u('beam')} />
{/* wainscot */}
<rect y="470" width="800" height="16" fill="#b9ae98" />
{/* bookshelf */}
<rect x="36" y="20" width="284" height="500" fill="#3e2619" />
<rect x="44" y="28" width="268" height="484" fill="#2b1a11" />
{shelves.map((y) => (
<rect key={y} x="44" y={y + 92} width="268" height="10" fill="#4a2e1e" />
))}
<rect x="44" y="28" width="268" height="88" fill="#2b1a11" />
<g>
{[0, 1, 2].map((i) => (
<rect key={i} x={64 + i * 64} y={56} width="40" height="52" rx="2" fill={['#d8cfbd', '#9aa58f', '#c8b28a'][i]} opacity="0.85" />
))}
<circle cx="270" cy="88" r="18" fill="#b88a4a" />
<rect x="262" y="104" width="16" height="6" fill="#8a6533" />
</g>
{books.map((b, i) => (
<g key={i} transform={b.lean ? `rotate(${b.lean} ${b.x + b.w} ${b.y + b.h})` : undefined}>
<rect x={b.x} y={b.y} width={b.w} height={b.h} rx="1.5" fill={b.c} />
<rect x={b.x} y={b.y} width={b.w} height={b.h} rx="1.5" fill={u('spine')} />
{b.band && (
<>
<rect x={b.x + 1} y={b.y + 9} width={b.w - 2} height="2.5" fill="#d4b06a" opacity="0.85" />
<rect x={b.x + 1} y={b.y + b.h - 14} width={b.w - 2} height="2.5" fill="#d4b06a" opacity="0.85" />
</>
)}
</g>
))}
<rect x="36" y="20" width="284" height="500" fill="none" stroke="#24150d" strokeWidth="4" />
{/* floor and rug */}
<rect y="486" width="800" height="114" fill={u('floor')} />
<path d="M0 520H800M0 556H800" stroke="#4a3424" strokeWidth="2" opacity="0.4" />
<path d="M120 600 L 210 520 H 690 L 790 600 Z" fill="#7d2f2b" />
<path d="M150 600 L 228 530 H 674 L 760 600" fill="none" stroke="#c99a5e" strokeWidth="3" opacity="0.6" />
{/* chair behind the desk */}
<path d="M420 240 c0 -30, 22 -44, 70 -44 s70 14, 70 44 v130 h-140 Z" fill="#2c1b14" />
<path d="M432 250 c0 -22, 18 -34, 58 -34 s58 12, 58 34 v110 h-116 Z" fill="#3a2419" />
{[0, 1, 2, 3].map((i) => <circle key={i} cx={442 + i * 32} cy="232" r="2.5" fill="#c99a5e" opacity="0.8" />)}
{/* desk */}
<rect x="340" y="350" width="400" height="22" rx="3" fill="#8a5634" />
<rect x="352" y="372" width="376" height="140" fill={u('desk')} />
<rect x="370" y="390" width="150" height="104" rx="3" fill="#5e3720" stroke="#3d2314" strokeWidth="3" />
<rect x="560" y="390" width="150" height="104" rx="3" fill="#5e3720" stroke="#3d2314" strokeWidth="3" />
<circle cx="445" cy="442" r="4" fill="#c99a5e" />
<circle cx="635" cy="442" r="4" fill="#c99a5e" />
{/* lamp */}
<circle cx="420" cy="300" r="120" fill={u('lamp')} />
<path d="M412 350 v-46 M400 350 h24" stroke="#b08a48" strokeWidth="5" strokeLinecap="round" />
<path d="M372 306 C 372 288, 392 280, 420 280 C 448 280, 468 288, 468 306 Z" fill="#1f5a3e" />
<path d="M372 306 h96" stroke="#163f2c" strokeWidth="3" />
<ellipse cx="420" cy="308" rx="40" ry="4" fill="#ffe2a0" opacity="0.9" />
{/* files, pen, cup */}
<g transform="rotate(-4 560 340)">
<rect x="500" y="326" width="120" height="22" rx="2" fill="#efe7d6" />
<rect x="506" y="318" width="112" height="10" rx="2" fill="#2f4a6b" />
<rect x="510" y="310" width="104" height="9" rx="2" fill="#c9b892" />
</g>
<path d="M636 344 l58 -8" stroke="#1c1c1c" strokeWidth="5" strokeLinecap="round" />
<path d="M686 337 l9 -1.3" stroke="#d4b06a" strokeWidth="5" strokeLinecap="round" />
<path d="M700 350 v-24 h26 v24 Z" fill="#efe9dd" />
<path d="M726 332 c10 0, 10 12, 0 12" fill="none" stroke="#efe9dd" strokeWidth="4" />
{/* plant */}
<path d="M756 470 h34 l-5 -46 h-24 Z" fill="#a9845e" />
<path d="M773 424 c-20 -40, -30 -90, -10 -130 M773 424 c6 -50, 24 -80, 40 -96 M773 424 c-30 -20, -50 -50, -54 -80" fill="none" stroke="#3f6b47" strokeWidth="4" strokeLinecap="round" />
{[[763, 300, -20], [812, 330, 30], [724, 346, -50], [790, 380, 20], [748, 392, -30]].map(([x, y, a], i) => (
<ellipse key={i} cx={x} cy={y} rx="16" ry="7" transform={`rotate(${a} ${x} ${y})`} fill={i % 2 ? '#4f7f56' : '#3f6b47'} />
))}
</svg>
);
}
/**
* A small boutique: an arched niche, oak shelves with ceramics and folded
* linen, a rail with garments, globe lights and a counter with a plant.
* seed shifts the garment colours.
*/
export function PhotoShop({ seed = 0, className }: { seed?: number; className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const g = (n: string) => `${n}${id}`;
const u = (n: string) => `url(#${n}${id})`;
const CLOTH = ['#c9714a', '#e9dcc5', '#3f5a4a', '#d9b26a', '#7a8fa3', '#b85c5c', '#2f3a4a', '#efe7da'];
const pick = (i: number) => CLOTH[(i + seed * 3) % CLOTH.length];
return (
<svg viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden data-clip-ok>
<defs>
<linearGradient id={g('wall')} x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stopColor="#f3ede4" />
<stop offset="1" stopColor="#e2d8ca" />
</linearGradient>
<linearGradient id={g('niche')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#c87a55" />
<stop offset="1" stopColor="#b2643f" />
</linearGradient>
<linearGradient id={g('floor')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#d2b48e" />
<stop offset="1" stopColor="#b08c64" />
</linearGradient>
<radialGradient id={g('glow')}>
<stop offset="0" stopColor="#fff1cf" stopOpacity="0.85" />
<stop offset="1" stopColor="#fff1cf" stopOpacity="0" />
</radialGradient>
<linearGradient id={g('fold')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#ffffff" stopOpacity="0.2" />
<stop offset="1" stopColor="#000000" stopOpacity="0.16" />
</linearGradient>
</defs>
<rect width="800" height="600" fill={u('wall')} />
{/* niche with shelves */}
<path d="M90 470 V190 a120 120 0 0 1 240 0 V470 Z" fill={u('niche')} />
{[230, 320, 410].map((y) => (
<g key={y}>
<rect x="90" y={y} width="240" height="10" fill="#c7a073" />
<rect x="90" y={y + 10} width="240" height="4" fill="#000" opacity="0.12" />
</g>
))}
{/* ceramics on the top shelf */}
<path d="M128 230 c-12 -18, -12 -44, 4 -56 h28 c16 12, 16 38, 4 56 Z" fill="#efe6d6" />
<path d="M190 230 c-6 -10, -6 -26, 2 -34 c-2 -8, -2 -14, 2 -20 h14 c4 6, 4 12, 2 20 c8 8, 8 24, 2 34 Z" fill="#3f5a4a" />
<path d="M238 230 c-16 0, -22 -10, -22 -24 h68 c0 14, -6 24, -22 24 Z" fill="#d9b26a" />
<circle cx="294" cy="214" r="14" fill="#2f3a4a" />
{/* folded linen */}
{[0, 1, 2].map((s) =>
[0, 1, 2, 3].map((k) => (
<g key={`${s}-${k}`}>
<rect x={110 + s * 72} y={306 - k * 14} width="60" height="13" rx="3" fill={pick(s * 4 + k)} />
<rect x={110 + s * 72} y={306 - k * 14} width="60" height="13" rx="3" fill={u('fold')} />
</g>
)),
)}
{/* baskets on the low shelf */}
{[0, 1].map((i) => (
<g key={i}>
<path d={`M${118 + i * 110} 410 l6 -54 h82 l6 54 Z`} fill="#b08a5a" />
<path d={`M${124 + i * 110} 372 h82 M${122 + i * 110} 390 h86`} stroke="#8c6a40" strokeWidth="3" />
</g>
))}
{/* rail with garments */}
<path d="M400 120 H760" stroke="#2c2622" strokeWidth="6" strokeLinecap="round" />
<path d="M410 120 V90 M750 120 V90" stroke="#2c2622" strokeWidth="5" />
{[0, 1, 2, 3, 4, 5].map((i) => {
const x = 430 + i * 54 + (i > 2 ? 12 : 0);
const len = 230 + ((i * 37 + seed * 11) % 5) * 22;
const c = pick(i + 1);
return (
<g key={i}>
<path d={`M${x} 120 l-16 22 h32 Z`} fill="none" stroke="#3a3330" strokeWidth="3" strokeLinejoin="round" />
<path d={`M${x - 24} 146 q24 -10 48 0 l6 ${len} h-60 Z`} fill={c} />
<path d={`M${x - 24} 146 q24 -10 48 0 l6 ${len} h-60 Z`} fill={u('fold')} />
<path d={`M${x} 140 v${len - 10}`} stroke="#000" strokeWidth="2" opacity="0.1" />
</g>
);
})}
{/* globe lights */}
{[460, 640].map((x) => (
<g key={x}>
<path d={`M${x} 0 V40`} stroke="#2c2622" strokeWidth="2" />
<circle cx={x} cy="60" r="70" fill={u('glow')} />
<circle cx={x} cy="58" r="20" fill="#fff6e0" />
</g>
))}
{/* floor, counter and plant */}
<rect y="470" width="800" height="130" fill={u('floor')} />
<path d="M0 500H800M0 540H800M0 580H800" stroke="#9a7752" strokeWidth="2" opacity="0.35" />
<rect x="360" y="420" width="300" height="20" rx="4" fill="#e9e2d6" />
<rect x="372" y="440" width="276" height="120" fill="#cfb38c" />
{Array.from({ length: 12 }, (_, i) => <path key={i} d={`M${384 + i * 23} 446 v108`} stroke="#b59670" strokeWidth="3" />)}
<rect x="560" y="388" width="64" height="32" rx="4" fill="#2f3a4a" />
<rect x="566" y="394" width="40" height="4" rx="2" fill="#7a8fa3" />
<path d="M392 420 c0 -24, 10 -36, 28 -36 s28 12, 28 36 Z" fill="#efe6d6" />
<path d="M700 560 h60 l-8 -64 h-44 Z" fill="#b2643f" />
<path d="M730 496 c-30 -40, -36 -100, -10 -150 M730 496 c10 -60, 40 -96, 64 -110 M730 496 c-40 -30, -66 -70, -70 -110" fill="none" stroke="#3f6b47" strokeWidth="4" strokeLinecap="round" />
{[[720, 346, -20], [792, 388, 40], [662, 388, -50], [770, 440, 30], [690, 440, -40], [742, 400, 10]].map(([x, y, a], i) => (
<ellipse key={i} cx={x} cy={y} rx="22" ry="9" transform={`rotate(${a} ${x} ${y})`} fill={i % 2 ? '#4f7f56' : '#3f6b47'} />
))}
</svg>
);
}
/**
* A coast at midday for travel: a hazy sky, cliffs with white houses, a
* turquoise bay with a sailboat and a beach with a striped umbrella.
*/
export function PhotoCoast({ className }: { className?: string }) {
const id = useContentId().replace(/[^a-zA-Z0-9]/g, '');
const g = (n: string) => `${n}${id}`;
const u = (n: string) => `url(#${n}${id})`;
const houses: [number, number, number, number][] = [[1080, 300, 54, 40], [1140, 286, 44, 50], [1192, 300, 60, 36], [1110, 340, 50, 34], [1170, 330, 40, 40], [1228, 340, 56, 30], [1270, 312, 46, 44], [1320, 330, 50, 36], [1060, 360, 40, 30], [1300, 372, 44, 26]];
return (
<svg viewBox="0 0 1600 1000" preserveAspectRatio="xMidYMid slice" className={cn('size-full', className)} aria-hidden data-clip-ok>
<defs>
<linearGradient id={g('sky')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#6fb3e0" />
<stop offset="0.6" stopColor="#bfe0f0" />
<stop offset="1" stopColor="#f2efe4" />
</linearGradient>
<radialGradient id={g('sun')}>
<stop offset="0" stopColor="#ffffff" stopOpacity="0.95" />
<stop offset="0.3" stopColor="#fff6d8" stopOpacity="0.5" />
<stop offset="1" stopColor="#fff6d8" stopOpacity="0" />
</radialGradient>
<linearGradient id={g('sea')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#2f86b0" />
<stop offset="0.45" stopColor="#2fa3b3" />
<stop offset="1" stopColor="#5fd0c6" />
</linearGradient>
<linearGradient id={g('cliff')} x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stopColor="#b48a62" />
<stop offset="1" stopColor="#8c6748" />
</linearGradient>
<linearGradient id={g('sand')} x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stopColor="#f1dfbf" />
<stop offset="1" stopColor="#e2c79c" />
</linearGradient>
</defs>
<rect width="1600" height="1000" fill={u('sky')} />
<circle cx="420" cy="180" r="260" fill={u('sun')} />
<g fill="#ffffff" opacity="0.7">
<ellipse cx="760" cy="150" rx="140" ry="22" />
<ellipse cx="840" cy="132" rx="80" ry="24" />
<ellipse cx="260" cy="300" rx="120" ry="16" />
</g>
{/* far headland */}
<path d="M0 470 C 120 440, 220 430, 340 452 S 520 470, 600 480 V 500 H0 Z" fill="#8fb0a8" opacity="0.8" />
<rect y="480" width="1600" height="520" fill={u('sea')} />
{/* cliff with the village */}
<path d="M960 520 C 990 420, 1040 360, 1080 330 C 1140 280, 1240 260, 1340 280 C 1440 300, 1520 330, 1600 320 V 640 C 1440 600, 1240 600, 1100 580 C 1020 570, 980 550, 960 520 Z" fill={u('cliff')} />
<path d="M1000 540 C 1060 520, 1140 510, 1220 520" stroke="#6f4f36" strokeWidth="6" opacity="0.4" fill="none" />
{houses.map(([x, y, w, h], i) => (
<g key={i}>
<rect x={x} y={y} width={w} height={h} fill="#fbf8f2" />
<rect x={x} y={y + h - 6} width={w} height="6" fill="#d9d2c4" />
<rect x={x + w * 0.3} y={y + h * 0.35} width={w * 0.18} height={h * 0.3} fill={i % 3 === 0 ? '#2f6f9f' : '#7a6a58'} />
{i % 4 === 1 && <path d={`M${x + 4} ${y} a${w / 2 - 4} ${w / 2 - 4} 0 0 1 ${w - 8} 0 Z`} fill="#2f6f9f" />}
</g>
))}
<g fill="#4f7f56">
<ellipse cx="1050" cy="400" rx="26" ry="14" />
<ellipse cx="1260" cy="390" rx="20" ry="12" />
<ellipse cx="1380" cy="350" rx="30" ry="14" />
</g>
{/* light on the water */}
<g fill="#ffffff" opacity="0.55">
{[[300, 520, 160], [520, 560, 220], [200, 610, 120], [700, 640, 260], [420, 700, 180], [900, 610, 140]].map(([x, y, w], i) => (
<rect key={i} x={x} y={y} width={w} height={i % 2 ? 3 : 4} rx="2" />
))}
</g>
{/* sailboat */}
<g transform="translate(640 560)">
<path d="M-60 0 h120 l-18 18 h-84 Z" fill="#fbf8f2" />
<path d="M0 -2 V-150" stroke="#3a3330" strokeWidth="3" />
<path d="M4 -146 L70 -8 H4 Z" fill="#fbf8f2" />
<path d="M-4 -120 L-52 -8 H-4 Z" fill="#e8e2d6" />
<path d="M-58 20 h116" stroke="#1f6f8a" strokeWidth="4" opacity="0.5" />
</g>
{/* beach */}
<path d="M0 760 C 260 720, 520 740, 760 790 S 1240 860, 1600 820 V1000 H0 Z" fill="#ffffff" opacity="0.65" />
<path d="M0 780 C 260 744, 520 762, 760 810 S 1240 878, 1600 840 V1000 H0 Z" fill={u('sand')} />
{/* umbrella and towel */}
<g transform="translate(360 880)">
<path d="M0 0 L22 -190" stroke="#5a4636" strokeWidth="6" strokeLinecap="round" />
<path d="M-120 -170 C -80 -250, 120 -260, 170 -190 Z" fill="#e9665a" />
<path d="M-60 -206 C -40 -248, 40 -254, 70 -220 L 22 -190 Z M 80 -232 C 110 -230, 150 -214, 170 -190 L 22 -190 Z" fill="#fbf3e6" />
<ellipse cx="40" cy="6" rx="150" ry="18" fill="#000" opacity="0.08" />
<rect x="-10" y="-20" width="190" height="44" rx="6" fill="#2f86b0" transform="skewX(-20)" />
<rect x="18" y="-20" width="24" height="44" fill="#fbf3e6" transform="skewX(-20)" />
<rect x="80" y="-20" width="24" height="44" fill="#fbf3e6" transform="skewX(-20)" />
</g>
</svg>
);
}Frequent questions
How do I install Drawn photos?
With aiw add @ai-website/photos. The command puts components/ui/photos.tsx into your project, the code is yours from then on. It brings along icon. It needs no further npm packages.
Which variants does Drawn photos have?
4 variants, Food and drinks, Places and houses, People and Covers and stages.
Can an AI assistant build with Drawn photos?
Yes. Through the MCP server of the aiw CLI, Claude, Cursor, GitHub Copilot, Windsurf, Codex and Gemini find Drawn photos with aiw_components and install it with aiw_add @ai-website/photos.