// ====================== JUKEBOX ================================

var expserv = location.port >= 8081
var infoframe = null
var playerfame = null
var queueframe = null

//------------------------------------------------------------------------
function showexpmaybe() {
  if (expserv)
    document.getElementById("experimental").style.display = "block";
}

//------------------------------------------------------------------------
function setwintitle(title) {
  icon = expserv? "\u2660" : "\u266a"
  document.title = icon + " " + (title || "(Ready)") + " " + icon + " Mike's Jukebox"
}

//------------------------------------------------------------------------
var playerkeys = {" ":"pause"} // arrows: "%":"prev", "&":"play", , "(":"stop", "'":"next"
var tabkeys = {"S":"search", "P":"pop", "C":"cla", "M":"misc", "D":"luckydip", "R":"recent", "L":"live", "Y":"lyrics", "A":"about"}
var sidebarkeys = {"E":"selection", "V":"view", "N":"playnow", "Q":"playlater"}
var dot

function checkkeypress(event) {
  if (event.target.tagName == "TEXTAREA")
    return true;
  var key = String.fromCharCode(event.which)
  var keyuc = key.toUpperCase()
  if (playerkeys[keyuc]) {
    var btn = document.getElementById(playerkeys[keyuc])
    if (btn.title) {
      player.location = btn.href
      btn.onclick()
    }
  } else if (key.match(/[A-Z]/)) {
    keypadkey(infoframe.document.getElementById(key))
  } else if (tabkeys[keyuc]) {
    infoframe.location = document.getElementById(tabkeys[keyuc]).childNodes[0].href
  } else if (sidebarkeys[keyuc]) {
    var el = (infoframe.document.getElementById(sidebarkeys[keyuc]))
    if (el) {
      if (el.tagName == "DIV") {
        radiokey(el)
      } else if (el.tagName == "INPUT") {
        checkboxkey(el)
      } else if (el.tagName == "A") {
        playbuttonkey(keyuc)
      }
    }
  } else if (keyuc == "Z") {
    if (infoframe.document.location.href.match(/level2/)) {
      zoombutton()
    } else if (infoframe.document.body.id == "recent") {
      setrecentview(tabmemory.recent.view == "listing"? "gallery" : "listing")
    }
  } else {
    return true
  }
  return false
  //
  function radiokey(el) {
    var node = el.firstChild
    while (node) {
      if (node.tagName == "A" && node.className.match(/selected/)) {
        node.className = "ctl";
        break
      }
      node = node.nextSibling
    }
    if (!node) // all disabled
      return;
    while (node) {
      node = node.nextSibling || el.firstChild
      if (node.tagName == "A" && !node.className.match(/disabled/) && node.id !== "zoom" ) {
        node.className = "ctl selected";
        break;
      }
    }
    if (node.onclick) {
      node.onclick()
    } else {
      infoframe.location = node.href
    }
  }
  function checkboxkey(el) {
    // no available shortcut key for Sampler
  }
  function playbuttonkey(keyuc) {
    infoframe.document.getElementById(keyuc=="N"?"playnow":"playlater").onclick()
  }
  function keypadkey(btn) {
    if (!btn)
      return;
    btn.onclick()
    infoframe.location = btn.href
  }
}

// ====================== PLAYER ================================

function setplayerbuttons() {
  setplayerbutton("prev", "Previous track (==TITLE==)", "prev")
  setplayerbutton("play", (player.playmode == "PLAY"? "Restart this track" : player.playmode == "PAUSE"? "Resume this track" : "Play") + " (==TITLE==)", "play")
  setplayerbutton("pause", (player.playmode == "PLAY"? "Pause" : "Resume this track (==TITLE==)") + " (Shortcut key for the Pause button: spacebar)", "pause")
  setplayerbutton("stop", "Stop", "stop>delete")
  setplayerbutton("next", "Next track (==TITLE==)", "next")
  //
  function setplayerbutton(btn_name, btn_title, btn_cmd) {
    var btn = document.getElementById(btn_name)
    btn.childNodes[0].className = (player.playmode == btn_name.toUpperCase()) ? "lit" : "unlit"
    if (player.titles[btn_name] == "") {
      btn.title = ""
      btn.style.cursor = "default"
      btn.childNodes[0].className += " disabled"
    } else {
      btn.title = btn_title.replace("==TITLE==", player.titles[btn_name])
      btn.href = "/cgi-bin/player.cgi?t=" + player.time + "&cmds=" + btn_cmd
      btn.style.cursor = "hand"
    }
  }
}

