<div id="app"></div>
@import url("//fonts.googleapis.com/css?family=Press+Start+2P");

body {
  font-family: 'Press Start 2P', cursive;
  background:#000; 
  line-height:20px;
}

button {
  background-color: #f4fc67;
  font-family: 'Press Start 2P', cursive;
  box-shadow:2px 2px #000;
  border:1px solid #000;
  padding:3px;
  outline:none;
  font-size:14px;
}

button:hover {
  box-shadow:5px 5px #000;
}

button:focus {
  outline:0 !important;
}

.cs-instructions {
  background-image: url('https://i.imgur.com/1woliuF.png');
  background-size: contain;
  background-position: bottom left;
  background-repeat: no-repeat;
  position: relative;
  z-index: 1;
  height:342px;
  width: 467px;
  margin:0 auto;
} 

.cs-instructions-inner {
  background-color:#fff;
  background-image: url('https://i.imgur.com/6aOej4c.jpg');
  background-size: contain;
  background-position: bottom left;
  background-repeat:repeat-y;
  font-size:14px;
  line-height:30px;
  padding:10px 30px 10px 30px;
  margin:10px 0 0 38px; 
  min-height:120px;
  width:330px;
  overflow-y:hidden;
  position: absolute;
  color:#000;
  z-index: 2;
  bottom:190px;
}
 
.mb-4 {
  margin-bottom:20px;
}

.mb-0 {
  margin-bottom:0;
}

.credits {
  text-align:center;
  margin-top:40px;
  color:#444;
  font-size:9px;
}

.credits a {
  color:#444;
  text-decoration:none;
}

class Typewriter extends React.Component {
  constructor(props){
    super(props);
    this.myDiv = null;
    this.mySound = null;
    this.typewriter = element => {
      this.myDiv = element;
    }; 
    this.setmySound = element => {
      this.mySound = element;
    };

    this.playSound = this.playSound.bind(this);
    this.pauseSound = this.pauseSound.bind(this);
  }

  playSound() {
    if (this.mySound && (this.mySound.duration === 0 || this.mySound.paused)) {
      this.mySound.play();
    }
  } 

  pauseSound() { 
    if(this.mySound){
      this.mySound.pause();
    }
  }

  typeWriter = (s, i=0) => {
    if (this.myDiv && i < s.length) {
      this.myDiv.innerHTML += s.charAt(i);
      i++;
      if(i>5) { 
        this.props.setScroll(); 
      }
      this.playSound();
      setTimeout(() => this.typeWriter(s, i), 40);
    }
    else {
       this.pauseSound();
       if(this.props.nextStep){
        this.props.nextStep();
       } 
    }
  }

  componentDidMount() {
    if(this.myDiv) {
      setTimeout(() => this.typeWriter(this.props.mystring), 200);
    }
  }

  componentWillUnmount() {
    this.pauseSound();
  }

  render() {
    return (
      <div className={this.props.mb}> 
        <audio autoPlay preload="auto" ref={this.setmySound} src="https://freesound.org/data/previews/138/138049_2423928-lq.mp3" type="audio/mpeg" > </audio>
        <span ref={this.typewriter}> </span>
      </div>
    );
  }
}

class App extends React.Component {
  constructor(){
    super();
    this.state = {
      step: 0
    }
    this.myDiv = null;
    this.instructions = element => {
      this.myDiv = element;
    };
    this.setScroll = this.setScroll.bind(this);
  }

  setScroll() {
    this.myDiv.scrollTo(0, this.myDiv.scrollHeight);
  }
  
  nextStep = () => this.setState({step: this.state.step + 1});

  render(){
    return(
      <div className="wrapper">
        <div className="cs-instructions">
          <div className="cs-instructions-inner" ref={this.instructions}>
            {
              this.state.step === 0
              ? <button onClick={() => {this.nextStep()}}>CLICK TO START THE CASE</button>
              : ''
            }

            {
              this.state.step > 0 
              ? <Typewriter mb={'mb-4'} nextStep={this.nextStep} setScroll={this.setScroll} mystring="****** FLASH ******"></Typewriter>
              : ''
            }

            {
              this.state.step > 1 
              ? <Typewriter mb={'mb-4'} nextStep={this.nextStep} setScroll={this.setScroll} mystring="National treasure stolen from Paris."></Typewriter>
              : ''
            }

            {
              this.state.step > 2 
              ? <Typewriter mb={'mb-0'} nextStep={this.nextStep} setScroll={this.setScroll} mystring="The treasure has been identified as the elevator from the Eiffel Tower."></Typewriter>
              : ''
            }


            {
              this.state.step > 3 
              ? <button className="mb-4" onClick={() => {this.nextStep()}}>Click to continue</button>
              : ''
            }
            {
              this.state.step > 4 
              ? <Typewriter mb={'mb-0'} nextStep={this.nextStep} setScroll={this.setScroll} mystring="Female suspect reported at the scene of the crime. Your assignement: Track the thief from Paris to her hideout and arrested her!"></Typewriter>
               : ''
            }

             {
              this.state.step > 5 
              ? <button className="mb-4" onClick={() => {this.nextStep()}}>Click to continue</button>
              : ''
            }

            {
              this.state.step > 6 
              ? <Typewriter mb={'mb-0'} setScroll={this.setScroll} mystring="You must apprehend the thief by Sunday, 5 pm. Good luck ! "></Typewriter>
               : ''
            }
          </div>
        </div>
        <div className="credits">Typewriter effect realised with ReactJS inspired by Carmen Sandiego 1991 MS-DOS <br/><a href="https://www.julien-verkest.fr/" target="_blank">Julien Verkest ©️ april 2019 </a> </div>
       </div>
    );
  }
}


ReactDOM.render(<App />, document.getElementById('app'));
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://unpkg.com/react/umd/react.development.js
  2. https://unpkg.com/react-dom/umd/react-dom.development.js