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


Babel is required to process package imports. If you need a different preprocessor remove all packages first.

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


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.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.


                <body class="grey lighten-3">
  <div class="container flow-text">
    <div class="card center-align">
      <div class="card-content yellow lighten-5 purple-text text-darken-3">
        <p>Click to hear the word:</p>
        <a id="play" class="btn-floating btn-large waves-effect waves-light teal"><i class="large material-icons">play_arrow</i></a>
        <div id="sentence">Example: </div>
        <p>Type your spelling:</p>
        <div class="input-field" id="response">
            <div class="row">
              <input placeholder="Type your word here" id="responseText" class="col s12 m8" type="text" autofocus autocomplete="off">
              <div class="col s12 m4 center">
                <button id="enter" class="btn waves-effect waves-light" type="submit" name="action"><i class="material-icons left">input</i>enter</button>
        <div class="divider"></div>
        <div id="scoreboard" class="row">
          <div id="scoreBoxes" class="col s12 m6">
            <span id="rightScore" class="score">0</span>
            <span id="wrongScore" class="score">0</span>
            <span id="recordScore" class="score">0</span>
          <div id="resetBox" class="col s12 m6">
            <span>RESET HIGH SCORE: <a id="resetScore" class="waves-effect waves-light btn little"><i class="material-icons">restore</i></a></span>
        <div class="divider"></div>
        <div class="row">
          <div class="col s10 m6 offset-m2 underline left-align">Give me a hint</div>
          <div class="col s1">
            <a id="hint" class="waves-effect waves-light btn little"><i class="material-icons">help</i></a></div>
        <div class="row">
          <div class="col s10 m6 offset-m2 underline left-align">Skip this word</div>
          <div class="col s1">
            <a id="skip" class="waves-effect waves-light btn little"><i class="material-icons">skip_next</i></a></div>
        <div class="row">
          <div class="col s10 m6 offset-m2 underline left-align">Restart list</div>
          <div class="col s1">
            <a id="restart" class="waves-effect waves-light btn little"><i class="material-icons">replay</i></a></div>
        <div class="row">
          <div class="col s10 m6 offset-m2 underline left-align">Jump to a different word</div>
          <div class="col s1">
            <a id="jump" class="waves-effect waves-light btn little modal-trigger" data-target="jumpWord"><i class="material-icons">fast_forward</i></a></div>
        <div class="row">
          <div class="col s10 m6 offset-m2 underline left-align">Show my study list</div>
          <div class="col s1">
            <a id="study" class="waves-effect waves-light btn little"><i class="material-icons" href="#studyList">library_books</i></a></div>
        <h6>Andy Bonner, 2016.&nbsp;
          <a id="aboutLink" class="modal-trigger" href="#about">ABOUT</a>
        <div id="jumpWord" class="modal">
          <div class="modal-content">
            <p class="left-align">
              Slide the dot to jump to a point on the list:
            <form id="wordJumper" action="#">
              <div class="row">
                <div class="col s10">
                  <p class="range-field">
                    <input type="range" id="jumpSlider" min="1" max="228">
                <div class="col s2">
                  <button id="jumpGo" class="waves-effect waves-light btn little modal-close" type="submit">go</button>
            <span>... or jump to a random word:</span>
            <a id="random" class="waves-effect waves-light btn little"><i class="modal-action modal-close material-icons">shuffle</i></a>
        <div id="studyList" class="modal">
          <div class="modal-content">
            <div class="right"><i class="modal-close material-icons">close</i></div>
            <p class="left-align">These are the words that you've gotten wrong or skipped sometimes. You can study them, click the "x" beside each one to remove it from the list, or... <a id="studyClear" class="waves-effect waves-light btn little"><i class="material-icons">delete</i>clear the list</a></p>
            <div id="studyListWords" class="left-align chips"></div>
        <div id="about" class="modal">
          <div class="modal-content">
            <div class="right"><i class="modal-close material-icons">close</i>
            <ul class="left-align browser-default">
              <li>Written by <a href="">Andy Bonner</a>, 2016</li>
              <li>Words from the <a href="">Scripps National spelling bee</a> 2016 first grade word list</li>
              <li>Audio and usage examples from the <a href="">Merriam-Webster Collegiate Dictionary</a>, patched with the <a href="">HTML 5 Web Speech API</a> as
              <li>Designed with <a href="">Materialize</a></li>


                #sentence {
  font-style: italic;
  font-size: .75em;