//------------------------------------------------------------------------
function updateotherframes() {
  if (!queueframe) return; // startup phase
  queueframe.location = "/cgi-bin/queue.cgi?t=" + player.time

  if (infoframe.document.body.className.match(/level3/)) { // displaying album in index
     showtrack()
  } else if (infoframe.location.pathname.match(/live\.cgi/)) { // displaying live tab
     infoframe.location.reload(true)
  }
}

//------------------------------------------------------------------------
function checkplayer() {
//  setTimeout("checkplayer()", 60000) // repeat this test in 1 minute
  if (player.playmode)
    return;
  // it's crashed (WiFi outage, etc)
  player.location = "/cgi-bin/player.cgi?r=check"
  console.log("Restarted player")
}

//------------------------------------------------------------------------
function updatesecs() {
  if (player.tracksecs == "") // not working
    return;
  now = new Date
  var secsnow = now.getTime()/1000
  if (!player.starttime) {// onload()
    player.starttime = secsnow-player.secsplayed
//    if (player.playmode == "PLAY")
//      player.starttime -= 1; // allow for delays
  }
  var elapsed = Math.min(Math.round(secsnow-player.starttime), player.tracksecs)
  player.document.getElementById("tracknbr").innerHTML = "Track " + player.tracknbr
  player.document.getElementById("tracksecs").innerHTML = top.hhmmss(player.tracksecs)
  player.document.getElementById("secselapsed").innerHTML = top.hhmmss(elapsed)
  player.document.getElementById("secsleft").innerHTML = top.hhmmss(player.tracksecs-elapsed)
  player.document.getElementById("imgprogress").style.width = (player.document.getElementById("progress").clientWidth * elapsed/player.tracksecs)+"px"
  if (elapsed < player.tracksecs) {
    if (player.playmode == "PLAY")
      setTimeout("top.updatesecs()", 1000); // repeat this every second
  } else {
    player.location = "/cgi-bin/player.cgi?t=" + player.time + "&r=elapsed"
  }
}

//------------------------------------------------------------------------
function seek(evt) {
  var offsetX = 0
  var offsetX = evt.clientX
  var el = evt.target;
  do {
    offsetX -= el.offsetLeft
  } while ((el = el.offsetParent))
  var secs = Math.floor(player.tracksecs*offsetX/player.document.getElementById("progress").clientWidth)
  secs = Math.min(secs,player.tracksecs)
  var flag = player.document.getElementById("seekflag")
  if (evt.type == "mouseover" || evt.type == "mousemove") {
    flag.style.left = offsetX-55
    flag.innerHTML = "Jump to "+format_duration(secs)
    flag.style.visibility = "visible"
  } else if (evt.type == "mouseout") {
    flag.style.visibility = "hidden"
  } else if (evt.type == "click") {
    playerframe.location = "/cgi-bin/player.cgi?t=" + player.time + "&cmds=jumptotime " + secs*1000
  }
}

//------------------------------------------------------------------------
function showplayerflag(ctl) {
  var id = ctl.id
  if (!player.titles[id]) { // disabled
    playlevel3maybe(id);
    return false
  }
  var flagmess
  if (id == "prev") {
    flagmess = (player.playmode=="STOP"? "Ready to play" : "Playing") + " the previous track ==TITLE=="
  } else if (id == "play") {
    flagmess = (player.playmode=="STOP"? "Playing" : player.playmode=="PAUSE" ? "Resuming" : "Restarting") + " ==TITLE=="
  } else if (id == "pause") {
    flagmess = player.playmode=="PAUSE"? "Resuming ==TITLE==" : "<div style='text-align: center'>Pausing<br><br><small>(To resume, click on PAUSE or PLAY<br>or press the space bar)</small></div>"
  } else if (id == "stop") {
    flagmess = "Stopping"    
  } else if (id == "next") {
    flagmess = (player.playmode=="STOP"? "Ready to play" : "Playing") + " the next track ==TITLE=="
  } else {
    return false
  }
  flagmess = flagmess.replace(" ==TITLE==", ":<br><div class=desc>" + player.titles[id] + "</div>")
  showflag(ctl, flagmess)
  return true
}

