← Fantasy3D assetfogon/totem-runico
Tótem rúnico
Drag to turn it// «Tótem rúnico» · gpt-6-sol (medium)
// A tall carved stone totem: stacked blocks of grey stone with carved faces and glowing blue runes, moss at the base, and a small floating crystal hovering above the top.
const stone = ["#666e70", "#717a7b", "#7c8585", "#899291"];
const dark = "#38474d";
const recess = "#30434c";
const cyan = "#43e8f2";
const bright = "#b5fbff";
function carvedBlock(x0, x1, y0, y1, z0, z1) {
for (let z = z0; z <= z1; z++)
for (let y = y0; y <= y1; y++)
for (let x = x0; x <= x1; x++) {
// Nipped corners make each massive block feel softly carved.
const cornerX = Math.min(x - x0, x1 - x);
const cornerY = Math.min(y - y0, y1 - y);
const cornerZ = Math.min(z - z0, z1 - z);
if (cornerX === 0 && cornerY === 0) continue;
if (cornerX === 0 && cornerZ === 0 && (y === y0 || y === y1)) continue;
let c = stone[z >= z1 - 2 ? 2 : z <= z0 + 1 ? 0 : 1];
if (rand() < 0.12) c = stone[Math.floor(rand() * stone.length)];
set(x, y, z, c);
}
}
function rune(x, y, z) {
const glow = "#168fa8";
line(x, y, z, x, y, z + 4, 0, glow);
set(x, y - 1, z + 1, cyan);
set(x, y - 1, z + 2, bright);
set(x, y - 1, z + 3, cyan);
set(x - 1, y - 1, z + 3, cyan);
set(x + 1, y - 1, z + 3, cyan);
set(x - 1, y - 1, z + 4, "#72f5ff");
set(x + 1, y - 1, z + 4, "#72f5ff");
}
function face(front, low, high) {
const y = front - 1;
const eyeZ = high - 5;
// Deep-set, generously sized cartoon eyes.
for (const x of [20, 28]) {
box(x - 1, y, eyeZ - 1, x + 1, y, eyeZ + 1, recess);
box(x - 1, y - 1, eyeZ, x + 1, y - 1, eyeZ + 1, cyan);
set(x, y - 2, eyeZ + 1, bright);
box(x - 2, y - 1, eyeZ + 2, x + 2, y, eyeZ + 2, "#596467");
box(x - 1, y - 2, eyeZ + 3, x + 1, y - 2, eyeZ + 3, "#929b98");
}
box(23, y - 2, eyeZ - 2, 25, y - 1, eyeZ, "#929b98");
box(23, y - 3, eyeZ - 2, 25, y - 3, eyeZ - 2, "#a7afaa");
const mouthZ = low + 3;
line(21, y - 1, mouthZ + 1, 23, y - 1, mouthZ, 0, dark);
line(23, y - 1, mouthZ, 25, y - 1, mouthZ, 0, dark);
line(25, y - 1, mouthZ, 27, y - 1, mouthZ + 1, 0, dark);
set(20, y - 1, mouthZ + 2, "#a09198");
set(28, y - 1, mouthZ + 2, "#a09198");
rune(16, y, low + 3);
rune(32, y, low + 3);
}
// Broad, weathered footing.
for (let z = 0; z <= 3; z++)
for (let y = 10; y <= 38; y++)
for (let x = 7; x <= 41; x++) {
const dx = (x - 24) / (17 - z * 0.7);
const dy = (y - 24) / (14 - z * 0.5);
if (dx * dx + dy * dy > 1 + (rand() - 0.5) * 0.075) continue;
set(x, y, z, rand() < 0.2 ? "#596663" : stone[z === 3 ? 2 : 0]);
}
carvedBlock(15, 33, 18, 32, 4, 15);
carvedBlock(14, 34, 17, 33, 16, 26);
carvedBlock(15, 33, 18, 32, 27, 36);
// Heavy lips between the three carved heads.
for (const [x0, x1, y0, y1, z] of [
[13, 35, 16, 34, 15],
[13, 35, 16, 34, 26],
[14, 34, 17, 33, 36]
]) {
box(x0 + 1, y0 + 1, z, x1 - 1, y1 - 1, z, "#525e61");
box(x0 + 2, y0, z, x1 - 2, y1, z, "#929b96");
box(x0, y0 + 2, z, x1, y1 - 2, z, "#929b96");
line(x0 + 2, y0 - 1, z, x1 - 2, y0 - 1, z, 0, "#a5ada5");
}
face(18, 4, 15);
face(17, 16, 26);
face(18, 27, 36);
// Tiny glowing marks in the gaps between heads.
for (const z of [15, 26, 36]) {
for (const x of [18, 24, 30]) {
set(x, 15, z, "#166a7c");
set(x, 14, z, x === 24 ? bright : cyan);
}
}
// Scattered moss on the footing and on lower stone ledges.
forEach((x, y, z, color) => {
if (z > 9 || z < 2 || get(x, y, z + 1) !== undefined) return;
const chance = z <= 3 ? 0.39 : 0.15;
if (rand() < chance) {
set(x, y, z + 1, rand() < 0.3 ? "#80a266" : "#537d62");
if (rand() < 0.16) set(x, y, z + 2, "#91ae70");
}
});
// Moss spills down the front corners.
for (const x of [16, 17, 31, 32]) {
for (let z = 4; z <= 8; z++)
if (rand() < 0.7) set(x, 17, z, rand() < 0.5 ? "#547b60" : "#789667");
}
// Two miniature storybook mushrooms.
for (const [x, y, cap] of [
[12, 15, "#b97ac2"],
[37, 18, "#db9eae"]
]) {
box(x, y, 3, x, y, 5, "#e9dbb6");
box(x - 2, y - 1, 6, x + 2, y + 1, 6, cap);
box(x - 1, y - 1, 7, x + 1, y + 1, 7, cap);
set(x - 1, y - 1, 7, "#f1c5de");
set(x + 1, y, 7, "#f1c5de");
}
// A brass-rimmed socket below the hovering crystal.
box(21, 21, 37, 27, 27, 37, "#685e51");
box(22, 22, 38, 26, 26, 38, "#c1a574");
box(23, 23, 39, 25, 25, 39, "#4e9da4");
part("crystal", [24, 24, 42]);
for (let z = 39; z <= 45; z++) {
const radius = [0, 2, 3, 3, 2, 1, 0][z - 39];
for (let y = 24 - radius; y <= 24 + radius; y++)
for (let x = 24 - radius; x <= 24 + radius; x++) {
if (Math.abs(x - 24) + Math.abs(y - 24) > radius + 1) continue;
set(x, y, z, x <= 24 && y <= 24 ? "#a8faff" : "#208dca");
}
}
animate("crystal", { type: "bob", axis: "z", amplitude: 1, speed: 2 });
animate("crystal", {
type: "colors",
palette: ["#216ac7", "#36e9ff", "#f0ffff"],
pattern: "pulse",
glow: 0.9
});
part("body", [24, 24, 0]);import { draw } from "./assets/voxel";
import { totemRunico } from "./assets/totem-runico";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, totemRunico, x, y, { size: 64, angle: 0, t: performance.now() / 1000 });
// size: its longest side, in px
// angle: turn on the floor, in radians
// t: seconds; if it moves, that animates it# In a terminal, in the game's folder
fogon assets add totem-runico
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["totem-runico"] })
# It lands in client/assets/totem-runico.ts
# https://fogon.page/object/totem-runico