feat: Modular aquatic background system and shared CodeBlock component
Add aquaticBackground/ module with OceanScene (unified gradient, light rays, sea floor, corals, seaweed, decorations), plus independent overlay layers (BubbleStream, FishSchool, JellyfishDrift, EventOverlay, EdgeFade). Includes event scheduling engine with 4 frequency tiers (minutes/hours/days/months) and 20 random events with localStorage persistence. Add shared CodeBlock component with copy-to-clipboard button, terminal-matched monospace font (Consolas), and proper line-height/letter-spacing. Refactor EditCard, WriteCard, TaskCard, and ToolResultBlock to use CodeBlock. Fix markdown code block alignment to match terminal rendering.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
/** Encode an SVG string to a CSS-usable data URI */
|
||||
export function svgDataUri(svg: string): string {
|
||||
return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`
|
||||
}
|
||||
|
||||
/** Generate a pixel art fish SVG */
|
||||
export function fishSvg(opts: {
|
||||
w: number
|
||||
h: number
|
||||
body: string
|
||||
accent: string
|
||||
eye?: string
|
||||
facing?: 'left' | 'right'
|
||||
}): string {
|
||||
const { w, h, body, accent, facing = 'right' } = opts
|
||||
const eye = opts.eye ?? '#000'
|
||||
// Build a generic pixel fish: body rect, tail, eye
|
||||
const hw = Math.floor(w / 2)
|
||||
const hh = Math.floor(h / 2)
|
||||
const tailW = Math.floor(w * 0.2)
|
||||
const bodyW = w - tailW
|
||||
const eyeX = facing === 'right' ? bodyW - 3 : tailW + 1
|
||||
const tailX = facing === 'right' ? 0 : bodyW
|
||||
|
||||
return `<svg xmlns='http://www.w3.org/2000/svg' width='${w}' height='${h}' viewBox='0 0 ${w} ${h}' shape-rendering='crispEdges'>`
|
||||
// Tail
|
||||
+ `<rect x='${tailX}' y='1' width='${tailW}' height='${Math.floor(h * 0.3)}' fill='${accent}' opacity='0.6'/>`
|
||||
+ `<rect x='${tailX}' y='${h - Math.floor(h * 0.3) - 1}' width='${tailW}' height='${Math.floor(h * 0.3)}' fill='${accent}' opacity='0.6'/>`
|
||||
// Body
|
||||
+ `<rect x='${facing === 'right' ? tailW : 0}' y='1' width='${bodyW}' height='${h - 2}' fill='${body}' opacity='0.55'/>`
|
||||
// Stripe
|
||||
+ `<rect x='${hw - 1}' y='1' width='2' height='${h - 2}' fill='${accent}' opacity='0.35'/>`
|
||||
// Eye
|
||||
+ `<rect x='${eyeX}' y='${hh - 1}' width='2' height='2' fill='${eye}' opacity='0.5'/>`
|
||||
+ `<rect x='${eyeX + (facing === 'right' ? 1 : 0)}' y='${hh - 1}' width='1' height='1' fill='white' opacity='0.35'/>`
|
||||
+ `</svg>`
|
||||
}
|
||||
|
||||
/** Generate a coral SVG of a given type */
|
||||
export function coralSvg(type: 'branching' | 'brain' | 'fan', color: string, accent: string): string {
|
||||
if (type === 'branching') {
|
||||
return `<svg xmlns='http://www.w3.org/2000/svg' width='16' height='20' viewBox='0 0 16 20' shape-rendering='crispEdges'>`
|
||||
+ `<rect x='7' y='8' width='2' height='12' fill='${color}' opacity='0.55'/>`
|
||||
+ `<rect x='5' y='6' width='2' height='8' fill='${color}' opacity='0.5'/>`
|
||||
+ `<rect x='9' y='5' width='2' height='9' fill='${color}' opacity='0.5'/>`
|
||||
+ `<rect x='3' y='3' width='2' height='6' fill='${accent}' opacity='0.45'/>`
|
||||
+ `<rect x='11' y='2' width='2' height='7' fill='${accent}' opacity='0.45'/>`
|
||||
+ `<rect x='5' y='4' width='2' height='2' fill='${accent}' opacity='0.4'/>`
|
||||
+ `<rect x='9' y='3' width='2' height='2' fill='${accent}' opacity='0.4'/>`
|
||||
+ `<rect x='1' y='1' width='2' height='4' fill='${accent}' opacity='0.35'/>`
|
||||
+ `<rect x='13' y='0' width='2' height='5' fill='${accent}' opacity='0.35'/>`
|
||||
+ `</svg>`
|
||||
}
|
||||
if (type === 'brain') {
|
||||
return `<svg xmlns='http://www.w3.org/2000/svg' width='12' height='10' viewBox='0 0 12 10' shape-rendering='crispEdges'>`
|
||||
+ `<rect x='2' y='3' width='8' height='6' rx='0' fill='${color}' opacity='0.5'/>`
|
||||
+ `<rect x='3' y='2' width='6' height='2' fill='${color}' opacity='0.45'/>`
|
||||
+ `<rect x='4' y='1' width='4' height='2' fill='${accent}' opacity='0.4'/>`
|
||||
+ `<rect x='3' y='4' width='2' height='2' fill='${accent}' opacity='0.3'/>`
|
||||
+ `<rect x='7' y='5' width='2' height='2' fill='${accent}' opacity='0.3'/>`
|
||||
+ `<rect x='5' y='7' width='2' height='2' fill='${accent}' opacity='0.25'/>`
|
||||
+ `</svg>`
|
||||
}
|
||||
// fan
|
||||
return `<svg xmlns='http://www.w3.org/2000/svg' width='14' height='18' viewBox='0 0 14 18' shape-rendering='crispEdges'>`
|
||||
+ `<rect x='6' y='12' width='2' height='6' fill='${color}' opacity='0.5'/>`
|
||||
+ `<rect x='4' y='8' width='6' height='5' fill='${color}' opacity='0.45'/>`
|
||||
+ `<rect x='3' y='5' width='8' height='4' fill='${accent}' opacity='0.4'/>`
|
||||
+ `<rect x='2' y='2' width='10' height='4' fill='${accent}' opacity='0.35'/>`
|
||||
+ `<rect x='4' y='0' width='6' height='3' fill='${accent}' opacity='0.3'/>`
|
||||
+ `<rect x='5' y='6' width='2' height='2' fill='${color}' opacity='0.25'/>`
|
||||
+ `<rect x='8' y='4' width='2' height='2' fill='${color}' opacity='0.25'/>`
|
||||
+ `</svg>`
|
||||
}
|
||||
|
||||
/** Generate a seaweed stalk SVG */
|
||||
export function seaweedSvg(height: number, color: string, accent: string): string {
|
||||
const segments: string[] = []
|
||||
for (let y = 0; y < height; y += 4) {
|
||||
const xOff = (y / 4) % 2 === 0 ? 0 : 1
|
||||
const c = y % 8 === 0 ? color : accent
|
||||
const op = 0.3 + (y / height) * 0.25
|
||||
segments.push(`<rect x='${1 + xOff}' y='${y}' width='2' height='4' fill='${c}' opacity='${op.toFixed(2)}'/>`)
|
||||
}
|
||||
// Leaf accents every 12px
|
||||
for (let y = 6; y < height - 4; y += 12) {
|
||||
const side = (y / 12) % 2 === 0 ? 0 : 3
|
||||
segments.push(`<rect x='${side}' y='${y}' width='2' height='3' fill='${accent}' opacity='0.3'/>`)
|
||||
}
|
||||
return `<svg xmlns='http://www.w3.org/2000/svg' width='5' height='${height}' viewBox='0 0 5 ${height}' shape-rendering='crispEdges'>`
|
||||
+ segments.join('')
|
||||
+ `</svg>`
|
||||
}
|
||||
|
||||
/** Generate a jellyfish SVG */
|
||||
export function jellyfishSvg(bell: string, tentacle: string): string {
|
||||
return `<svg xmlns='http://www.w3.org/2000/svg' width='16' height='22' viewBox='0 0 16 22' shape-rendering='crispEdges'>`
|
||||
// Bell
|
||||
+ `<rect x='4' y='0' width='8' height='2' fill='${bell}' opacity='0.4'/>`
|
||||
+ `<rect x='2' y='2' width='12' height='2' fill='${bell}' opacity='0.45'/>`
|
||||
+ `<rect x='1' y='4' width='14' height='4' fill='${bell}' opacity='0.5'/>`
|
||||
+ `<rect x='2' y='8' width='12' height='2' fill='${bell}' opacity='0.45'/>`
|
||||
// Inner glow
|
||||
+ `<rect x='5' y='4' width='6' height='3' fill='white' opacity='0.15'/>`
|
||||
// Tentacles
|
||||
+ `<rect x='3' y='10' width='1' height='8' fill='${tentacle}' opacity='0.35'/>`
|
||||
+ `<rect x='6' y='10' width='1' height='10' fill='${tentacle}' opacity='0.3'/>`
|
||||
+ `<rect x='9' y='10' width='1' height='9' fill='${tentacle}' opacity='0.32'/>`
|
||||
+ `<rect x='12' y='10' width='1' height='7' fill='${tentacle}' opacity='0.28'/>`
|
||||
// Tentacle wiggles
|
||||
+ `<rect x='2' y='14' width='1' height='2' fill='${tentacle}' opacity='0.25'/>`
|
||||
+ `<rect x='7' y='16' width='1' height='2' fill='${tentacle}' opacity='0.22'/>`
|
||||
+ `<rect x='10' y='15' width='1' height='2' fill='${tentacle}' opacity='0.22'/>`
|
||||
+ `</svg>`
|
||||
}
|
||||
|
||||
/** Generate a starfish SVG */
|
||||
export function starfishSvg(color: string): string {
|
||||
return `<svg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8' shape-rendering='crispEdges'>`
|
||||
+ `<rect x='3' y='0' width='2' height='3' fill='${color}' opacity='0.45'/>`
|
||||
+ `<rect x='0' y='3' width='3' height='2' fill='${color}' opacity='0.45'/>`
|
||||
+ `<rect x='5' y='3' width='3' height='2' fill='${color}' opacity='0.45'/>`
|
||||
+ `<rect x='2' y='2' width='4' height='4' fill='${color}' opacity='0.5'/>`
|
||||
+ `<rect x='1' y='5' width='2' height='2' fill='${color}' opacity='0.4'/>`
|
||||
+ `<rect x='5' y='5' width='2' height='2' fill='${color}' opacity='0.4'/>`
|
||||
+ `<rect x='3' y='3' width='2' height='2' fill='white' opacity='0.15'/>`
|
||||
+ `</svg>`
|
||||
}
|
||||
|
||||
/** Generate a small shell SVG */
|
||||
export function shellSvg(color: string): string {
|
||||
return `<svg xmlns='http://www.w3.org/2000/svg' width='6' height='4' viewBox='0 0 6 4' shape-rendering='crispEdges'>`
|
||||
+ `<rect x='1' y='2' width='4' height='2' fill='${color}' opacity='0.4'/>`
|
||||
+ `<rect x='2' y='1' width='2' height='1' fill='${color}' opacity='0.35'/>`
|
||||
+ `<rect x='0' y='3' width='1' height='1' fill='${color}' opacity='0.3'/>`
|
||||
+ `<rect x='5' y='3' width='1' height='1' fill='${color}' opacity='0.3'/>`
|
||||
+ `<rect x='2' y='2' width='1' height='1' fill='white' opacity='0.15'/>`
|
||||
+ `</svg>`
|
||||
}
|
||||
Reference in New Issue
Block a user