my eye

Move javascript out of site template into static asset

Committed 7b74ac

index 0000000..c52b499
--- /dev/null

+import { _, cookies, go, upgradeLink } from '/static/web.js'
+
+let mode = 'site'
+
+// TODO $.load(...)
+document.addEventListener('DOMContentLoaded', ev => {
+  microphoneStatus('off')
+  listen.onmouseup = initDictation
+  join.onmouseup = initChat
+
+  _('a:not(.breakout)').each(upgradeLink)
+  history.pushState({scroll: 0}, 'title', window.location)
+  window.go = go
+
+  const client = new WebTorrent()
+  DragDrop('body', (files, pos, fileList, directories) => {
+    console.log(files[0])
+    // TODO client.seed(files, torrent => {
+    // TODO   console.log('Client is seeding ' + torrent.magnetURI)
+    // TODO })
+  })
+
+  if (cookies.get('rhythm') == 'on') {
+    document.querySelector('body').style.backgroundImage = 'url(/static/measure.png)'
+  }
+  cookies.set('mediasoup-demo.user', `{"displayName": "${userName}"}`)
+
+  document.addEventListener('keydown', ev => {
+    if (mode == 'room') {
+      if (ev.key == 'w') {                              // w   TODO walk forward
+      } else if (ev.key == 'a') {                       // a   TODO strafe left
+      } else if (ev.key == 's') {                       // s   TODO walk backward
+      } else if (ev.key == 'd') {                       // d   TODO strafe right
+      } else if (ev.key == 'h') {                       // h   TODO pan left
+      } else if (ev.key == 'j') {                       // j   TODO scroll map down
+      } else if (ev.key == 'k') {                       // k   TODO scroll map up
+      } else if (ev.key == 'l') {                       // l   TODO pan right
+      }
+      return
+    }
+    if (following) {
+      if (ev.key == 'Escape') {
+        hideFollowLinks()
+        return
+      }
+      if (['Shift', 'Alt', 'Control', 'Tab'].indexOf(ev.key) != -1)
+        return
+      followQueue += ev.key
+      if (followList.hasOwnProperty(followQueue)) {
+        go(followList[followQueue])
+        hideFollowLinks()
+      }
+      return
+    }
+    if (ev.ctrlKey && ev.key == '.') {                  // C^. toggle rhythm indicator
+      if (cookies.get('rhythm') == 'on') {
+        document.querySelector('body').style.backgroundImage = 'none'
+        cookies.set('rhythm', 'off')
+      } else {
+        document.querySelector('body').style.backgroundImage = 'url(/static/measure.png)'
+        cookies.set('rhythm', 'on')
+      }
+    } else if (ev.key == '?') goHelp()                  // ?   show help
+    else if (ev.key == 'f') showKeyboardFollowLinks()   // f   show "follow links"
+    else if (ev.key == 'm') goHome()                    // m   go home
+    else if (ev.key == 'h') goBack()                    // h   go back
+    else if (ev.key == 'j') scrollDown()                // j   scroll down 3 EMs
+    else if (ev.key == 'k') scrollUp()                  // k   scroll up 3 EMs
+    else if (ev.key == 'l') goForward()                 // l   go forward
+    else if (ev.key == 'u') goUp()                      // u   follow rel=up
+    else if (ev.key == 'p') goPrevious()                // p   follow rel=prev
+    else if (ev.key == 'n') goNext()                    // n   follow rel=next
+    else if (ev.key == '[') pageUp()                    // [   scroll page up
+    else if (ev.key == ']') pageDown()                  // ]   scroll page down
+    else if (ev.key == '{') scrollTop()                 // {   scroll to top
+    else if (ev.key == '}') scrollBottom()              // }   scroll to bottom
+  }, false)
+})
+
+const em = parseFloat(getComputedStyle(document.documentElement).fontSize)
+
+const goHelp = () => { go('/help') }
+const goHome = () => { go(document.querySelector("a[rel=home]").href) }
+const goUp = () => { go(document.querySelector("a[rel=up]").href) }
+const goPrevious = () => { go(document.querySelector("a[rel=prev]").href) }
+const goNext = () => { go(document.querySelector("a[rel=next]").href) }
+const goBack = () => { history.back() }
+const goForward = () => { history.forward() }
+const scrollDown = () => { document.documentElement.scrollTop += 3 * em }
+const scrollUp = () => { document.documentElement.scrollTop -= 3 * em }
+const pageUp = () => { document.documentElement.scrollTop -= 15 * em }
+const pageDown = () => { document.documentElement.scrollTop += 15 * em }
+const scrollTop = () => { document.documentElement.scrollTop = 0 }
+const scrollBottom = () => { document.documentElement.scrollTop = 99999 }
+
+let following = false
+let followList = {}
+let followQueue = ''
+const showKeyboardFollowLinks = () => {
+  following = true
+  const links = document.querySelectorAll('a')
+  links.forEach((link, n) => {
+    const characters = 'asdfghjkl;'
+    const combinationLength = Math.log10(links.length)
+    let combination = ''
+    while (true) {
+      combination = ''
+      for (let i = 0; i < combinationLength; i++)
+        combination += characters.charAt(Math.floor(Math.random() * characters.length))
+      if (!followList.hasOwnProperty(combination)) break
+    }
+    followList[combination] = link.href
+    link.innerHTML = link.innerHTML + `<span class=followlink>${combination}</span>`
+  })
+}
+const showVoiceFollowLinks = () => {
+  following = true
+  document.querySelectorAll('a').forEach((link, n) => {
+    followList[numberToWords(n).replace(' ', '')] = link.href
+    link.innerHTML = link.innerHTML + `<span class=followlink>${n}</span>`
+  })
+}
+const hideFollowLinks = () => {
+  following = false
+  followList = {}
+  followQueue = ''
+  document.querySelectorAll('.followlink').forEach(e => e.remove())
+}
+
+const initDictation = async () => {
+  microphoneStatus('loading')
+  const partialContainer = document.querySelector('#search .partial')
+
+  const channel = new MessageChannel()
+  const model = await Vosk.createModel('/static/vosk-model-small-en-us-0.15.tar.gz')
+  model.registerPort(channel.port1)
+
+  const sampleRate = 48000
+
+  const recognizer = new model.KaldiRecognizer(sampleRate)
+  recognizer.setWords(true)
+
+  let wakeWord = 'ghost'
+  let readyWord = 'Say "ghost help"'
+  let state = 'asleep'
+  microphoneStatus('on')
+  partialContainer.innerHTML = readyWord
+
+  recognizer.on('result', message => {
+    let input = message.result.text
+    if (following && input != '') {
+      let number = input.replace(' ', '')
+      if (number == 'for') number = 'four'
+      go(followList[number])
+      hideFollowLinks()
+      return
+    }
+    if (input.slice(0, wakeWord.length) != wakeWord) {
+      partialContainer.innerHTML = readyWord
+      return
+    }
+    input = input.slice(wakeWord.length + 1)
+    microphoneStatus('on')
+    if (input.endsWith('cancel cancel')) {
+      partialContainer.innerHTML = ''
+      return
+    }
+    if (input == 'help') goHelp()                    // help
+    else if (input.startsWith('query for')) {      // query for
+      const query = input.slice(10)
+      go(`/search?q=${query}`)
+    } else if (input.startsWith('go')) {             // go
+      const where = input.slice(3)
+      if (where == 'home') goHome()                  //    home
+      else if (where == 'up') goUp()                 //    up
+      else if (where == 'prev') goPrevious()         //    prev
+      else if (where == 'next') goNext()             //    next
+      else if (where == 'back') goBack()             //    back
+      else if (where == 'forward') goForward()       //    forward
+    } else if (input.startsWith('follow')) {         // follow
+      showVoiceFollowLinks()
+    } else if (input.startsWith('tell me')) {        // tell me
+      const request = input.slice(8)
+      const response = fetch('/ai/assistant', {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json',
+          },
+          body: JSON.stringify({request: request}),
+      })
+      .then(response => response.blob())
+      .then(blob => {
+        const audio = new Audio(URL.createObjectURL(blob));
+        audio.addEventListener('canplaythrough', () => {
+          state = 'asleep'
+          microphoneStatus('on')
+          audio.play()
+          // partialContainer.value = readyWord
+        })
+      })
+    }
+    partialContainer.innerHTML = `${input}`
+  })
+  recognizer.on('partialresult', message => {
+    const input = message.result.partial
+    if (input.slice(0, wakeWord.length) != wakeWord || !following)
+      return
+    state = 'awake'
+    microphoneStatus('active')
+    partialContainer.innerHTML = input.slice(wakeWord.length)
+  })
+
+  const mediaStream = await navigator.mediaDevices.getUserMedia({
+    video: false,
+    audio: {
+      echoCancellation: true,
+      noiseSuppression: true,
+      channelCount: 1,
+      sampleRate
+    },
+  })
+
+  const audioContext = new AudioContext()
+  await audioContext.audioWorklet.addModule('/static/recognizer-processor.js')
+  const recognizerProcessor = new AudioWorkletNode(
+    audioContext,
+    'recognizer-processor',
+    { channelCount: 1, numberOfInputs: 1, numberOfOutputs: 1 }
+  )
+  recognizerProcessor.port.postMessage(
+    { action: 'init', recognizerId: recognizer.id },
+    [ channel.port2 ]
+  )
+  recognizerProcessor.connect(audioContext.destination)
+
+  const source = audioContext.createMediaStreamSource(mediaStream)
+  source.connect(recognizerProcessor)
+}
+
+const initChat = () => {
+  join.disabled = true
+  join.style.display = 'none'
+
+  const tag = document.createElement('script')
+  tag.src = '/chats/mediasoup-demo-app.js'
+  document.getElementsByTagName('head')[0].appendChild(tag)
+
+  const autoMuter = setInterval(autoMute, 100)
+  const autoMute = () => {
+    if (typeof window.CLIENT !== 'undefined' && window.CLIENT._micProducer) {
+      window.CLIENT.muteMic()
+      clearInterval(autoMuter)
+    }
+  }
+}
+
+const numberToWords = num => {
+  if (num === 0)
+    return 'zero'
+  const units = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
+  const teens = ['eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen']
+  const tens = ['', 'ten', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety']
+  const convert = n => {
+    if (n === 0) {
+      return ''
+    } else if (n < 10) {
+      return units[n - 1] + ' '
+    } else if (n == 10) {
+      return 'ten'
+    } else if (n < 20) {
+      return teens[n - 11] + ' '
+    } else if (n < 100) {
+      return tens[Math.floor(n / 10)] + ' ' + convert(n % 10)
+    } else {
+      return units[Math.floor(n / 100)-1] + ' hundred ' + convert(n % 100)
+    }
+  }
+  return convert(num).trim()
+}
+
+const microphoneStatus = mode => {
+  let color
+  let icon
+  if (mode == 'on' || mode == 'active') {
+    if (mode == 'on')
+      color = 'dc322f'
+    else if (mode == 'active')
+      color = '268bd2'
+    icon = `<path d="M7 7.40991C7 6.08383 7.52677 4.81207 8.46445 3.87439C9.40213 2.93671 10.6739 2.40991 12 2.40991C13.3261 2.40991 14.5978 2.93671 15.5355 3.87439C16.4732 4.81207 17 6.08383 17 7.40991V13.4099C17 14.736 16.4732 16.0079 15.5355 16.9456C14.5978 17.8832 13.3261 18.4099 12 18.4099C10.6739 18.4099 9.40213 17.8832 8.46445 16.9456C7.52677 16.0079 7 14.736 7 13.4099V7.40991Z" stroke="#${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M21 13.4099C21 15.7969 20.0518 18.0861 18.364 19.7739C16.6761 21.4618 14.3869 22.4099 12 22.4099C9.61305 22.4099 7.32384 21.4618 5.63602 19.7739C3.94819 18.0861 3 15.7969 3 13.4099" stroke="#${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
+  } else if (mode == 'off' || mode == 'loading') {
+    if (mode == 'off')
+      color = '586e75'
+    else if (mode == 'loading')
+      color = '6c71c4'
+    icon = `<path d="M17.0005 11.24V13C17.0005 14.3261 16.4737 15.5978 15.536 16.5355C14.5983 17.4732 13.3266 18 12.0005 18C11.4846 17.9975 10.972 17.9166 10.4805 17.76" stroke="#${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 16C7.35089 15.1345 7 14.0819 7 13V7C7 5.67392 7.52677 4.40216 8.46445 3.46448C9.40213 2.5268 10.6739 2 12 2C13.3261 2 14.5978 2.5268 15.5355 3.46448C16.4732 4.40216 17 5.67392 17 7" stroke="#${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M5.21081 18.84C3.81268 17.216 3.04593 15.1429 3.0508 13" stroke="#${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M21.0007 13C20.9995 14.5822 20.5812 16.1361 19.788 17.5051C18.9948 18.8741 17.8547 20.0098 16.4827 20.7977C15.1107 21.5857 13.5551 21.9979 11.973 21.993C10.3908 21.9882 8.83786 21.5664 7.4707 20.77" stroke="#${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 2L2 22" stroke="#${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
+  }
+  listen.innerHTML = icon
+}

--- a/canopy/templates/template.html
+++ b/canopy/templates/template.html

 <script src=//cdn.jsdelivr.net/npm/vosk-browser@latest/dist/vosk.js></script>
 <script src=//cdn.jsdelivr.net/npm/webtorrent@latest/webtorrent.min.js></script>
 <script src=//cdn.jsdelivr.net/npm/drag-drop@latest/dragdrop.min.js></script>
-<script type=module>
-import { _, cookies, go, upgradeLink } from '/static/web.js'
-
+<script>
 $if "session" in tx.user and tx.user.session.get("uid", None):
-    let userName = '$tx.user.session["name"][0]'
+    $ username = '$tx.user.session["name"][0]'
 $else:
-    let userName = 'Guest'
-
-let mode = 'site'
-
-// TODO $$.load(...)
-document.addEventListener('DOMContentLoaded', ev => {
-  microphoneStatus('off')
-  listen.onmouseup = initDictation
-  join.onmouseup = initChat
-
-  _('a:not(.breakout)').each(upgradeLink)
-  history.pushState({scroll: 0}, 'title', window.location)
-  window.go = go
-
-  const client = new WebTorrent()
-  DragDrop('body', (files, pos, fileList, directories) => {
-    console.log(files[0])
-    // TODO client.seed(files, torrent => {
-    // TODO   console.log('Client is seeding ' + torrent.magnetURI)
-    // TODO })
-  })
-
-  if (cookies.get('rhythm') == 'on') {
-    document.querySelector('body').style.backgroundImage = 'url(/static/measure.png)'
-  }
-  cookies.set('mediasoup-demo.user', `{"displayName": "$${userName}"}`)
-
-  document.addEventListener('keydown', ev => {
-    if (mode == 'room') {
-      if (ev.key == 'w') {                              // w   TODO walk forward
-      } else if (ev.key == 'a') {                       // a   TODO strafe left
-      } else if (ev.key == 's') {                       // s   TODO walk backward
-      } else if (ev.key == 'd') {                       // d   TODO strafe right
-      } else if (ev.key == 'h') {                       // h   TODO pan left
-      } else if (ev.key == 'j') {                       // j   TODO scroll map down
-      } else if (ev.key == 'k') {                       // k   TODO scroll map up
-      } else if (ev.key == 'l') {                       // l   TODO pan right
-      }
-      return
-    }
-    if (following) {
-      if (ev.key == 'Escape') {
-        hideFollowLinks()
-        return
-      }
-      if (['Shift', 'Alt', 'Control', 'Tab'].indexOf(ev.key) != -1)
-        return
-      followQueue += ev.key
-      if (followList.hasOwnProperty(followQueue)) {
-        go(followList[followQueue])
-        hideFollowLinks()
-      }
-      return
-    }
-    if (ev.ctrlKey && ev.key == '.') {                  // C^. toggle rhythm indicator
-      if (cookies.get('rhythm') == 'on') {
-        document.querySelector('body').style.backgroundImage = 'none'
-        cookies.set('rhythm', 'off')
-      } else {
-        document.querySelector('body').style.backgroundImage = 'url(/static/measure.png)'
-        cookies.set('rhythm', 'on')
-      }
-    } else if (ev.key == '?') goHelp()                  // ?   show help
-    else if (ev.key == 'f') showKeyboardFollowLinks()   // f   show "follow links"
-    else if (ev.key == 'm') goHome()                    // m   go home
-    else if (ev.key == 'h') goBack()                    // h   go back
-    else if (ev.key == 'j') scrollDown()                // j   scroll down 3 EMs
-    else if (ev.key == 'k') scrollUp()                  // k   scroll up 3 EMs
-    else if (ev.key == 'l') goForward()                 // l   go forward
-    else if (ev.key == 'u') goUp()                      // u   follow rel=up
-    else if (ev.key == 'p') goPrevious()                // p   follow rel=prev
-    else if (ev.key == 'n') goNext()                    // n   follow rel=next
-    else if (ev.key == '[') pageUp()                    // [   scroll page up
-    else if (ev.key == ']') pageDown()                  // ]   scroll page down
-    else if (ev.key == '{') scrollTop()                 // {   scroll to top
-    else if (ev.key == '}') scrollBottom()              // }   scroll to bottom
-  }, false)
-})
-
-const em = parseFloat(getComputedStyle(document.documentElement).fontSize)
-
-const goHelp = () => { go('/help') }
-const goHome = () => { go(document.querySelector("a[rel=home]").href) }
-const goUp = () => { go(document.querySelector("a[rel=up]").href) }
-const goPrevious = () => { go(document.querySelector("a[rel=prev]").href) }
-const goNext = () => { go(document.querySelector("a[rel=next]").href) }
-const goBack = () => { history.back() }
-const goForward = () => { history.forward() }
-const scrollDown = () => { document.documentElement.scrollTop += 3 * em }
-const scrollUp = () => { document.documentElement.scrollTop -= 3 * em }
-const pageUp = () => { document.documentElement.scrollTop -= 15 * em }
-const pageDown = () => { document.documentElement.scrollTop += 15 * em }
-const scrollTop = () => { document.documentElement.scrollTop = 0 }
-const scrollBottom = () => { document.documentElement.scrollTop = 99999 }
-
-let following = false
-let followList = {}
-let followQueue = ''
-const showKeyboardFollowLinks = () => {
-  following = true
-  const links = document.querySelectorAll('a')
-  links.forEach((link, n) => {
-    const characters = 'asdfghjkl;'
-    const combinationLength = Math.log10(links.length)
-    let combination = ''
-    while (true) {
-      combination = ''
-      for (let i = 0; i < combinationLength; i++)
-        combination += characters.charAt(Math.floor(Math.random() * characters.length))
-      if (!followList.hasOwnProperty(combination)) break
-    }
-    followList[combination] = link.href
-    link.innerHTML = link.innerHTML + `<span class=followlink>$${combination}</span>`
-  })
-}
-const showVoiceFollowLinks = () => {
-  following = true
-  document.querySelectorAll('a').forEach((link, n) => {
-    followList[numberToWords(n).replace(' ', '')] = link.href
-    link.innerHTML = link.innerHTML + `<span class=followlink>$${n}</span>`
-  })
-}
-const hideFollowLinks = () => {
-  following = false
-  followList = {}
-  followQueue = ''
-  document.querySelectorAll('.followlink').forEach(e => e.remove())
-}
-
-const initDictation = async () => {
-  microphoneStatus('loading')
-  const partialContainer = document.querySelector('#search .partial')
-
-  const channel = new MessageChannel()
-  const model = await Vosk.createModel('/static/vosk-model-small-en-us-0.15.tar.gz')
-  model.registerPort(channel.port1)
-
-  const sampleRate = 48000
-
-  const recognizer = new model.KaldiRecognizer(sampleRate)
-  recognizer.setWords(true)
-
-  let ownerGivenName = '$owner["name"][0].split()[0]'
-  let wakeWord = 'ghost'
-  let readyWord = 'Say "ghost help"'
-  let state = 'asleep'
-  microphoneStatus('on')
-  partialContainer.innerHTML = readyWord
-
-  recognizer.on('result', message => {
-    let input = message.result.text
-    if (following && input != '') {
-      let number = input.replace(' ', '')
-      if (number == 'for') number = 'four'
-      go(followList[number])
-      hideFollowLinks()
-      return
-    }
-    if (input.slice(0, wakeWord.length) != wakeWord) {
-      partialContainer.innerHTML = readyWord
-      return
-    }
-    input = input.slice(wakeWord.length + 1)
-    microphoneStatus('on')
-    if (input.endsWith('cancel cancel')) {
-      partialContainer.innerHTML = ''
-      return
-    }
-    if (input == 'help') goHelp()                    // help
-    else if (input.startsWith('query for')) {      // query for
-      const query = input.slice(10)
-      go(`/search?q=$${query}`)
-    } else if (input.startsWith('go')) {             // go
-      const where = input.slice(3)
-      if (where == 'home') goHome()                  //    home
-      else if (where == 'up') goUp()                 //    up
-      else if (where == 'prev') goPrevious()         //    prev
-      else if (where == 'next') goNext()             //    next
-      else if (where == 'back') goBack()             //    back
-      else if (where == 'forward') goForward()       //    forward
-    } else if (input.startsWith('follow')) {         // follow
-      showVoiceFollowLinks()
-    } else if (input.startsWith('tell me')) {        // tell me
-      const request = input.slice(8)
-      const response = fetch('/ai/assistant', {
-          method: 'POST',
-          headers: {
-            'Content-Type': 'application/json',
-          },
-          body: JSON.stringify({request: request}),
-      })
-      .then(response => response.blob())
-      .then(blob => {
-        const audio = new Audio(URL.createObjectURL(blob));
-        audio.addEventListener('canplaythrough', () => {
-          state = 'asleep'
-          microphoneStatus('on')
-          audio.play()
-          // partialContainer.value = readyWord
-        })
-      })
-    }
-    partialContainer.innerHTML = `$${input}`
-  })
-  recognizer.on('partialresult', message => {
-    const input = message.result.partial
-    if (input.slice(0, wakeWord.length) != wakeWord || !following)
-      return
-    state = 'awake'
-    microphoneStatus('active')
-    partialContainer.innerHTML = input.slice(wakeWord.length)
-  })
-
-  const mediaStream = await navigator.mediaDevices.getUserMedia({
-    video: false,
-    audio: {
-      echoCancellation: true,
-      noiseSuppression: true,
-      channelCount: 1,
-      sampleRate
-    },
-  })
-
-  const audioContext = new AudioContext()
-  await audioContext.audioWorklet.addModule('/static/recognizer-processor.js')
-  const recognizerProcessor = new AudioWorkletNode(
-    audioContext,
-    'recognizer-processor',
-    { channelCount: 1, numberOfInputs: 1, numberOfOutputs: 1 }
-  )
-  recognizerProcessor.port.postMessage(
-    { action: 'init', recognizerId: recognizer.id },
-    [ channel.port2 ]
-  )
-  recognizerProcessor.connect(audioContext.destination)
-
-  const source = audioContext.createMediaStreamSource(mediaStream)
-  source.connect(recognizerProcessor)
-}
-
-const initChat = () => {
-  join.disabled = true
-  join.style.display = 'none'
-
-  const tag = document.createElement('script')
-  tag.src = '/chats/mediasoup-demo-app.js'
-  document.getElementsByTagName('head')[0].appendChild(tag)
-
-  const autoMuter = setInterval(autoMute, 100)
-  const autoMute = () => {
-    if (typeof window.CLIENT !== 'undefined' && window.CLIENT._micProducer) {
-      window.CLIENT.muteMic()
-      clearInterval(autoMuter)
-    }
-  }
-}
-
-const numberToWords = num => {
-  if (num === 0)
-    return 'zero'
-  const units = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
-  const teens = ['eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen']
-  const tens = ['', 'ten', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety']
-  const convert = n => {
-    if (n === 0) {
-      return ''
-    } else if (n < 10) {
-      return units[n - 1] + ' '
-    } else if (n == 10) {
-      return 'ten'
-    } else if (n < 20) {
-      return teens[n - 11] + ' '
-    } else if (n < 100) {
-      return tens[Math.floor(n / 10)] + ' ' + convert(n % 10)
-    } else {
-      return units[Math.floor(n / 100)-1] + ' hundred ' + convert(n % 100)
-    }
-  }
-  return convert(num).trim()
-}
-
-const microphoneStatus = mode => {
-  let color
-  let icon
-  if (mode == 'on' || mode == 'active') {
-    if (mode == 'on')
-      color = 'dc322f'
-    else if (mode == 'active')
-      color = '268bd2'
-    icon = `<path d="M7 7.40991C7 6.08383 7.52677 4.81207 8.46445 3.87439C9.40213 2.93671 10.6739 2.40991 12 2.40991C13.3261 2.40991 14.5978 2.93671 15.5355 3.87439C16.4732 4.81207 17 6.08383 17 7.40991V13.4099C17 14.736 16.4732 16.0079 15.5355 16.9456C14.5978 17.8832 13.3261 18.4099 12 18.4099C10.6739 18.4099 9.40213 17.8832 8.46445 16.9456C7.52677 16.0079 7 14.736 7 13.4099V7.40991Z" stroke="#$${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M21 13.4099C21 15.7969 20.0518 18.0861 18.364 19.7739C16.6761 21.4618 14.3869 22.4099 12 22.4099C9.61305 22.4099 7.32384 21.4618 5.63602 19.7739C3.94819 18.0861 3 15.7969 3 13.4099" stroke="#$${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
-  } else if (mode == 'off' || mode == 'loading') {
-    if (mode == 'off')
-      color = '586e75'
-    else if (mode == 'loading')
-      color = '6c71c4'
-    icon = `<path d="M17.0005 11.24V13C17.0005 14.3261 16.4737 15.5978 15.536 16.5355C14.5983 17.4732 13.3266 18 12.0005 18C11.4846 17.9975 10.972 17.9166 10.4805 17.76" stroke="#$${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 16C7.35089 15.1345 7 14.0819 7 13V7C7 5.67392 7.52677 4.40216 8.46445 3.46448C9.40213 2.5268 10.6739 2 12 2C13.3261 2 14.5978 2.5268 15.5355 3.46448C16.4732 4.40216 17 5.67392 17 7" stroke="#$${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M5.21081 18.84C3.81268 17.216 3.04593 15.1429 3.0508 13" stroke="#$${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M21.0007 13C20.9995 14.5822 20.5812 16.1361 19.788 17.5051C18.9948 18.8741 17.8547 20.0098 16.4827 20.7977C15.1107 21.5857 13.5551 21.9979 11.973 21.993C10.3908 21.9882 8.83786 21.5664 7.4707 20.77" stroke="#$${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 2L2 22" stroke="#$${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
-  }
-  listen.innerHTML = icon
-}
+    $ username = 'Guest'
+let userName = '$username'
+let ownerGivenName = '$owner["name"][0].split()[0]'
 </script>
+<script type=module src=/static/enliven.js></script>
 <script src=/chats/resources/js/antiglobal.js></script>
 <script>
 window.localStorage.setItem('debug', '* -engine* -socket* -RIE* *WARN* *ERROR*')