Use index.html to inject metadata into your collection
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="./style.css">
<script src="./metadata.js"></script>
<!-- if you need to import additional js scripts do it here -->
</head>
<body>
<!-- WEBPACK will inject the bundle.js here -->
</body>
</html>
Use index.js to manage traits
import * as context from "./nil-launchpad.js"
import p5 from 'p5'
/**
* Define your project traits (optional)
*
* Following example shows how to compute two correlated traits.
* colorTrait constant defines both traits: backgroundColor and lineColor.
*
* If you would like to setup traits with independent values, random number generator
* needs to be called for each of them separately.
*/
// Define rarity of different values based on a random number
const backgroundColorFn = (n) => {
if (n <= 0.5) { // 50%
return 'gold'
} else { // 50%
return 'silver'
}
}
const lineColorFn = (n) => {
if (n <= 0.1) { // 10%
return 'green'
} else
if (n <= 0.3) { // 20%
return 'blue'
} else { // 70%
return 'white'
}
}
// Adding multiple traits in one call will use the same random number for all of them.
// This way it's possible to have a correlated set of traits, eg. matching colours.
const [backgroundColor, lineColor] = context.addTraits(
['backgroundColor', 'lineColor'], // names how they are going to be seen in metadata
[backgroundColorFn, lineColorFn] // functions returning values based on a random number between 0 and 1
)
// Individual traits can be added as follows:
// const someTrait = context.addTrait('some trait', (n) => `value of ${n}`)
/**
* Rendering metadata
*
* This fragment is required because it is rendering metadata for marketplaces compatility.
*/
context.renderMetadata()
/**
* Define your rendering logic
*/
const margin = 25
window.windowResized = () => {
const { width, height } = context.getDimensions()
resizeCanvas(width, height)
}
window.setup = () => {
const { width, height } = context.getDimensions()
createCanvas(width, height)
noLoop()
strokeWeight(2)
}
window.draw = () => {
background(backgroundColor)
stroke(lineColor)
noFill()
rect(margin, margin, width - margin * 2, height - margin * 2)
for (let y = margin*2; y < height - margin * 2; y += 25) {
drawLine(y)
}
}
const drawLine = (lineY) => {
const range = map(lineY, margin * 2, height - margin * 2, 0, 50)
let prevX = margin * 2
let prevY = lineY
const lineSpacing = 10
for (let x = prevX + lineSpacing; x <= width - margin * 2; x += lineSpacing) {
const y = lineY + random(-range, range)
line(prevX, prevY, x, y)
prevX = x
prevY = y
}
}
new p5()
const randomGenerator = (seed) => {
// TODO: validate seed length
const hashes = seed.match(/.{1,16}/g).map(r => parseInt(r, 16))
const sfc32 = (a, b, c, d) => {
return () => {
a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0;
var t = (a + b) | 0;
a = b ^ b >>> 9;
b = c + (c << 3) | 0;
c = (c << 21 | c >>> 11);
d = d + 1 | 0;
t = t + d | 0;
c = c + t | 0;
return (t >>> 0) / 4294967296;
}
}
const generator = sfc32(...hashes)
const randomNumberInRange = (start = 0, end = 1) => (
generator() * (Math.abs(start) + Math.abs(end)) + start
)
return randomNumberInRange
}
const randomSeed = (size = 64) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('')
/**
* fitInBox
* Constrains a box (width x height) to fit in a containing box (maxWidth x maxHeight), preserving the aspect ratio
* @param maxWidth width of the containing box
* @param maxHeight height of the containing box
* @param ratio
* @return {width, height} of the resized box
*/
const fitInBox = (maxWidth, maxHeight, ratio) => {
let viewportRatio = maxWidth / maxHeight,
width = 0,
height = 0
if (ratio == undefined || ratio == null || ratio == 0) {
width = maxWidth
height = maxHeight
} else if (ratio <= viewportRatio) {
height = maxHeight
width = Math.floor(height * ratio)
} else {
width = maxWidth
height = Math.floor(width / ratio)
}
return {
width: width,
height: height
}
}
const getDimensions = () => {
const maxWidth = document.body.clientWidth
const body = document.body,
html = document.documentElement
const maxHeight = Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight
)
const { ratio } = getMetadata()
return fitInBox(maxWidth, maxHeight, ratio)
}
const params = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
})
const { nilSeed, preview } = params
// Randomise seed
const seed = nilSeed || (preview ? randomSeed() : null)
if (!seed) {
throw ('seed not defined')
}
const random = randomGenerator(seed)
const attributes = []
const addTrait = (name, traitFunction, randomNumber = random()) => {
const value = traitFunction(randomNumber)
attributes.push({
trait_type: name,
value
})
return value
}
const addTraits = (names, functions) => {
if (typeof functions === Function) {
return addTrait(names, functions)
}
if (names.size != functions.size) {
throw('Provide names for all the trait functions. Both arrays should have the same length.')
}
const randomNumber = random()
return names.map((name, idx) => addTrait(name, functions[idx], randomNumber))
}
const getMetadata = () => {
if (!window.nil || !window.nil.metadata) {
throw "Project metadata not defined"
}
return {
...window.nil.metadata,
attributes,
}
}
const renderMetadata = () => {
const metadata = getMetadata()
const meta = document.createElement('meta')
meta.name = "nil-metadata"
meta.content = JSON.stringify(metadata)
document.getElementsByTagName('head')[0].appendChild(meta)
}
export { random, addTraits, renderMetadata, getDimensions }