//------------------------------------------------------------------------
function playlevel3maybe(id) {
  if (id !== "play")
    return;
  var btn = infoframe.document.getElementById("playnow")
  if (!btn)
    return;
  infoframe.document.forms[0].cmds.value='playnow @'
  infoframe.document.forms[0].submit()
  showlevel3flag(btn) // wrong position: not easy to fix <shrug>
}    

//------------------------------------------------------------------------
function hhmmss(secs) {
  var mins = Math.floor(secs/60)
  secs -= mins*60
  var hrs
  if (mins>59) {
    hrs = Math.floor(mins/60)
    mins = hrs + ":" + String(100+mins-hrs*60).substr(1,2)
  }
  return mins + ":" + String(100+secs).substr(1,2)
}
    
// ======================= INFOTABS ===============================

function infoloaded() {
  if (!infoframe) return; // startup phase
  
  // Highlight tab
  var tabname = infoframe.document.body.id.split("_")[0]
  if (!tabname) return; // e.g. 404 during testing
  var tabs = document.getElementsByTagName("li")
  for (var i=0; i<tabs.length; i++)
    tabs[i].className = (tabs[i].id == tabname) ? "selected":"";
  document.getElementById("hr").className = tabname
  
  // Record the latest page for this tab
  if (tabname == "search") { // (don't repeat the previous search)
  } else if (tabname == "lyrics") { // (href has been recorded by the page itself)
} else {
    document.getElementById(tabname).childNodes[0].href = infoframe.location.href
  }
  
  // Refer keypresses upstairs
  infoframe.onkeypress = top.onkeypress
}

// ======================= INFO ===============================

var tabmemory = {}
tabmemory.search = {searchfor:"Welcome", filter:0}
tabmemory.index = {initial:null, viewsampler:false}
tabmemory.pop = {sidebar:"pop_albumartists_albums"}
tabmemory.cla = {sidebar:"cla_albumartists_albums"}
tabmemory.misc = {sidebar:"misc_albumartists_albums"}
tabmemory.recent = {view:"listing"}

function pushradiobutton(doc, groupname, buttonname) {
  var node = doc.getElementById(groupname).firstChild
  while (node) {
    if (node.tagName == "A" && !node.className.match(/disabled/)) node.className = (node.id == groupname + "_" + buttonname) ? "ctl selected":"ctl"
    node = node.nextSibling
  }
}

//------------------------------------------------------------------------
function readradiogroup(doc, groupname) {
  var node = doc.getElementById(groupname).firstChild
  while (node) {
    if (node.tagName == "A" && node.className.match(/selected/)) return node.id.replace(/^.*?_/,"")
    node = node.nextSibling
  }
  return "albums" // ??
}

//------------------------------------------------------------------------
function showtrack() { // Highlight the currently-playing track. Called when loading the page and by Player on refresh (track change).
  var infodoc = infoframe.document
  if (infoframe.showntrack) {
    infodoc.getElementById(infoframe.showntrack).className = "played" // unhighlight (normal) or dim (sampler) previous track
    infoframe.showntrack = null
  }
  if (infoframe.albumid == player.albumid) {
    infoframe.showntrack = "text"+player.tracknbr
    var ctl = infodoc.getElementById(infoframe.showntrack)
    if (ctl) { // not there in Live Lyrics
      ctl.className = "playing" // highlight current track
      scrolltracksmaybe(player.tracknbr)
    }
  }
}

//------------------------------------------------------------------------------------
function scrolltracksmaybe(tracknbr) {
  var tracksperpage = (infoframe.document.body.className.match(/level3_s/) || infoframe.location.pathname.match(/live\.cgi/))?22:18
  if (tracknbr >= tracksperpage) {
    infoframe.document.getElementById("text"+(tracknbr-1)).scrollIntoView()
  }
}

//------------------------------------------------------------------------
function showinfoflag(node, what, desc1, desc2) {
  if (node.tagName !== "A")
    node = node.parentNode;
  var when = node.innerHTML.match(/play(now|later)/)[1]
  var contents = (when == "now") ? "Now playing " + what : "Adding " + what + " to the queue"
  contents = contents.replace(/ the piece$/,'')
  if (desc1 !== undefined) {
    contents += ":<br><div class=desc>" + desc1
    if (desc2 !== undefined) {
      contents += "<br>" + desc2
    }
    contents += "</div>"
  }
  showflag(node, contents)
}

