text: #include "demo.h"
  
  // we have a set of cyclic glyphs, with each cycle composed of some number N_c
  // of glyphs. our string is made up of each cycle, with each occupying N_c
  // cells, each iterating through the N_c states in a round. the string emerges
  // from the center of the screen, moving in a spiral.
  //
  // so, each iteration, start at the head of the chain, and move one forward.
  // work back along the path, moving back through the string. if we reach the
  // end of the string, clear the cell behind it. eventually, we'll clear the
  // entirety of the string, and we're done.
  //
  // path: the geometry of the spiral is dictated by distance from the center.
  //
  static const char* cycles[] = {
    "🞯🞰🞱🞲🞳🞴",   // 6 five-point asterisks
    "🞵🞶🞷🞸🞹🞺",   // 6 six-point asterisks
    "🞻🞼🞽🞾🞿",    // 5 eight-point asterisks
  #ifndef __APPLE__ // FIXME
    "◧◩⬒⬔◨◪⬓⬕", // 8 half-black squares
    "◐◓◑◒",     // 4 half-black circles
    "◢◣◤◥",     // 4 black triangles
    "◰◳◲◱",     // 4 white squares with quadrants
    "◴◷◶◵",     // 4 white circles with quadrants
    "🞅🞆🞇🞈🞉🞊",   // 6 white circles
    "🞎🞏🞐🞑🞒🞓",   // 6 white squares
    "▤▥▦▧▨▩",   // 6 squares with fill
    "⯁⯂⯃⯄",     // 4 regular black polyhedra
    "⌌⌍⌎⌏",     // 4 crops
  #endif
    NULL,
  };
  
  typedef enum {
    PHASE_SPIRAL,
    PHASE_DONE,
  } phase_e;
  
  // get the new head position, given the old head position
  static void
  get_next_head(struct ncplane* std, struct ncplane* left, struct ncplane* right,
                int* heady, int* headx, phase_e* phase){
    if(*heady == -1 && *headx == -1){
      *headx = ncplane_dim_x(std) / 2;
      *heady = ncplane_dim_y(std) / 2;
      *phase = PHASE_SPIRAL;
      return;
    }
    if(*phase == PHASE_DONE){
      return;
    }
    int lrcol; // left column of right progbar
    int rlcol; // right column of left progbar
    int trow, brow; // top and bottom
    ncplane_abs_yx(right, &trow, &lrcol);
    ncplane_abs_yx(left, &brow, &rlcol);
    rlcol += ncplane_dim_x(left) - 1;
    brow += ncplane_dim_y(left) - 1;
    // in the spiral cycle. it's a counterclockwise spiral, come out the bottom,
    // calculate distances from the center in both directions. if the absolute
    // values are equal, turn counterclockwise, *unless* xdist is positive and
    // ydist is negative. in that case, we're coming down the left side, and
    // need go down one further, only then turning right. that case is xdist is
    // positive, ydist is negative, and xdist + ydist == -1. otherwise, continue
    // moving counterclockwise (right if |ydist|>|xdist| and negative y, left
    // if |ydist|>|xdist| and positive y, etc.)
    int ydist = ncplane_dim_y(std) / 2 - *heady;
    int xdist = ncplane_dim_x(std) / 2 - *headx;
    if(*heady < trow && xdist < 0){
      *phase = PHASE_DONE;
    }
    if(ydist == 0 && xdist == 0){
      ++*heady; // move down
    }else if(abs(ydist) == abs(xdist)){ // corner
      if(ydist < 0 && xdist > 0){ // lower-left, move down
        ++*heady; // move down
      }else if(ydist < 0 && xdist < 0){ // lower-right, move up
        --*heady;
      }else if(xdist > 0){ // upper-left, move down
        ++*heady;
      }else{ // upper-right, love left
        --*headx;
      }
    }else if(ydist < 0 && xdist > 0 && ydist + xdist == -1){ // new iteration
      ++*headx;
    }else{
      if(abs(ydist) > abs(xdist)){
        if(ydist < 0){
          ++*headx;
        }else{
          --*headx;
        }
      }else{
        if(xdist < 0){
          if(--*heady < trow){
            *phase = PHASE_DONE;
          }
        }else{
          ++*heady;
        }
      }
    }
  }
  
  static void
  get_next_end(struct ncplane* std, struct ncplane* left, struct ncplane* right,
               int* endy, int* endx, phase_e* endphase){
    get_next_head(std, left, right, endy, endx, endphase);
  }
  
  // determine the total number of moves we will make. this is most easily and
  // accurately done by running through a loop.
  static int
  determine_totalmoves(struct ncplane* std, struct ncplane* left, struct ncplane* right,
                       int heady, int headx, int endy, int endx, int totallength){
    int moves = 0, length = 0;
    phase_e headphase, endphase;
    do{
      get_next_head(std, left, right, &heady, &headx, &headphase);
      if(length < totallength){
        ++length;
      }else{
        get_next_end(std, left, right, &endy, &endx, &endphase);
      }
      ++moves;
    }while(endy != heady || endx != headx);
    return moves;
  }
  
  // find the 'iters'th EGC in 'utf8', modulo the number of EGCs in 'utf8'
  static int
  spin_cycle(const char* utf8, int iters){
    int offsets[10]; // no cycles longer than this
    mbstate_t mbs = { };
    int offset = 0;
    size_t s;
    int o = 0;
    while((s = mbrtowc(NULL, utf8 + offset, strlen(utf8 + offset) + 1, &mbs)) != (size_t)-1){
      if(s == 0){ // ended with o EGCs
        if(o == 0){
          return -1;
        }
        return offsets[iters % o];
      }
      if(o == sizeof(offsets) / sizeof(*offsets)){
        break;
      }
      offsets[o] = offset;
      offset += s;
      if(++o == iters){
        return offsets[iters % o];
      }
    }
    return -1;
  }
  
  static int
  drawcycles(struct ncplane* std, struct ncprogbar* left, struct ncprogbar* right,
             int length, int endy, int endx, phase_e endphase, uint64_t* channels,
             int iters){
    const char** c = cycles;
    const char* cstr = *c;
    int offset = spin_cycle(cstr, iters);
    if(offset < 0){
      return -1;
    }
    while(length--){
      get_next_head(std, ncprogbar_plane(left), ncprogbar_plane(right),
                    &endy, &endx, &endphase);
      free(ncplane_at_yx(std, endy, endx, NULL, channels));
      ncplane_set_bg_rgb(std, ncchannels_bg_rgb(*channels));
      ncplane_set_fg_rgb(std, 0xffffff);
      int sbytes;
      if(ncplane_putegc_yx(std, endy, endx, cstr + offset, &sbytes) < 0){
        return -1;
      }
      offset += sbytes;
      if(cstr[offset] == '\0'){
        cstr = *++c;
        if(cstr == NULL){
          c = cycles;
          cstr = *c;
        }
        if((offset = spin_cycle(cstr, iters)) < 0){
          return -1;
        }
      }
    }
    return 0;
  }
  
  static int
  animate(struct notcurses* nc, struct ncprogbar* left, struct ncprogbar* right){
    int dimy, dimx;
    struct ncplane* std = notcurses_stddim_yx(nc, &dimy, &dimx);
    int headx = -1;
    int heady = -1;
    int endy = -1;
    int endx = -1;
    int totallength = 0;
    for(const char** c = cycles ; *c ; ++c){
      totallength += ncstrwidth(*c);
    }
    int totalmoves = determine_totalmoves(std, ncprogbar_plane(left), ncprogbar_plane(right),
                                          heady, headx, endy, endx, totallength);
    ++totalmoves; // for final render
    // headx and heady will not return to their starting location until the
    // string begins to disappear. endx and endy won't equal heady/headx until
    // the entire string has been consumed.
    struct timespec delay;
    uint64_t iterns = (timespec_to_ns(&demodelay) * 5) / totalmoves;
    phase_e headphase = PHASE_SPIRAL;
    phase_e endphase = PHASE_SPIRAL;
    int moves = 0;
    struct timespec expected;
    clock_gettime(CLOCK_MONOTONIC, &expected);
    uint64_t expect_ns = timespec_to_ns(&expected);
    uint64_t channels = 0;
    int length = 1;
    do{
      get_next_head(std, ncprogbar_plane(left), ncprogbar_plane(right),
                    &heady, &headx, &headphase);
      if(headphase != PHASE_DONE){
        if(drawcycles(std, left, right, length, endy, endx, endphase, &channels, moves) < 0){
          return -1;
        }
      }
      if(length < totallength){
        ++length;
      }else{
        get_next_end(std, ncprogbar_plane(left), ncprogbar_plane(right),
                    &endy, &endx, &endphase);
        ncplane_set_fg_rgb(std, ncchannels_fg_rgb(channels));
        ncplane_putwc_yx(std, endy, endx, L'▄');
      }
      ++moves;
      ncprogbar_set_progress(left, ((float)moves) / totalmoves);
      ncprogbar_set_progress(right, ((float)moves) / totalmoves);
      DEMO_RENDER(nc);
      struct timespec now;
      expect_ns += iterns;
      clock_gettime(CLOCK_MONOTONIC, &now);
      uint64_t nowns = timespec_to_ns(&now);
      if(nowns < expect_ns){
        ns_to_timespec(expect_ns - nowns, &delay);
        demo_nanosleep(nc, &delay);
      }
    }while(endy != heady || endx != headx);
    ncprogbar_set_progress(left, 1);
    ncprogbar_set_progress(right, 1);
    DEMO_RENDER(nc);
    return 0;
  }
  
  static int
  make_pbars(struct ncplane* column, struct ncprogbar** left, struct ncprogbar** right){
    int dimy, dimx, coly, colx, colposy, colposx;
    struct notcurses* nc = ncplane_notcurses(column);
    notcurses_stddim_yx(nc, &dimy, &dimx);
    ncplane_dim_yx(column, &coly, &colx);
    ncplane_yx(column, &colposy, &colposx);
    ncplane_options opts = {
      .x = colposx / 4 * -3,
      .rows = coly,
      .cols = (dimx - colx) / 4,
    };
    struct ncplane* leftp = ncplane_create(column, &opts);
    if(leftp == NULL){
      return -1;
    }
    ncplane_set_base(leftp, " ", 0, NCCHANNELS_INITIALIZER(0xdd, 0xdd, 0xdd, 0x1b, 0x1b, 0x1b));
    ncprogbar_options popts = { };
    ncchannel_set_rgb8(&popts.brchannel, 0, 0, 0);
    ncchannel_set_rgb8(&popts.blchannel, 0, 0xff, 0);
    ncchannel_set_rgb8(&popts.urchannel, 0, 0, 0xff);
    ncchannel_set_rgb8(&popts.ulchannel, 0, 0xff, 0xff);
    *left = ncprogbar_create(leftp, &popts);
    if(*left == NULL){
      return -1;
    }
    opts.x = colx + colposx / 4;
    struct ncplane* rightp = ncplane_create(column, &opts);
    if(rightp == NULL){
      return -1;
    }
    ncplane_set_base(rightp, " ", 0, NCCHANNELS_INITIALIZER(0xdd, 0xdd, 0xdd, 0x1b, 0x1b, 0x1b));
    popts.flags = NCPROGBAR_OPTION_RETROGRADE;
    *right = ncprogbar_create(rightp, &popts);
    if(*right == NULL){
      ncprogbar_destroy(*left);
      return -1;
    }
    return 0;
  }
  
  int animate_demo(struct notcurses* nc){
    if(!notcurses_canutf8(nc)){
      return 0;
    }
    int dimy, dimx;
    struct ncplane* n = notcurses_stddim_yx(nc, &dimy, &dimx);
    ncplane_erase(n);
    ncplane_home(n);
    uint32_t tl = 0, tr = 0, bl = 0, br = 0;
    ncchannel_set_rgb8(&tl, 0, 0, 0);
    ncchannel_set_rgb8(&tr, 0, 0xff, 0);
    ncchannel_set_rgb8(&bl, 0, 0, 0xff);
    ncchannel_set_rgb8(&br, 0, 0xff, 0xff);
    if(ncplane_highgradient(n, tl, tr, bl, br, dimy - 1, dimx - 1) < 0){
      return -1;
    }
    ncplane_set_fg_rgb(n, 0xf0f0a0);
    ncplane_set_bg_rgb(n, 0);
    int width = 40;
    if(width > dimx - 8){
      if((width = dimx - 8) <= 0){
        return -1;
      }
    }
    int height = 40;
    if(height >= dimy - 4){
      if((height = dimy - 5) <= 0){
        return -1;
      }
    }
    const int planey = (dimy - height) / 2 + 1;
    ncplane_options nopts = {
      .y = planey,
      .x = NCALIGN_CENTER,
      .rows = height,
      .cols = width,
      .flags = NCPLANE_OPTION_HORALIGNED,
    };
    struct ncplane* column = ncplane_create(n, &nopts);
    if(column == NULL){
      return -1;
    }
    struct ncprogbar *pbarleft, *pbarright;
    if(make_pbars(column, &pbarleft, &pbarright)){
      ncplane_destroy(column);
      return -1;
    }
    ncplane_destroy(column);
    int r = animate(nc, pbarleft, pbarright);
    ncprogbar_destroy(pbarleft);
    ncprogbar_destroy(pbarright);
    return r;
  }
  