// Shared bits used by all three homepage directions. // - Logo: brand wordmark from PNG // - QuiltGrid: parametric grid of half-square triangles (HSTs) // - RainbowDivider: 6-stripe band of brand colors // - StreamDot, ArrowLink, eyebrows — small atoms // --- Logo ---------------------------------------------------------- function Logo({ variant = "color", height = 44, style }) { // 'color' is the regular logo. 'mono-paper' overlays a knockout for dark backgrounds. const src = variant === "color" ? "assets/roco-logo-full.png" : "assets/roco-logo-transparent.png"; return ( RoCo Arts Council ); } // A simple mono "RoCo / Arts Council" stamp used on dark surfaces in case // the rasterized logo reads poorly. Wordmark approximation; the real PNG // stays the canonical mark. function LogoMono({ height = 44, color = "#FBF7EE", style }) { return (
RoCo
— Arts Council —
); } // --- QuiltGrid ----------------------------------------------------- // Renders an R×C grid of half-square triangles. Each cell is a square split // diagonally with two colors; orientation alternates pseudo-randomly per seed. // Used as a hero block in directions A and B. // // Palette is the brand 6 + paper. We bias for full saturation in cells and // occasional empty (paper/navy) tiles to give the grid breathing room. function QuiltGrid({ cols = 5, rows = 4, cellSize = 140, seed = 7, palette = ["#45217A", "#F3CF03", "#C81344", "#78BE4D", "#4195D3", "#01234B", "#FBF7EE"], emptyChance = 0.08, style, }) { // simple deterministic RNG so every artboard render is identical let s = seed; const rnd = () => { s = (s * 9301 + 49297) % 233280; return s / 233280; }; const cells = []; for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { const x = c * cellSize; const y = r * cellSize; const orientation = Math.floor(rnd() * 4); // 0..3 // pick two distinct colors const ai = Math.floor(rnd() * palette.length); let bi = Math.floor(rnd() * palette.length); if (bi === ai) bi = (bi + 1) % palette.length; const a = palette[ai]; const b = palette[bi]; // small chance of an empty paper square for rhythm const empty = rnd() < emptyChance; const baseFill = empty ? "#FBF7EE" : a; // Triangle paths per orientation (the "top" triangle): // 0 = top-left triangle (cuts ↘), 1 = top-right (cuts ↙), // 2 = bottom-right, 3 = bottom-left const tri = (() => { switch (orientation) { case 0: return `M${x},${y} L${x + cellSize},${y} L${x},${y + cellSize} Z`; case 1: return `M${x},${y} L${x + cellSize},${y} L${x + cellSize},${y + cellSize} Z`; case 2: return `M${x + cellSize},${y} L${x + cellSize},${y + cellSize} L${x},${y + cellSize} Z`; case 3: default: return `M${x},${y} L${x + cellSize},${y + cellSize} L${x},${y + cellSize} Z`; } })(); cells.push( {!empty && } ); } } return ( {cells} ); } // --- HST corner stamp (small) ------------------------------------- function HstStamp({ size = 48, top = "#45217A", bottom = "#F3CF03", style }) { return ( ); } // --- Pinwheel block ----------------------------------------------- function Pinwheel({ size = 320, colors = ["#45217A", "#F3CF03", "#C81344", "#78BE4D"], style }) { return ( ); } // --- Rainbow divider ---------------------------------------------- function RainbowDivider({ height = 8, style }) { const colors = ["#45217A", "#F3CF03", "#C81344", "#78BE4D", "#4195D3", "#01234B"]; return (
{colors.map((c, i) => (
))}
); } // --- Stream-dot indicator (for nav, eyebrows, etc.) ----------------- function StreamDot({ stream, size = 9, style }) { const map = { council: "#45217A", events: "#2F7A2E", artists: "#01234B", news: "#1F1B2E", }; return ( ); } // --- ArrowLink ---------------------------------------------------- function ArrowLink({ children, color = "var(--roco-purple)", style }) { return ( {children} ); } // --- Eyebrow ------------------------------------------------------ function Eyebrow({ color = "var(--roco-crimson)", children, style }) { return (
{children}
); } // --- Buttons (per-stream) ----------------------------------------- function Btn({ tone = "primary", stream = "council", children, style, size = "md" }) { const streams = { council: { primary: { bg: "#45217A", fg: "#FBF7EE" }, deep: { bg: "#2B1450", fg: "#FBF7EE" }, pop: { bg: "#FF7A5C", fg: "#1A0830" }, ghost: { fg: "#45217A", border: "#45217A" } }, events: { primary: { bg: "#2F7A2E", fg: "#FBF7EE" }, deep: { bg: "#1A461A", fg: "#FBF7EE" }, pop: { bg: "#EB4D86", fg: "#FBF7EE" }, ghost: { fg: "#1A461A", border: "#1A461A" } }, artists: { primary: { bg: "#01234B", fg: "#FBF7EE" }, deep: { bg: "#000E22", fg: "#FBF7EE" }, pop: { bg: "#FF9B3D", fg: "#000E22" }, ghost: { fg: "#01234B", border: "#01234B" } }, news: { primary: { bg: "#1F1B2E", fg: "#FBF7EE" }, deep: { bg: "#000000", fg: "#FBF7EE" }, pop: { bg: "#F3CF03", fg: "#000000" }, ghost: { fg: "#1F1B2E", border: "#1F1B2E" } }, }; const sz = { sm: { p: "8px 14px", f: 13 }, md: { p: "12px 22px", f: 14.5 }, lg: { p: "16px 28px", f: 16 }, }[size]; const set = streams[stream][tone] || streams[stream].primary; const isGhost = tone === "ghost"; return ( {children} ); } // --- HeroTexture -------------------------------------------------- // Subtle paper-color half-square-triangle pattern for use on black hero // backgrounds. Tiles 60px; uses two opacities so the pattern reads as // a soft texture, not a hard grid. Place it inside a position:relative // hero — it absolutely fills its parent and sits behind content. function HeroTexture({ opacity = 1, color = "#FBF7EE", style }) { return ( ); } Object.assign(window, { Logo, LogoMono, QuiltGrid, HstStamp, Pinwheel, RainbowDivider, StreamDot, ArrowLink, Eyebrow, Btn, HeroTexture });