Browse Source

Programphile

Alan Colon 6 years ago
parent
commit
747f498622
9 changed files with 532 additions and 28 deletions
  1. 1 0
      src/index.css
  2. 82 2
      src/index.html
  3. 107 20
      src/index.js
  4. 85 0
      src/programphile2.2.html
  5. 79 0
      src/programphile2.3.html
  6. 46 0
      src/programphile2.4.5.html
  7. 122 0
      src/programphile2.4.html
  8. 2 0
      src/util.js
  9. 8 6
      webpack.config.js

+ 1 - 0
src/index.css

@@ -36,6 +36,7 @@ sprite, .sprite {
 centerer {
   display: inline-block;
   transform: translate(-50%, -50%);
+  position: relative;
 }
 mover, scaler {
   display: inline-block;

+ 82 - 2
src/index.html

@@ -2,8 +2,8 @@
   <head>
     <title>CHANGEME</title>
   </head>
-  <body ~="https://www.alanc.net/programphile">
-    <scene duration="10">
+  <body ~="http://localhost:9000">
+    <!-- <scene duration="10">
       <video autoplay loop>
         <source data-src="~/computer-code-flythrough-loop-01.mov" type="video/mp4" />
       </video>
@@ -20,6 +20,86 @@
         translateY[0,2]="60h,0"
         left="50w" top="70h">
 
+    </scene> -->
+    <scene preroll="5">
+      <span class="sprite timecode" left="50w" top="10h" scale="3"
+      translateY[-5,-0.5]="0,-100h"
+      duration[-5,-0.5]="0"
+      >1:00:00:00</span>
+      <span class="sprite expr"
+        top="50h"
+        left="50w"
+        translateY[0,1:20,6]="0,-25h,0"
+        scale[0,16.75]="1,0"
+        easing[16.75]="linear" duration[16.75]=".25"
+      >
+        <span class="sprite" left="-200px"
+        scale[0,:9,1:10,6,12]="1,2,1,2,2.2"
+        color[0,12,14]="#fff,#0f0,#fff"
+        easing[4,6]="linear"
+        >1</span>
+        <span class="sprite"
+        scale[0,:19,1:10,6,13]="1,2,1,2,2.2"
+        color[0,13]="#fff,#ff0"
+        >+</span>
+        <span class="sprite" left="200px"
+        scale[0,:27,1:10,6,12.25]="1,2,1,2,2.2"
+        color[0,12.25,14]="#fff,#0f0,#fff"
+        >2</span>
+      </span>
+      <span class="sprite expr"
+        top="50h"
+        left="50w"
+        scale[0,2,4:10,16.75]="0,2,1,0"
+        easing[16.75]="linear" duration[16.75]=".25"
+        translateY[0,4:10,6]="0,25h,70h"
+        rotateZ[0,6]="0,45"
+      >
+        <span class="sprite" translateX[0,4:10]="-400px,-200px">1</span>
+        <span class="sprite" translateX[0,4:10]="-200px,-100px">+</span>
+        <span class="sprite" translateX[0,4:10]="0,0">x</span>
+        <span class="sprite" translateX[0,4:10]="200px,100px">=</span>
+        <span class="sprite" translateX[0,4:10]="400px,200px">2</span>
+      </span>
+      <span class="sprite label" left="50w" top="35h" scale[0,1:20,16.75]="0,1,0"
+      easing[16.75]="linear" duration[16.75]=".25"
+        translateY[0,6]="0,40h"
+      >
+        Expression
+      </span>
+      <span class="sprite label" left="50w" top="85h" scale[0,4:10]="0,1"
+        translateY[0,6]="0,70h"
+        rotateZ[0,6]="0,45"
+      >
+        Equation
+      </span>
+      <div style="position: absolute; top: 0; width: 100%; height: 420px; overflow: hidden;">
+        <span class="sprite" left="50w" top="30h"
+          scale[0,16.75]="30,0"
+          easing[16.75]="linear" duration[16.75]=".25"
+          color="#08f"
+          rotateZ[0,15.75]="180,0"
+          translateY[0,15.75]="155px,0"
+        >
+          ↶
+        </span>
+      </div>
+      <span class="sprite expr"
+        left="50w"
+        top="50h"
+        scale[0,17]="0,1"
+      >3</span>
+      <!-- <video autoplay src="voiceover.mov" style="visibility: hidden;">
+      </video> -->
     </scene>
+    <style>
+      .expr {
+        font-family: monospace;
+        font-size: 150px;
+      }
+      .label {
+        font-size: 50px;
+      }
+    </style>
   </body>
 </html>

+ 107 - 20
src/index.js

@@ -27,9 +27,22 @@ document.addEventListener('DOMContentLoaded', () => {
 
   const scenes = Array.from(document.querySelectorAll('scene, .scene'))
   scenes.forEach(scene => {
-
+    const beatsElement = scene.querySelector('beats')
+    let beats = []
+    if (beatsElement) {
+      beatsElement.style.whiteSpace = 'pre'
+      beats = Object.fromEntries(
+        beatsElement.innerText
+          .split('\n')
+          .map(line => line.trim().replace(/\s+/g, ' '))
+          .filter(x => x)
+          .map(x => x.split(' '))
+          .map(([key, time, ...words]) => [key, {time, words: words.join(' ')}])
+      )
+      beatsElement.style.display = 'none'
+    }
     const sprites = Array.from(scene.querySelectorAll('sprite, .sprite'))
-
+    const preroll = +scene.getAttribute('preroll') || 0
     const spritesById = {}
     sprites
       .filter(sprite => sprite.id)
@@ -48,7 +61,7 @@ document.addEventListener('DOMContentLoaded', () => {
 
       sprite.keyFrames = {}
       sprite.moverFrames = {}
-      ;['left', 'right', 'top', 'bottom', 'width', 'height', 'opacity'].forEach(key => {
+      ;['left', 'right', 'top', 'bottom', 'width', 'height', 'opacity', 'zoom'].forEach(key => {
         const val = sprite.attributes[key]
         if (val) mover.style[key] = unit(val)
       })
@@ -68,11 +81,38 @@ document.addEventListener('DOMContentLoaded', () => {
         return sprite.moverFrames[key]
       }
 
+      const parseTime = (x, withPreroll = preroll) => {
+        const b = /^([^+-]*)([+-]?)(.*)$/.exec(x)
+        if (b && beats[b[1]]) {
+          const key = b[1]
+          const operator = b[2]
+          const operand = b[3]
+          let opValue = 0
+          const beat = beats[key]
+          const beatTime = parseTime(beat.time, 0)
+          if (operand) {
+            opValue = parseTime(operand, 0)
+            if (operator === '-') opValue = -opValue
+          }
+          let time = (beatTime + opValue)
+          time = time && (withPreroll + time)
+          return time
+        } else {
+          let {1:seconds, 2:frame} = /([^:]*):?(.*)/.exec(x)
+          seconds = +(seconds || 0)
+          frame = +(frame || 0)
+          let time = seconds + (frame / 30)
+          // Only apply preroll when time > 0
+          time = time && (withPreroll + time)
+          return time
+        }
+      }
+
       Array.from(sprite.attributes).forEach(attr => {
         let {1:name, 2:time} = (/^([^\]]*)\[([^\]]*)\]$/.exec(attr.name) || [null, attr.name, "0"])
         const transform = transforms[name]
         if (transform) {
-          const times = time.split(',').map(x => +x)
+          const times = time.split(',').map(x => parseTime(x))
           const values = attr.value.split(',').map(x => x.trim())
           if (times.length !== values.length) return console.error('Mismatched time, value arrays', attr)
           times.forEach((time, i) => {
@@ -101,34 +141,81 @@ document.addEventListener('DOMContentLoaded', () => {
 
     const allFrames = []
     sprites.forEach(({keyFrames, moverFrames}) => {
-      const frames = [...Object.entries(keyFrames), ...Object.entries(moverFrames)].map(([time, frame]) => [+time * 1000, frame])
-      frames.sort((a, b) => a[0] - b[0])
+      const frames = [...Object.entries(keyFrames), ...Object.entries(moverFrames)].map(([time, frame]) => ({time: +time * 1000, keyFrame: frame}))
+      frames.sort((a, b) => a.time - b.time)
       frames.forEach(frame => {
-        frame[1].targets.lastTime = 0
-        if (frame[1].duration) {
-          frame[1].duration = 1000 * +frame[1].duration
+        frame.keyFrame.targets.lastTime = 0
+        if (frame.keyFrame.duration) {
+          frame.keyFrame.duration = 1000 * +frame.keyFrame.duration
         }
       })
-      frames.forEach(frame => {
+      frames.forEach((frame, i) => {
+        const nextFrame = frames[i + 1]
         allFrames.push(frame)
-        if (frame[0] == 0) {
-          frame[0] = Number.EPSILON
-          frame[1].easing = 'steps(1)'
+        if (frame.time == 0) {
+          //frame.time = Number.EPSILON
+          frame.keyFrame.easing = 'steps(1)'
         } else {
-          frame[1].duration = frame[1].duration || Math.min(1000, frame[0] - frame[1].targets.lastTime)
+          const lastFrame = frame.keyFrame.targets.lastFrame
+          frame.keyFrame.duration = frame.keyFrame.duration || Math.min(1000, frame.time - frame.keyFrame.targets.lastTime - 1)
+          if (lastFrame) {
+            const maxDuration = frame.time - lastFrame.time
+            if (lastFrame.keyFrame.duration > maxDuration) lastFrame.keyFrame.duration = maxDuration
+            lastFrame.maxDuration = maxDuration
+            lastFrame.lastTime = lastFrame.time
+          }
+          // if (nextFrame) {
+          //   const maxDuration = nextFrame.time - frame.time
+          //   if (frame.keyFrame.duration > maxDuration) frame.keyFrame.duration = maxDuration
+          // }
         }
-        frame[1].targets.lastTime = frame[0]
+        frame.keyFrame.targets.lastTime = frame.time
+        frame.keyFrame.targets.lastFrame = frame
       })
     })
-    allFrames.sort((a, b) => a[0] - b[0])
+    const targets = new Set(allFrames.map(x => x.keyFrame.targets))
+    const maxTime = Array.from(targets).map(x => x.lastTime).reduce((a, b) => Math.max(a, b), 0)
+    for (let target of targets) {
+      if (target.lastTime !== maxTime) {
+        console.log('Adding lastframe')
+//        allFrames.push({time: maxTime, keyFrame: { ...target.lastFrame.keyFrame, duration: 0}})
+      }
+    }
+    allFrames.sort((a, b) => a.time - b.time)
 
     console.log(allFrames)
-    allFrames.forEach(([time, keyFrame]) => {
-      timeline.add(keyFrame, time)
-      console.log(time, keyFrame)
+    window.allFrames = allFrames
+    const timelineAdd = (frame, time) => {
+      timeline.add(frame, time)
+      console.log(time, frame)
+    }
+    allFrames.forEach(({time, keyFrame}) => {
+      if (time == 0) {
+        timelineAdd({...keyFrame, duration: 0.001}, Number.EPSILON)
+        timelineAdd({...keyFrame, duration: 0.001}, Number.EPSILON * 2)
+      } else {
+        timelineAdd(keyFrame, time)
+      }
     })
 
-    console.log(timeline)
+    const timecodes = Array.from(document.querySelectorAll('.timecode'))
+    if (timecodes.length) {
+      timeline.add({
+        update(anim) {
+          const ms = Math.floor((timeline.progress / 100) * timeline.duration)
+          const fps = 30
+          const seconds = Math.floor(ms / 1000)
+          const leftover = ms % 1000
+          const frames = Math.floor(leftover / 1000 * fps)
+          const timecodeString = `${seconds}:${frames.toString().padStart(2, '0')}`
+          timecodes.forEach(x => x.innerText = timecodeString)
+        },
+        loop: true,
+        duration: 100,
+        targets: document.documentElement
+      }, 0)
+    }
+    console.log('timeline', timeline)
   })
 })
 

+ 85 - 0
src/programphile2.2.html

@@ -0,0 +1,85 @@
+<html>
+  <head>
+    <title>CHANGEME</title>
+  </head>
+  <body ~="http://localhost:9000">
+    <scene preroll="5">
+      <beats>
+
+        a :10     In math, we have operators like
+        b 1:23    plus (+),
+        c 2:10      minus (-),
+        d 2:29    multiply (x), and 
+        e 3:23      divide (÷).
+        f 4:26      In coding, we have the
+        g 5:17      same operators, only slightly different:
+        g2 8:13   Multiply is an 
+        h 9:15      asterisk (*), and 
+        h2 10:16   divide is a 
+        i 11:08      forward slash (/). The 
+        j 12:29      asterisk kind of looks like an 
+        k 14:15      x, and 
+        l 15:27    forward slash kind of looks like the
+        m 17:12   line in a fraction, so hopefully it makes sense.         
+      </beats>
+      <span class="sprite timecode" left="50w" top="10h" scale="3"
+      translateY[-5,-0.5]="0,-100h"
+      duration[-5,-0.5]="0"
+      >1:00:00:00</span>
+
+      <span class="sprite" left="50w" top="50h"
+        scale[0,a]="0,1"
+        translateY[0,b-.5]="0,-35h"
+        translateX[0,f]="0,-25w"
+      >
+        <span class="sprite">Math</span>
+        <span class="sprite" top="200px" scale[0,b]="0,1">+</span>
+        <span class="sprite" top="400px" scale[0,c]="0,1">-</span>
+        <span class="sprite" top="600px" scale[0,d,k,l]="0,1,2,1"
+          translateX[k,l]="20w,0"
+        >×</span>
+        <span class="sprite" top="800px" scale[0,e]="0,1">÷</span>
+      </span>
+      <span class="sprite" left="50w" top="50h"
+        scale[0,f]="0,1"
+        translateY="-35h"
+        translateX[0,f]="0,25w"
+      >
+        <span class="sprite">Coding</span>
+        <span class="sprite" top="200px" scale[0,g]="0,1">+</span>
+        <span class="sprite" top="400px" scale[0,g+:2]="0,1">-</span>
+        <span class="sprite" top="600px" scale[0,g+:4,h]="0,1,0">×</span>
+        <span class="sprite" top="800px" scale[0,g+:8,i]="0,1,0">÷</span>
+        <span class="sprite" top="600px" scale[0,h,j,l]="0,1,2,1"
+          translateX[j,l]="-20w,0"
+        >
+          <span class="sprite" top="20px">*</span>
+        </span>
+        <span class="sprite" top="800px" scale[0,i,l,m]="0,1,2,3"
+          translateX[l]="-25w"
+          translateY[l]="-300px"
+          rotateZ[m]="69"
+        >/</span>
+      </span>
+      <span class="sprite" top="520px" left="50w" scale[0,m]="0,2">1</span>
+      <span class="sprite" top="815px" left="50w" scale[0,m]="0,2">2</span>
+      <span class="sprite" top="838" left="50w"
+        scale="4"
+        rotate[0,g2,h2,j]="-22,0,22,90"
+        translateY[0,g2,j]="50h,0,50h"
+      ><span class="sprite" top=-26px>👉</span></span>
+    </scene>
+    <style>
+      .expr {
+        font-family: monospace;
+        font-size: 150px;
+      }
+      .label {
+        font-size: 50px;
+      }
+      .sprite {
+        font-size: 100px;
+      }
+    </style>
+  </body>
+</html>

+ 79 - 0
src/programphile2.3.html

@@ -0,0 +1,79 @@
+<html>
+  <head>
+    <title>CHANGEME</title>
+  </head>
+  <body ~="http://localhost:9000">
+    <scene preroll="5">
+      <beats>
+
+        a 4:27     picture
+        b 5:21     standing in sentence
+        c 6:23     facing
+        d 7:11     direction we read
+        d1 9:03    leaning backwards
+        e 9:20     lean forward
+        f 11:18     forward slash
+        g 12:29     lean backwards
+        h 13:18     backslash
+      </beats>
+      <span class="sprite timecode" left="50w" top="10h" scale="3"
+      translateY[-5,-0.5]="0,-100h"
+      duration[-5,-0.5]="0"
+      >1:00:00:00</span>
+      <span class="sprite" top=50h left=50w
+        scale[0,1]=0,1
+      >
+        <span class="sprite"
+          translateX[0,b]=-142px,-212px
+        >Hello</span>
+        <span class="sprite"
+          translateX[0,b]=127px,198px
+        >World</span>
+        <span class="sprite" left="-14px">
+          <span class="sprite"
+            scaleX[0,c]=0,-1
+            easing[c]=linear
+            duration[c]=0.1
+            skew[d1,e,g]=-22,22,-22
+          >🚶🏼‍♀️</span>
+          <span class="sprite"
+            scale[0,b]=0,1
+            scaleX[c]=0
+            easing[c]=linear
+            duration[c]=0.1
+          >🧝🏼‍♀️</span>
+        </span>
+        <span class="sprite" top=100px scaleX=3 color=#0f0
+          easing[0,d,d+.5]="linear,linear,linear"
+          duration[0,d,d+.5]=".5,.5,.5"
+          translateX[0,d,d+.5]=-200px,0,200px
+          opacity[0,d,d+.5]=0,1,0
+        >
+          →
+        </span>
+
+      </span>
+      <span class="sprite" left=25w top=75h scale[0,h]=0,1>
+        Backslash \
+      </span>
+      <span class="sprite" left=75w top=75h scale[0,f]=0,1>
+        Forward Slash /
+      </span>
+
+    </scene>
+    <style>
+      .expr {
+        font-family: monospace;
+        font-size: 150px;
+      }
+      .label {
+        font-size: 50px;
+      }
+      .sprite {
+        font-size: 100px;
+        word-wrap: none;
+        white-space: nowrap;
+      }
+    </style>
+  </body>
+</html>

+ 46 - 0
src/programphile2.4.5.html

@@ -0,0 +1,46 @@
+<html>
+  <head>
+    <title>CHANGEME</title>
+  </head>
+  <body ~="http://localhost:9000">
+    <scene preroll="5">
+
+      <span class="sprite timecode" left="50w" top="10h" scale="3"
+      translateY[-5,-0.5]="0,-100h"
+      duration[-5,-0.5]="0"
+      >1:00:00:00</span>
+      </span>
+      <span class="sprite" top=50h left=50w
+      >
+        <span class="sprite"
+          translateX[0,1]=0,-55
+        >
+          <span class="sprite expr" left=-220>1</span>
+          <span class="sprite expr" left=-110>+</span>
+          <span class="sprite expr" left=0   scale[0,1]=0,1>(</span>
+          <span class="sprite" translateX[0,1]=0,110>
+            <span class="sprite expr" left=0   >2</span>
+            <span class="sprite expr" left=110 >*</span>
+            <span class="sprite expr" left=220 >3</span>
+          </span>
+          <span class="sprite expr" left=440 scale[0,1]=0,1>)</span>
+        </span>
+      </span>
+
+    </scene>
+    <style>
+      .expr {
+        font-family: monospace;
+        font-size: 150px;
+      }
+      .label {
+        font-size: 50px;
+      }
+      .sprite {
+        font-size: 100px;
+        word-wrap: none;
+        white-space: nowrap;
+      }
+    </style>
+  </body>
+</html>

+ 122 - 0
src/programphile2.4.html

@@ -0,0 +1,122 @@
+<html>
+  <head>
+    <title>CHANGEME</title>
+  </head>
+  <body ~="http://localhost:9000">
+    <scene preroll="5">
+      <script type="text/plain">
+        1+2*3
+        So let's look at a slightly more complex expression. There's two ways to evaluate this: 
+        
+        1 + 2 = 3, and 3 * 3 = 9. or
+        2 * 3 = 6, and 6 + 1 = 7. 
+        
+        So the computer will do the multiplication first, 
+        and yes, I am talking about order of operations, 
+        but you don't have to memorize it, learn it, even 
+        worry about it. I'm not even gonna tell you what 
+        it is. Just know that there is one, and when in
+        doubt, throw parentheses around stuff that matters. 
+        
+      </script>
+      <beats>
+        a 1 lets look
+        b 2:12 two ways
+        c 3:27 first
+          ca 4:03 one
+          cb 4:16 plus
+          cc 4:25 two
+          cd 5:07 equals
+          ce 5:12 three
+        d 5:22 and
+          da 5:27 three
+          db 6:10 times
+          dc 6:22 three
+          dd 7:05 equals
+          de 7:11 nine
+        e 7:22 or
+          ea 8:03 two
+          eb 8:16 times
+          ec 8:28 three
+          ed 9:12 equals
+          ef 9:20 six
+        f 10:05 and
+          fa 10:09 one
+          fb 10:22 plus
+          fc 11:00 six
+          fe 11:13 equals
+          ff 11:19 seven
+        g 12 Finally
+      </beats>
+      <span class="sprite timecode" left="50w" top="10h" scale="3"
+      translateY[-5,-0.5]="0,-100h"
+      duration[-5,-0.5]="0"
+      >1:00:00:00</span>
+      </span>
+      <span id="original" class="sprite expr"
+        top=50h
+        left=50w
+        scale[0,a]=0,1
+        translateY[0,b]=0,-25h
+        >
+        1 + 2 * 3
+      </span>
+
+      <span class="sprite" top=50h left=50w
+        translateX[b,g]=-25w,-75w
+        scale[c,e]=1.25,.75
+        opacity[c,e]=1,.5
+      >
+        <span class="sprite"
+          opacity[0,b-:1]=0,1
+          duration[b-:1]=0
+        >
+          <span class="sprite expr" left=-220 scale[ca]=1.5>1</span>
+          <span class="sprite expr" left=-110 scale[cb]=1.5>+</span>
+          <span class="sprite expr" left=0    scale[cc]=1.5>2</span>
+          <span class="sprite expr" left=110 translateY[d]=150 scale[db]=1.5>*</span>
+          <span class="sprite expr" left=220 translateY[d]=150 scale[dc]=1.5>3</span>
+
+          <span class="sprite expr" translateY[0,ce]=0,150 left=-110 scale[0,ce,da]=0,1,1.5>3</span>
+          <span class="sprite expr" left=110 translateY[0,de]=150,300 scale[0,de]=0,1.5>9</span>
+        </span>
+      </span>
+
+      <span class="sprite" top=50h left=50w
+        translateX[b,g]=25w,0
+        scale[c,e]=.75,1.25
+        opacity[c,e]=.5,1
+      >
+        <span class="sprite"
+          opacity[0,b-:1]=0,1
+          duration[b-:1]=0
+        >
+          <span class="sprite expr" left=-220 translateY[f]=150 scale[fa]=1.5>1</span>
+          <span class="sprite expr" left=-110 translateY[f]=150 scale[fb]=1.5>+</span>
+          <span class="sprite expr" left=0   scale[ea]=1.5>2</span>
+          <span class="sprite expr" left=110 scale[eb]=1.5>*</span>
+          <span class="sprite expr" left=220 scale[ec]=1.5>3</span>
+
+          <span class="sprite expr" translateY[ef]=150 left=110 scale[0,ef,fc]=0,1,1.5>6</span>
+          <span class="sprite expr" left=-110 translateY[0,ff]=150,300 scale[0,ff]=0,1.5>7</span>
+
+        </span>
+      </span>
+
+    </scene>
+    <style>
+      .expr {
+        font-family: monospace;
+        font-size: 150px;
+      }
+      .label {
+        font-size: 50px;
+      }
+      .sprite {
+        font-size: 100px;
+        word-wrap: none;
+        white-space: nowrap;
+      }
+    </style>
+  </body>
+</html>

+ 2 - 0
src/util.js

@@ -35,6 +35,8 @@ const transforms = [
   {name: 'skew', unit: 'deg' },
   {name: 'skewX', unit: 'deg' },
   {name: 'skewY', unit: 'deg' },
+  {name: 'opacity', unit: '' },
+  {name: 'color', unit: ''},
   {name: 'perspective', unit: 'px' },
   {name: 'duration', unit: '', global: true},
   {name: 'easing', unit: '', global: true}

+ 8 - 6
webpack.config.js

@@ -1,7 +1,7 @@
 const HtmlWebpackPlugin = require('html-webpack-plugin')
 const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin')
 const LiveReloadPlugin = require('webpack-livereload-plugin')
-
+const fs = require('fs')
 module.exports = {
   entry: './src/index.js',
   mode: process.env.NODE_ENV || 'development',
@@ -12,11 +12,13 @@ module.exports = {
     writeToDisk: true
   },
   plugins: [
-    new HtmlWebpackPlugin({
-      alwaysWriteToDisk: true,
-      filename: 'index.html',
-      template: 'src/index.html'
-    }),
+    ...fs.readdirSync('src').filter(x => x.endsWith('.html')).map(file =>
+      new HtmlWebpackPlugin({
+        alwaysWriteToDisk: true,
+        filename: file,
+        template: `src/${file}`
+      })
+    ),
     new HtmlWebpackHarddiskPlugin(),
     new LiveReloadPlugin({
       appendScriptTag: true