Library repo 👉 click here


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()

Add nil-launchpad.js in your file

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 }