//------------------------------------------------------------------------
function zoombutton() {
  var style = infoframe.document.body.className 
  var zoom = style.match(/zoom(\d)/)
  if (!zoom)
    return; //?
  zoom = zoom[1]
  var newzoom = {0:1, 1:2, 2:3, 3:0}
  infoframe.document.body.className = style.replace(zoom, newzoom[zoom])
}

// ======================= SEARCH ===============================


function setsearch(searchfor, filter) {
  if (infoframe == null) // (happens in Google Chrome)
    return;
  if (searchfor == null) // onload
    searchfor = tabmemory.search.searchfor;
  if (filter == null) // onload
    filter = tabmemory.search.filter;
  pushradiobutton(infoframe.document, "filter", filter)
  infoframe.document.getElementById("formsearchfor").value = searchfor
  infoframe.document.getElementById("formfilter").value = filter
  for (var s=1; s<=4; s++) {
    var section = infoframe.document.getElementById("cgi_section" + s)
    if (section)
      section.style.display = ((filter==0 || filter==s)? "block":"none");
  }
  tabmemory.search.searchfor = searchfor
  tabmemory.search.filter = filter
  infoframe.document.body.className = infoframe.document.body.className.replace(/zoom\d/, "zoom"+infoframe.filterzoom[filter])
}

//------------------------------------------------------------------------
function searchonsubmit() {
  var searchforbox = infoframe.document.getElementById("formsearchfor")
  var searchfor = searchforbox.value.replace(/[^ -~]/g,"")
  tabmemory.search.searchfor = searchfor
  searchforbox.value = searchfor
  if (searchfor == "") {
    searchforbox.focus()
    return false
  }
  results = infoframe.document.getElementById("cgi_results")
  if (results)
    results.style.display = "none";
  searchfor = searchfor.toLowerCase()
  var searching = infoframe.document.getElementById("searching")
  if (searching) {
    searching.innerHTML = searching.innerHTML.replace("==SEARCHFOR==", searchfor)
    searching.style.display = "block"
  }
//test  return false
  return true
} 

// ======================= INDEX ===============================

function setindexsidebar() {
  // sidebar
  if (infoframe.sidebarname) {
    tabmemory[infoframe.document.body.id].sidebar = infoframe.sidebarname
  } else {
    infoframe.sidebarname = tabmemory[infoframe.document.body.id].sidebar
  }
  infoframe.document.getElementById("sidebar").innerHTML = top.index_sidebars.document.getElementById(infoframe.sidebarname).childNodes[1].innerHTML 
  // view
  setbutton("view_tracks")
  setbutton("view_pieces")
  if (infoframe.document.body.className.match(/level3/)) {
    setlevel3view(tabmemory.index.viewsampler)
  }
  // keypad
  if (infoframe.sidebarname.match(/years/)) {
    setprevnextyear("prev")
    setprevnextyear("next")
  } else {
    var ctl = infoframe.document.getElementById(tabmemory.index.initial)
    if (!tabmemory.index.initial || ctl.className.match(/disabled/)) {
      clearprevnextinitial("prev")
      clearprevnextinitial("next")
    } else {
      ctl.className = "ctl selected";
      setprevnextinitial("prev")
      setprevnextinitial("next")
    }
  }
  // web info
  if (infoframe.webinfo_i) { // from template
    infoframe.document.getElementById("webinfo_i").href = infoframe.webinfo_i.href
    infoframe.document.getElementById("webinfo_i").title = "Open a new tab to search the web for information about " + infoframe.webinfo_i.title
    infoframe.document.getElementById("webinfo").style.display = "block"; // hidden by default
  }
  //
  function setbutton(btn_name) { // from template
    var whynot = infoframe[btn_name]
    if (!whynot) return;
    var btn = infoframe.document.getElementById(btn_name)
    btn.title = whynot
    btn.className += " disabled"
  }
  //
  function setprevnextyear(which) {
    var btn = infoframe.document.getElementById(which)
    var yearmatch = infoframe.location.href.match(/\/(\d+)\.html$/)
    var thisyear
    if (yearmatch) {
      thisyear = yearmatch[1]
    } else { // no match in level 3 page
      var yeardiv = infoframe.document.getElementById("pl_year")
      thisyear = yeardiv?yeardiv.childNodes[1].innerHTML:2008
    }
    var page = sidebarframe.keypad.pop_years_albums[which][thisyear] || ""
    if (page) {
      btn.href = "/data/" + format + "_pop_years_albums/level2/" + page + ".html"
    } else {
      btn.className += " disabled"
    }
    btn.innerHTML = btn.innerHTML.replace(".", page.replace(/19|20/, "'"))
  }
  //
  function setprevnextinitial(which) {
    var btn = infoframe.document.getElementById(which)
    var page = sidebarframe.keypad[infoframe.sidebarname][which][tabmemory.index.initial] || ""
    if (page) {
      btn.href = "/data/" + format + "_" + sidebarframe.keypad[infoframe.sidebarname].dest + "/" + page + ".html"
    } else {
      btn.className += " disabled"
    }
    btn.innerHTML = btn.innerHTML.replace(".", page.replace(/^(.)..+(.)$/, "$1-$2").toUpperCase())
  }
  //
  function clearprevnextinitial(which) {
    var btn = infoframe.document.getElementById(which)
    btn.className += " disabled"
    btn.innerHTML = btn.innerHTML.replace(".", "")
  }
}

