Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation


Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

              <body id="body">
  <form id="form">
    <input autocomplete="off" id="input" type="text">
    <button id="button">Go</button>
  <svg version="1.1" baseProfile="full" height="100%" id="canvas" width="100%" xmlns="http://www.w3.org/2000/svg">
    <rect id="bg" fill="transparent" />
    <g id="constellation"></g>

              :root {
  --bg-color: #213155;
  --fg-color: #fff;

body {
  background-color: var(--bg-color);
  font-size: 0;
  height: 100vh;
  margin: 0;
  padding: 0;
  width: 100vw;

#form {
  align-items: center;
  display: flex;
  font-size: 1.5rem;
  justify-content: center;
  padding: 1rem 0;
  position: absolute;
  top: 0;
  width: 100vw;

#input {
  background-color: transparent;
  border: 0;
  border-bottom: 0.5px solid var(--fg-color);
  color: var(--fg-color);
  font-family: "Crimson Text", serif;
  font-size: 1.5rem;
  line-height: 2;
  width: 60ch;

#input:active {
  outline: 0.5px dotted var(--fg-color);

#button {
  border: 1px solid var(--fg-color);
  background: transparent;
  border-radius: 0.2em;
  color: var(--fg-color);
  font-family: "Crimson Text", serif;
  font-size: 1rem;
  margin-left: 1rem;
  padding: 0.5rem 1rem;

#button:active {
  background: var(--fg-color);
  color: var(--bg-color);

#button:focus {
  outline: 0.5px dotted var(--fg-color);

              // Grab the window size and set the size of the canvas to it
const canvasHeight = body.clientHeight;
const canvasWidth = body.clientWidth;

bg.setAttribute("height", canvasHeight);
bg.setAttribute("width", canvasWidth);

 * Adds a line segment
 * @param {number} x1 - x value of origin point
 * @param {number} x2 - x value of destination point
 * @param {number} y1 - y value of origin point
 * @param {number} y2 - y value of destination point
const addLine = (x1, x2, y1, y2) => {
  const attrs = { x1, x2, y1, y2, stroke: "white", "stroke-width": 0.5 };

  renderElement("line", attrs);

 * Adds point
 * @param {number} cx - x value of point’s centre
 * @param {number} cy - y value of point’s centre
 * @param {number} r - radius of desired point
const addPoint = (cx, cy, r) => {
  const attrs = { cx, cy, r, fill: "white" };

  renderElement("circle", attrs);

 * Adds star. Heavily, er, ‘influenced’ by:
 * https://dillieodigital.wordpress.com/2013/01/16/quick-tip-how-to-draw-a-star-with-svg-and-javascript/
 * @param {number} cx - x value of star’s centre
 * @param {number} cy - y value of star’s centre
 * @param {number} arms - number of points on the star
 * @param {number} radius - outer radius of star
const addStar = (cx, cy, arms, radius) => {
  let results = "";
  const angle = Math.PI / arms;

  for (let i = 0; i < arms * 2; i++) {
    // Alternate between outer and inner radius
    const r = i % 2 === 0 ? radius : radius / 2;

    const currX = cx + Math.cos(angle * i) * r;
    const currY = cy + Math.sin(angle * i) * r;

    results += `${currX} ${currY} `;

  const attrs = { points: results, fill: "white" };

  renderElement("polygon", attrs);

// Push the possible angles on to an array (having the angles be *truly* random
// might be a bit much). We’re doing this in the global scope because
// we only need to do this once.
let angles = [];
const interval = (Math.PI * 2) / 9;

for (let i = 0; i < 9; i++) {
  angles.push(interval * i);

 * Append line segment and point to path
 * @param {number} x1 - x value of line segment’s origin
 * @param {number} y1 - y value of line segment’s origin
 * @param {number} dist - distance from previous point
 * @param {number} r - radius of point to add
 * @returns {array} - position of added point
const appendPoint = (x1, y1, dist, r) => {
  // Choose random angle from the possibilities we declared in the global scope
  const index = Math.floor(Math.random() * 9);

  // TBH I just grabbed these from our star-rendering function
  // and didn’t bother to check the trigonometry. Seems legit, though.
  const x2 = x1 + Math.cos(angles[index]) * dist;
  const y2 = y1 + Math.sin(angles[index]) * dist;

  addLine(x1, x2, y1, y2);
  addPoint(x2, y2, r);

  return [x2, y2];

 * Clear the canvas by blowing out the children we handily grouped earlier
 * and appending a new empty group
const clearCanvas = () => {

  const newGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
  newGroup.setAttribute("id", "constellation");


 * Take a string and add the appropriate star, line segments, and points
 * @param {string} string - string to constellate
const drawConstellation = string => {

  const arr = string.split(" ");

  // ‘Factor’ is probably a bad thing to call these numbers;
  // but since we need to draw a star based on the first word
  // and the first point based on the second word, we have to do this
  // outside our loop
  const factor1 = arr[0].length;
  const factor2 = arr[1].length;

  // Add the star at the centre point of the canvas for the first word
  addStar(canvasWidth / 2, canvasHeight / 2, 10, factor1 * 3);

  // Add the line segment and point for the second word, which requires
  // sending the centre point of the canvas. 20 and 2 are Magic Multipliers™
  // that just happen to look good.
  let nextPoints = appendPoint(
    canvasWidth / 2,
    canvasHeight / 2,
    factor2 * 20,
    factor2 * 2

  // Loop over the rest of the words
  for (let i = 2; i < arr.length; i++) {
    // Here I go with ‘factor’ and the Magic Multipliers™ again
    const factor = arr[i].length;

    nextPoints = appendPoint(
      factor * 20,
      factor * 2

 * Utility function to append a child with the desired attributes
 * to our child group
 * @param {string} type - type of SVG element to append
 * @param {object} attrs - key-value pairs for SVG attributes to add
const renderElement = (type, attrs) => {
  const el = document.createElementNS("http://www.w3.org/2000/svg", type);

  for (const attr in attrs) {
    if (attrs.hasOwnProperty(attr)) {
      el.setAttribute(attr, attrs[attr]);


// Let’s not be boring and just have *one* starter string....
const starterStrings = [
  "Now is the time for all good men to come to the aid of their country.",
  "Jackdaws love my big sphinx of quartz.",
  "On the whole it wasn’t the small green pieces of paper that were unhappy.",
  "Never gonna give you up; never gonna let you down.",
  "You must always blow on the pie."

 * Initialise the application
const init = () => {
  const string =
    starterStrings[Math.floor(Math.random() * starterStrings.length)];

  document.getElementById("input").value = string;



// Wire up interactivity
const form = document.getElementById("form");
const input = document.getElementById("input");

form.addEventListener("submit", event => {
  // Prevent reloading the page (and thus blowing out our input)
  // on form submit


🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.