0x983f10b6…313dsent to0xac521ea7…fb1f·#19,245,564·view on Etherscan
const project = "Time Between the Lines is Thread Through the Mind";
console.log(`${project} v5.0 © Matto 2024`);
console.log(`TOKEN ENTROPY: ${tokenData.hash}`);
let skeuomorphic = tokenData.plot == true ? false : true;
const tokenId = tokenData.tokenId;
const attributes = {};
const urlParams = new URLSearchParams(window.location.search);
const customSig = urlParams.get("signature");
let customOp = urlParams.get("optimization");
if (customOp) {
customOp = customOp.toUpperCase();
}
const altMode = urlParams.get("alternate");
const skeuMode = urlParams.get("skeuomorphic");
let modeDesc = ["SKEUOMORPHIC", "SIMPLE"];
let spreadDesc = ["MINIMUM", "SOME", "MORE", "MAXIMUM"];
let penSuggestion = [];
if (skeuMode == "true") {
skeuomorphic = true;
console.log(`CUSTOM MODE: ${modeDesc[0]}`);
} else if (skeuMode == "false") {
skeuomorphic = false;
console.log(`CUSTOM MODE: ${modeDesc[1]}`);
} else {
console.log("DEFAULT MODE: " + (skeuomorphic ? modeDesc[0] : modeDesc[1]));
}
console.log("");
let seed = makeSeed(tokenData.hash);
let nonce = 0;
let showSignature = customSig == "false" ? false : true;
let circlesCount = 0;
const numGroups = selectByProbs([5,6,4,7,3], fractionalRand());
const m = (rand(0, 200) / 100) - 1;
let svg, sig;
let groupedLines = "";
let texturedLines = "";
let gappedPoints = [];
let palettes = [];
let points = [];
const allOptDesc = ["ANTI", "LAPS", "STANDARD", "MAXIMUM"];
const defaultOpt = selectByProbs([3, 2, 1, 0], fractionalRand());
let optimization = allOptDesc.indexOf(customOp);
if (optimization === -1) {
optimization = defaultOpt;
}
let optDesc = allOptDesc[optimization];
let dark = selectByProbs([0, 1], fractionalRand());
if (altMode == "true") {
console.log("Alternate Mode selected, switching paper and pens.");
dark = dark == 1 ? 0 : 1;
}
let perpGap = selectByProbs([true, false], fractionalRand());
const bkg = dark == 1 ? 22 : 250;
const penCollection = dark == 0 ? "Stabilo" : "Gelly Roll";
const paletteArray = [
[
{ hex: "#83553b", name: "Brown", id: 45 },
{ hex: "#d74143", name: "Dark Red", id: 50 },
{ hex: "#f75c45", name: "Carmine", id: 48 },
{ hex: "#ff8b15", name: "Pale Vermillion", id: 30 },
{ hex: "#ffb814", name: "Orange", id: 54 },
{ hex: "#ffd913", name: "Yellow", id: 44 },
{ hex: "#fff248", name: "Lemon Yellow", id: 24 },
{ hex: "#b7c800", name: "Light Green", id: 33 },
{ hex: "#4c8b06", name: "Leaf Green", id: 43 },
{ hex: "#1c6a27", name: "Green", id: 36 },
{ hex: "#3d47b3", name: "Ultramarine", id: 32 },
{ hex: "#3074e5", name: "Dark Blue", id: 41 },
{ hex: "#3093f8", name: "Light Blue", id: 31 },
{ hex: "#58b6ff", name: "Azure", id: 57 },
{ hex: "#c9c4c4", name: "Light Cold Gray", id: 94 },
{ hex: "#a59da5", name: "Medium Cold Gray", id: 95 },
{ hex: "#665e61", name: "Dark Gray", id: 96 },
{ hex: "#2d2828", name: "Black", id: 46 },
],
[
{ hex: "#E9E9E9", name: "Platinum", id: 50 },
{ hex: "#85CFF5", name: "Pale Azure", id: 425 },
{ hex: "#7696DC", name: "Vista Blue", id: "437p" },
{ hex: "#2A6CD0", name: "Celtic Blue", id: 438 },
{ hex: "#109EE0", name: "Celestial Blue", id: 436 },
{ hex: "#97DCD6", name: "Tiffany Blue", id: "426p" },
{ hex: "#28DBD5", name: "Turquoise", id: 429 },
{ hex: "#85D18A", name: "Emerald", id: 428 },
{ hex: "#E1EC7A", name: "Mindaro", id: "403p" },
{ hex: "#E7B968", name: "Earth Yellow", id: "405p" },
{ hex: "#D0866D", name: "Burnt Sienna", id: 412 },
{ hex: "#DB5D6E", name: "Indian Red", id: 419 },
{ hex: "#CE6ABC", name: "Sky Magenta", id: 421 },
{ hex: "#9774D3", name: "Amethyst", id: 424 },
{ hex: "#AB98E0", name: "Wisteria", id: 423 },
{ hex: "#D2AFDB", name: "Lilac", id: "420p" },
{ hex: "#838A93", name: "Slate Gray", id: 444 },
{ hex: "#513E3A", name: "Van Dyke", id: 417 },
],
[
{ hex: "#EDECE4", name: "Metallic Silver", id: 553 },
{ hex: "#FFD062", name: "Metallic Gold", id: 551 },
{ hex: "#F8BEB6", name: "Metallic Red", id: 519 },
]
];
if (dark == 1 && numGroups == 3) {
dark = 2;
}
const startingColor = rand(0, paletteArray[dark].length - 1);
const paletteSpread = dark < 2 ? selectByProbs([1,2,3,4], fractionalRand()) :
1;
console.log("EXTENDED ATTRIBUTES:");
for (let i = 0; i < numGroups + 1; i++) {
palettes[i] = paletteArray[dark][(startingColor + (i * paletteSpread)) % paletteArray[dark].length];
let name = `Group ${i + 1} Color`;
if (i == numGroups) {
name = "Signature Color";
}
penSuggestion[i] = `Suggested Pen Color: ${penCollection} #${palettes[i].id}`;
console.log(`${name}: ${palettes[i].name} (${palettes[i].hex}) - ${penSuggestion[i]}`);
}
addAttribute("COLOR GROUPS", `${numGroups}`);
addAttribute("COLOR SPREAD", spreadDesc[paletteSpread - 1]);
addAttribute("PAPER", dark > 0 ? "DARK" : "LIGHT");
addAttribute("COMPOSITION", perpGap ? "BISECTED" : "WHOLE");
addAttribute("SLOPE", -1 * m.toFixed(2));
addAttribute("OPTIMIZATION", optDesc);
const svgStart = `<?xml version="1.0" encoding="utf-8"?><svg viewBox="0 0 1000 1000" style="background-color:rgb(${bkg},${bkg},${bkg})" xmlns="http://www.w3.org/2000/svg">`;
const suggestedThickness = "Suggested line thickness: plot side length (mm) divided by 400.";
draw();
function setup() {
noCanvas();
noLoop();
}
function draw() {
const ySpacing = 2.6 - (1 - Math.abs(m)) / 3;
let lineNum = 0;
for (let b = -2000; b < 2000; b += ySpacing ) {
const x0 = Math.max(0, -b / m);
const x1 = Math.min(1000, (1000 - b) / m);
const y0 = b + x0 * m;
const y1 = b + x1 * m;
const clipped = getClippedLine(x0, y0, x1, y1);
if (clipped) {
lineWithGaps(clipped.x0, clipped.y0, clipped.x1, clipped.y1, lineNum);
}
lineNum++;
}
// 0: anti, 1: laps (handled elsewhere), 2: normal, 3: maximum
if (optimization == 0) {
points = sortPointPairs(points, true, false);
} else if (optimization == 2) {
points = sortPointPairs(points, false, true);
} else if (optimization == 3) {
points = sortPointPairs(points, true, true);
}
const linesPerGroup = Math.ceil(points.length / numGroups);
let pointIndex = 0;
for (let i = 0; i < numGroups; i++) {
groupedLines += G(i + 1, palettes[i].hex, 2.5, palettes[i].name, penSuggestion[i]);
texturedLines += G(i + 1, palettes[i].hex, 1.5, palettes[i].name, penSuggestion[i]);
for (let j = 0; j < linesPerGroup && pointIndex < points.length; j++) {
if (isNaN(points[pointIndex].y2)) {
break;
}
groupedLines += P([points[pointIndex].x1, points[pointIndex].y1, points[pointIndex].x2, points[pointIndex].y2]);
if (skeuomorphic) {
texturedLines += line2dots(points[pointIndex].x1, points[pointIndex].y1, points[pointIndex].x2, points[pointIndex].y2);
}
pointIndex++;
}
groupedLines += "</g>";
texturedLines +="</g>";
}
console.log(`Background Line Segments: ${pointIndex}`);
if (skeuomorphic) {
console.log(`Composing Circles: ${circlesCount}`);
}
console.log(suggestedThickness);
console.log("License: CC BY-NC 4.0");
sig = signature();
svg = skeuomorphic ? svgStart + texturedLines : svgStart + groupedLines;
updateSVG();
}
function signature() {
let sigLinePoints = [
[924,956,920,956],
[920,956,920,860],
[920,860,940,872],
[940,872,960,860],
[960,860,960,956],
[960,956,956,956],
[928,902,940,872],
[940,872,952,902],
[934,888,946,888],
[920,902,960,902],
[932,902,932,926],
[948,902,948,926]
];
let str;
if (skeuomorphic) {
str = G("signature", palettes[numGroups].hex, 1.5, palettes[numGroups].name, penSuggestion[numGroups]);
for (let i=0; i<sigLinePoints.length; i++) {
str += line2dots(sigLinePoints[i][0], sigLinePoints[i][1], sigLinePoints[i][2], sigLinePoints[i][3]);
}
str += circle2dots(940, 940, 15);
} else {
str = G("signature", palettes[numGroups].hex, 2.5, palettes[numGroups].name, penSuggestion[numGroups]);
str += `<polyline points="924,956 920,956 920,860 940,872 960,860 960,956 956,956" stroke-linecap="round" stroke-linejoin="round"/>`;
str += `<polyline points="928,902 940,872 952,902" stroke-linejoin="bevel" />`;
str = str + P(sigLinePoints[8]) + P(sigLinePoints[9]) + P(sigLinePoints[10]) + P(sigLinePoints[11]);
str += C([940, 940, 15]);
}
str += "</g>";
return str;
}
function G(groupNum, hex, strokeW, paletteName, penSuggestion) {
return `<g id="group-${groupNum}-${hex}-${paletteName}" style="stroke:${hex}; stroke-width: ${strokeW}px; stroke-opacity:1; fill-opacity:0"><desc>${paletteName} - ${penSuggestion}. ${suggestedThickness}</desc>`;
}
function P(vars) {
return `<path d="M ${vars[0]} ${vars[1]} L ${vars[2]} ${vars[3]}" stroke-linecap="round" />`
}
function C(vars) {
return `<circle cx="${vars[0]}" cy="${vars[1]}" r="${vars[2]}" />`;
}
function crossesBisector(x1, y1, x2, y2) {
const xIntersection = ((500 + 500 / m + m * x1 - y1) / (m + 1 / m));
const yIntersection = 500 - (1 / m) * (xIntersection - 500);
return xIntersection >= Math.min(x1, x2) && xIntersection <= Math.max(x1, x2) &&
yIntersection >= Math.min(y1, y2) && yIntersection <= Math.max(y1, y2);
}
function addAttribute(trait_type, value) {
console.log(`${trait_type}: ${value}`);
attributes[trait_type] = value;
}
function makeSeed(input) {
let s = 1;
for (let i = 0; i < (input.length - 2) / 2; i++) {
let v = parseInt(input.slice(2 + i * 2, 4 + i * 2), 16);
s = ((s * v) % 999999999999) + 1
}
return s;
}
function fractionalRand() {
return rand(1,100)/100;
}
function rand(min, max) {
const range = max - min + 1;
const v = (seed % range) + min;
newSeed();
return Math.floor(v);
}
function newSeed() {
nonce++;
let bigSeed = BigInt(seed);
const bigNonce = BigInt(nonce);
const bigMultiplier = BigInt(35932678341237);
const bigModulus = BigInt(999999999999);
bigSeed = (bigSeed * bigMultiplier + bigNonce) % bigModulus + BigInt(1);
if (bigSeed <= Number.MAX_SAFE_INTEGER) {
seed = Number(bigSeed);
} else {
console.log("Seed exceeded safe range, reseeding...");
seed %= 999999999999;
}
}
document.addEventListener("keydown", function (event) {
if (!tokenData.plot) {
const k = event.key.toUpperCase();
if (k === "H") {
console.log(`Toggling signature, please be patient. ${circlesCount} circles need to be redrawn...`);
showSignature = !showSignature;
document.body.innerHTML = '';
updateSVG();
} else if (k === "S") {
let mS = skeuomorphic ? modeDesc[0] : modeDesc[1];
console.log(`Saving ${mS}-mode SVG with ${optDesc} Optimization...`)
saveStrings([assembleComp() + "</svg>"], `TBTLITTTM-${tokenId}-${mS}-${optDesc}`, "svg");
}
}
});
function saveStrings(data, filename, extension) {
const blob = new Blob([data], {type: 'text/plain'});
let anchor = document.createElement('a');
anchor.download = `${filename}.${extension}`;
anchor.href = window.URL.createObjectURL(blob);
anchor.style.display = 'none';
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
}
function assembleComp() {
let comp = svg;
if (showSignature) {
comp += sig;
}
return comp;
}
function updateSVG() {
document.body.insertAdjacentHTML('beforeend', assembleComp() + "</svg>");
}
function selectByProbs(list, randomValue) {
const totalWeight = list.reduce(function(acc, item, index) {
return acc + (1 / (index + 2));
}, 0);
const threshold = totalWeight * randomValue;
let currentSum = 0;
for (let i = 0; i < list.length; i++) {
currentSum += 1 / (i + 2);
if (currentSum >= threshold) {
return list[i];
}
}
return list[list.length - 1];
}
function getY(x0, y0, x1, y1, x) {
const m = (y1 - y0) / (x1 - x0);
const b = y0 - m * x0;
const y = m * x + b;
return y;
}
function lineWithGaps(x0, y0, x1, y1, lineNum) {
let xT, yT, xE, yE;
let tempPoints = [];
if(x0 > x1) {
xT = x0;
yT = y0;
x0 = x1;
y0 = y1;
x1 = xT;
y1 = yT;
}
let xS = x0;
let yS = y0;
let lfg = true;
while (lfg) {
let dS;
let dnm = [[60,4,48,12],[80,12,40,8]];
dS = 0;
let lSeg = rand((1000 / dnm[dS][0]), (1000 / dnm[dS][1]));
let gSeg = rand((1000 / dnm[dS][2]), (1000 / dnm[dS][3]));
xS + lSeg > x1 ? xE = x1 : xE = xS + lSeg;
yE = getY(x0, y0, x1, y1, xE);
if (!perpGap || !crossesBisector(xS, yS, xE, yE)) {
tempPoints.push({x1: xS, y1: yS, x2: xE, y2: yE});
}
xE + gSeg > x1 ? lfg = false : xS = xE + gSeg;
xS = xE + gSeg;
yS = getY(x0, y0, x1, y1, xS);
}
for (let i = 0; i < tempPoints.length; i++) {
if (optimization == 1 && lineNum % 2 == 1) { // laps optimization
const j = tempPoints.length - i - 1;
points.push({x1: tempPoints[j].x2, y1: tempPoints[j].y2, x2: tempPoints[j].x1, y2: tempPoints[j].y1});
} else {
points.push(tempPoints[i]);
}
}
}
function getClippedLine(x0, y0, x1, y1) {
const xmin = rand(1, 100) < 33 ? rand(4, 100) : rand(4, 20);
const ymin = rand(1, 100) < 33 ? rand(4, 100) : rand(4, 20);
const xmax = 1000 - rand(4, 20);
const ymax = rand(1,100) < 33 ? 1000 - rand(4, 60) : 1000 - rand(4, 20);
const inside = (x, y) => x >= xmin && x <= xmax && y >= ymin && y <= ymax;
if (inside(x0, y0) && inside(x1, y1)) {
return { x0, y0, x1, y1 };
}
const m = (y1 - y0) / (x1 - x0);
const b = y0 - m * x0;
const xLeft = xmin;
const yLeft = m * xLeft + b;
const xRight = xmax;
const yRight = m * xRight + b;
const yTop = ymin;
const xTop = (yTop - b) / m;
const yBottom = ymax;
const xBottom = (yBottom - b) / m;
const intersections = [
{ x: xLeft, y: yLeft },
{ x: xRight, y: yRight },
{ x: xTop, y: yTop },
{ x: xBottom, y: yBottom }
];
const validIntersections = intersections.filter(p => inside(p.x, p.y));
if (validIntersections.length >= 2) {
return {
x0: validIntersections[0].x,
y0: validIntersections[0].y,
x1: validIntersections[1].x,
y1: validIntersections[1].y
};
}
return null;
}
function distance(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
function sortPointPairs(pointPairs, allowReverse, optimize) {
const sortedPairs = [pointPairs.shift()];
while (pointPairs.length > 0) {
let selectedPairIndex = 0;
let selectedDistance = optimize ? Infinity : -Infinity;
let reverse = false;
for (let i = 0; i < pointPairs.length; i++) {
const pair = pointPairs[i];
const lastPair = sortedPairs[sortedPairs.length - 1];
const distances = [
distance(lastPair.x2, lastPair.y2, pair.x1, pair.y1),
distance(lastPair.x2, lastPair.y2, pair.x2, pair.y2)
];
if (allowReverse) {
distances.push(
distance(lastPair.x2, lastPair.y2, pair.x2, pair.y2),
distance(lastPair.x2, lastPair.y2, pair.x1, pair.y1)
);
}
distances.forEach((dist, index) => {
if (optimize ? dist < selectedDistance : dist > selectedDistance) {
selectedDistance = dist;
selectedPairIndex = i;
reverse = index % 2 !== 0;
}
});
}
let selectedPair = pointPairs.splice(selectedPairIndex, 1)[0];
if (reverse && allowReverse) {
[selectedPair.x1, selectedPair.x2] = [selectedPair.x2, selectedPair.x1];
[selectedPair.y1, selectedPair.y2] = [selectedPair.y2, selectedPair.y1];
}
sortedPairs.push(selectedPair);
}
return sortedPairs;
}
function line2dots(x1, y1, x2, y2) {
const d = .4;
let texture = "";
const totalDistance = distance(x1, y1, x2, y2);
const steps = totalDistance / d;
const xStep = (x2 - x1) / steps;
const yStep = (y2 - y1) / steps;
for (let i = 0; i <= steps; i++) {
const r = fractionalRand() * .75;
let tempX = x1 + i * xStep;
let tempY = y1 + i * yStep;
if (i === steps) {
tempX = x2;
tempY = y2;
}
texture += C([tempX.toFixed(2), tempY.toFixed(2), r]);
circlesCount++;
}
return texture;
}
function circle2dots(cx, cy, r) {
const d = 0.4;
let texture = "";
const circumference = 2 * Math.PI * r;
const steps = circumference / d;
for (let i = 0; i < steps; i++) {
const theta = (i / steps) * 2 * Math.PI;
const dotX = cx + r * Math.cos(theta);
const dotY = cy + r * Math.sin(theta);
const dotR = fractionalRand() * 0.75;
texture += C([dotX.toFixed(2), dotY.toFixed(2), dotR]);
circlesCount++;
}
return texture;
}