//------------------------------------------------------------------------
function indexbuttonselection(ctl) { // not top20 or years (fixed href)
  if (ctl.className.match(/disabled/))
    return false;
  var newselection = ctl.id.replace(/selection_/, "")
  var newview = readradiogroup(infoframe.document, "view")
  var newindexname = newselection + "_" + newview
  var basename = sidebarframe.keypad[newindexname].thispage[tabmemory.index.initial || "A"]
  infoframe.location = "/data/" + format + "_" + newindexname  + "/" + basename + ".html"
}

//------------------------------------------------------------------------
function indexbuttonprevnext(ctl) {
  if (ctl.className.match(/disabled/))
    return false;
  if (!infoframe.sidebarname.match(/years/)) {
    tabmemory.index.initial = ctl.href.match(/\/([a-z0-9])[a-z0-9]*\.html$/)[1].toUpperCase()
  }
}    
    
//------------------------------------------------------------------------
function indexbuttonview(ctl) { // not top20 or years (single view)
  if (ctl.className.match(/disabled/))
    return false;
  var newview = ctl.id.replace(/view_/, "")
  if (infoframe.document.location.href.match(/level2/)) {
    infoframe.location = infoframe.document.location.href.replace(/albums|tracks|pieces/, newview)
  } else { // level 1
    var newindexname = readradiogroup(infoframe.document, "selection") + "_" + newview
    var basename = sidebarframe.keypad[newindexname].thispage[tabmemory.index.initial || "A"]
    infoframe.location = "/data/" + format + "_" + newindexname  + "/" + basename + ".html"
  }
}

// ======================= INDEX LEVEL3 ===============================

function do_xtracks() {
  var params = infoframe.location.search.split("=")
  if (params[0] == "?xtracks") {
    var tracknbrs = params[1].split(",")
    var newvals = {}
    scrolltracksmaybe(tracknbrs[0])
    while (tracknbrorrange = tracknbrs.pop()) {
      var rangesplit = tracknbrorrange.split("-")
      var tracknbr = rangesplit[0]
      var last = rangesplit[1]
      do {
        newvals[tracknbr] = 1
      } while (tracknbr++<last);
    }
    for (var t=1; t<=infoframe.nbrtracks; t++) {
      if (!newvals[t]) { // all initially ticked
        infoframe.document.getElementById("box"+t).checked = 0
        infoframe.document.getElementById("text"+t).className = "unticked"
      }
    }
    showselection()
  }
}

//------------------------------------------------------------------------
function setlevel3view(bool) { // [X] Sampler
  infoframe.document.body.className = "info index level3" + (bool?"_s":"")
  infoframe.document.getElementById("view_sampler").checked = bool
  tabmemory.index.viewsampler = bool
}

//------------------------------------------------------------------------------------
function alltracksclicked(newval) { // [X] All
  var newstyle = newval?"":"unticked"
  for (var t=1; t<=infoframe.nbrtracks; t++) {
    infoframe.document.getElementById("box"+t).checked = newval
    infoframe.document.getElementById("text"+t).className = newstyle
  }
  showselection()
}

//------------------------------------------------------------------------
function onetrackclicked(ctl) { // [X] 1-n
  infoframe.document.getElementById(ctl.id.replace(/box/, "text")).className = ctl.checked?"":"unticked"
  showselection()
}