#scoreboard {
  font-size: 0.7em;
  margin: 2px;

.score {
  margin: 2px;
  padding: 2px;
  border: 1px solid #6a1b9a;
  border-radius: 5px;

span {
  white-space: nowrap;

.divider {
  margin: 10px;

.little {
  height: 24px;
  line-height: 24px;
  padding: 0 0.5rem;

.underline {
  border-style: none none solid none;
  border-width: 1px;
  border-bottom-color: #e0e0e0;

.valign-wrapper {
  vertical-align: middle;

#studyListWords {
  margin: 5px;

ul {
  padding-left: 1.75em;


                if ($(window).width() >= 600) { //layout alignment based on screen size

var spellingList = ["fit",
    "center", //or "centre"
    "hiccups", //or hiccoughs

  // For a few words, the Merriam-Webster API returns audio for something other than the desired word, e.g. "Gila monster" for "monster." I just hard-code them as exceptions here.
  problemList = [

  // Booleans to be toggled if these APIs prove non-functional
  hasTTS = true,
  hasStorage = true;

if ('speechSynthesis' in window === false) {
  hasTTS = false;
  alert("I'm sorry, your browser doesn't support a text-to-speech feature, so some words will be skipped. To access all the words, try Chrome v 33 or higher, Firefox v 49 or higher,  Safari v 7 or higher, or Microsoft Edge v 38 or higher.");

// Words spelled wrong or skipped will be pushed to this array, and ultimately to the #studyList modal
var studyListArray = [];

if (!window.localStorage) {
  hasStorage = false;
  alert("Sorry, we can't remember which word you're on when you leave this page. The next time you come back or reload the page it will start from the beginning.");
} else if (localStorage.currentWordIndex === undefined) {
  localStorage.currentWordIndex = 0;
  localStorage.recordScore = 0;

if (!window.localStorage) {
} else if (localStorage.studyListArray === undefined) {
  localStorage.studyListArray = ""; //everything in storage is converted to a string
} else if (localStorage.studyListArray.length > 0) {
  studyListArray = localStorage.studyListArray.split(",");

// Safari in Private Browsing mode still shows localStorage as available, but sets its capacity to 0. So a check: after all that above, if the key studyListArray is still undefined, localStorage must be functionally unavailable.
if (localStorage.studyListArray === undefined) {
  hasStorage = false;

// Load current word and record high score from localStorage, or start them from scratch
var currentWordIndex;
if (hasStorage) {
  currentWordIndex = localStorage.currentWordIndex;
} else {
  currentWordIndex = 0;
var recordCount;
if (hasStorage && Number(localStorage.recordScore) >= 0) {
  recordCount = Number(localStorage.recordScore);
} else {
  recordCount = 0;
var word = "",
    usingTTS = false,
    audioURL = "",
    hintLetter = 1,
    chip = {
  tag: 'chip content',
  id: 1, //optional

// Often the API takes several seconds to return, so it's possible to submit an answer and have the app move on to expect the "new" word, but the play button is still playing the "old" word. So here's a function to disable the button and give it a loading animation, to be called in updateWord() below.
function preloader() {
  $("#play").addClass("disabled").html('<div class="preloader-wrapper small active valign-wrapper"><div class="spinner-layer spinner valign"><div class="circle-clipper left"><div class="circle"></div></div><div class="gap-patch"><div class="circle"></div></div><div class="circle-clipper right"><div class="circle"></div></div></div></div>');
  loadaling = true;

// the function to re-enable the button and remove the animation is included in the "success" function of the API call
function stopLoader() {
  $("#play").removeClass("disabled").html('<i class="large material-icons">play_arrow</i>');
  loadaling = false; //

var updateWord = function() {
  word = spellingList[currentWordIndex];
  localStorage.currentWordIndex = currentWordIndex;
  hintLetter = 1; // resetting the "hint" function to the beginning of the new word

// Here's where the magic happens.
function getMerriamWebster() {
  usingTTS = false; // reset in case the last word made it true
    url: "" + word + "?key=9ef9d420-7fba-449f-9167-bd807480798e", //maybe remove the cors-anywhere once I get this off Codepen
    type: "GET",
    contentType: "text/plain",
    xhrFields: {
      withCredentials: false
    dataType: "xml",
    success: function(data) {
      var audioFilename = $(data).find("hw:contains(" + word + ") ~ sound wav").html();
      // look through the XML response for a "<hw>" element with the desired word, with a sibling <sound> and harvest its <wav>. Cause that seems to be how the API is set up... most often. Except when it's not (gila monster!).
      console.log("wav =" + audioFilename);
      // If that doesn't yield a usable result--including if it yields a gila monster--then turn to HTML 5 SpeechSynthesis
      if (audioFilename === undefined || problemList.includes(word)) {
        if (hasTTS === false) {
        } else {
          usingTTS = true;
          console.log("using TTS");
          tts = new SpeechSynthesisUtterance(word);
      } else {
        var subdir = "";
        // the API specifies some edge cases for their audio directory structure
        if (audioFilename.startsWith("bix")) {
          subdir = "bix/";
        } else if (audioFilename.startsWith("gg")) {
          subdir = "gg/"
        } else if (parseInt(audioFilename.charAt(0)) === "number") {
          subdir = "number/"
        } else {
          subdir = audioFilename.charAt(0) + "/";
        audioURL = "" + subdir + audioFilename;
        audio = new Audio(audioURL);
      // to generate the "example": .text() instead of .html() gets around the fact that sometimes the word itself is interrupted by tags. Unfortunately, it returns all the instances, so we have to filter by .first(). Even if the first one isn't always the best... Too bad there's no $.best().
      var sentence = $(data).find("vi:contains(" + word + ")").first().text();
      if (sentence === undefined || sentence.length < 1) {
        sentence = "sorry, unavailable";
      $("#sentence").html("Example: " + sentence.replace(word, "_____"));
    error: function() {
      alert("I'm sorry, there was an error. Try reloading?");

// increment the "right" counter
function updateRight() {
  var rightCount = $("#rightScore").html();

// increment the "wrong" counter, AND push the misspelled word to the study list
function updateWrong() {
  var wrongCount = $("#wrongScore").html();
  if (studyListArray.includes(word)) {
    //do nuttin
  } else {
    localStorage.studyListArray = studyListArray;

// increment the record high score
function updateRecord() {
  if (hasStorage) {
    recordCount = Number(localStorage.recordScore);
    localStorage.recordScore = recordCount;
  } else {

// function to play the audio
$("#play").click(function() {
  if (loadaling === false) {
    if (usingTTS === true) {
    } else {;
  } else {
    //do nuttin

// function to process submitted answer
$("#response").submit(function(event) {
  var response = $("#responseText").val();
  if (word === spellingList[spellingList.length - 1]) {
    Materialize.toast("Yay!! You made it to the end of the list! Now you can click the 'restart' button to start again at the beginning.", 4000, "rounded");
  } else if (response === word) {
    Materialize.toast("That's right! You rock! Now click to hear the next word.", 4000, "rounded");
  } else {
    Materialize.toast("Sorry, that's not right. Try again!", 4000, "rounded");

$("#resetScore").click(function() {
  localStorage.recordScore = 0;
  recordCount = 0;

$("#hint").click(function() {
  Materialize.toast("It starts with: " + word.substring(0, hintLetter), 4000, "rounded");

// I pulled out the skip function, minus the part about saving to localStorage, to let the app skip TTS words when TTS is unavailable
function skipWord() {

$("#skip").click(function() {
  localStorage.studyListArray = studyListArray;

$("#restart").click(function() {
  currentWordIndex = 0;

$("#random").click(function() {
  currentWordIndex = Math.floor((Math.random() * 100) + 1);

// open the study list modal, and build its content
$("#study").click(function() {
  for (var i = 0, limit = studyListArray.length; i < limit; i++) {
    var studyWord = studyListArray[i];
    if (studyWord === "undefined" || studyWord.length < 1 || $("#studyListWords").find("#" + studyWord).length) {
    } else {
      $("#studyListWords").append('<div id="' + studyWord + '" class="chip">' + studyWord + '<i class="close material-icons">close</i></div>');

$("#jump").click(function() {

$("#wordJumper").submit(function(event) {
  currentWordIndex = document.getElementById("jumpSlider").value - 1;

$("#aboutLink").click(function() {

// remove words from study list
$("#studyListWords").on("click", ".chip", function() {
  studyListArray.splice(studyListArray.indexOf(, 1);
  localStorage.studyListArray = studyListArray;

// clear study list
$("#studyClear").click(function() {
  studyListArray = [];
  localStorage.studyListArray = "";

// kick it off

$(document).ready(function() {
  // the "href" attribute of .modal-trigger must specify the modal ID that wants to be triggered