//------------------------------------------------------------------------
function showselection() {
  var toplay = 0, toskip = 0, secs = 0
  for (var t=1; t<=infoframe.nbrtracks; t++) {
    if (infoframe.document.getElementById("box"+t).checked) {
      toplay++
      secs += Number(infoframe.document.getElementById("secs"+t).innerHTML)
    } else {
      toskip++
    }
  }
  // note the texts below are read by showlevel3flag()
  var sel_show
 if (toplay == 0) {
    sel_show = "No tracks"
  } else if (toplay == 1) {
    sel_show = "One track (==DURATION==)"
  } else if (toskip == 0) {
    sel_show = "All tracks (==DURATION==)"
  } else {
    sel_show = toplay + " tracks (==DURATION==)"
  }
  infoframe.document.getElementById("whichtracks").innerHTML = sel_show.replace("==DURATION==",format_duration(secs))
  infoframe.document.getElementById("checkall").checked = toskip?0:1
}

//------------------------------------------------------------------------
function format_duration(secs) {
  var result = Math.floor(secs/3600) + ":" + Math.floor(secs/60)%60 + ":" + secs%60
  result = result.replace(/:(\d):/, ":0$1:")
  result = result.replace(/:(\d)$/, ":0$1")
  result = result.replace(/^0:0?/, "")
  return result
}

//------------------------------------------------------------------------
function showlevel3flag(ctl) {
  var which = infoframe.document.getElementById("whichtracks").innerHTML
  if (which.match(/^No/))
    return;
  var what = ""
  var desc1 = ""
  var desc2 = ""
  if (which.match(/^One/)) {
    what = 'the track'
    for (t=1; t<=infoframe.nbrtracks; t++) {
      if (infoframe.document.getElementById("box"+t).checked)
        break;
    }
    desc1 = infoframe.document.getElementById("flag1_"+t)
    desc2 = infoframe.document.getElementById("flag2_"+t)
  } else if (which.match(/^\d+/)) { // n tracks
    var nbrtracks = 0, first = 0, last = 0;
    for (var t=1; t<=infoframe.nbrtracks; t++) {
      if (infoframe.document.getElementById("box"+t).checked) {
        nbrtracks++
        if (!first)
          first = t;
        last = t
      }
    }
    if (first + nbrtracks-1 == last) { // possibly a piece
      desc1 = infoframe.document.getElementById("flag1_" + first + "_" + last)
      desc2 = infoframe.document.getElementById("flag2_" + first + "_" + last)
    }
    if (desc1 || desc2) {
      what = "the piece"
    } else {
      what = nbrtracks + " tracks from the album"
    }
  } else {
    what = "the album"
  }
  showinfoflag(ctl, what, (desc1 || infoframe.document.getElementById("flag1")).innerHTML, (desc2 || infoframe.document.getElementById("flag2")).innerHTML)
}

// ======================= RECENT ===============================

function setrecentview(view) { // (*) Listing ( ) Gallery
  if (!view) // onload()
    view = tabmemory.recent.view;
  pushradiobutton(infoframe.document, "view", view)
  infoframe.document.body.className = "info recent " + view
  tabmemory.recent.view = view
}

// ======================= FLAG ===============================

var flagtimeout=null

//------------------------------------------------------------------------
function showflag(ctl, contents) {
  if (flagtimeout) {
    clearTimeout(flagtimeout)
    hideflag()
  }
  var flag = document.getElementById("flag")
  document.getElementById("flagpanel").innerHTML = contents
  positionflag(flag, ctl)
  flag.style.visibility = "visible"
  flagtimeout = setTimeout("hideflag()", 2000)
  //
  function positionflag(flag, button) {
    var framename = button.ownerDocument.body.className.split(" ")[0]
    var frame = framename ? document.getElementById(framename) : top.document.body
    var buttonright = frame.offsetLeft + button.getBoundingClientRect().right
    var buttontop = frame.offsetTop + button.getBoundingClientRect().top
    var buttonbottom = frame.offsetTop + button.getBoundingClientRect().bottom
    // flag right of button, but slide to left if it would go off the right edge, sliding no further than the left edge
    flag.style.left = Math.max(Math.min(buttonright, window.innerWidth - flag.offsetWidth), 0)
    // flag above button, but switch to below if it would go off the top
    flag.style.top = flag.offsetHeight < buttontop ? buttontop - flag.offsetHeight : buttonbottom
  }
}

//------------------------------------------------------------------------
function hideflag() {
  document.getElementById("flag").style.visibility = "hidden"
  flagtimeout = null
}

