<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="css/style.css">
  <link href="https://fonts.googleapis.com/css?family=Megrim" rel="stylesheet">
  <link href="https://fonts.googleapis.com/css?family=Dosis:200,300,400,500,600,700" rel="stylesheet">
  <link href="https://fonts.googleapis.com/css?family=Nunito:400,600,700,800" rel="stylesheet">
</head>

<body>



	<!-- 	LANDING -->

	<div id="loader_div"></div>  <!-- brief blank loader screen; fades out on document ready -->
	<script> document.addEventListener("DOMContentLoaded", function() { $("#loader_div").fadeOut(1500); }); </script>

	<div id="landing_page_div">
		<div id="landing_content_div">
			<img id="landing_svg_bg" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/title_landing.svg">
			<img id="button_game_mode" class="button_landing" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/button_game_mode.svg">
			<img id="button_ambient_mode" class="button_landing" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/button_ambient_mode.svg">
		</div>
	</div>
  
  <!-- places jquery and landing resize earlier here to avoid codepen preview timeout -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script>
    $("#landing_content_div").css({ height: "90%", width: "90%" });
    $("#landing_svg_bg").css({ height: "100%", width: "100%" });  
    if ( $("#landing_content_div").height() > $("#landing_content_div").width() ) {
      $("#landing_svg_bg").height( $("#landing_content_div").width() );
      $("#landing_content_div").height( $("#landing_svg_bg").height() );
    } else {
      $("#landing_svg_bg").width( $("#landing_content_div").height() );
      $("#landing_content_div").width( $("#landing_svg_bg").width() );
    }
  </script>
  


	<!-- 	HEADER -->

	<div id="header_div">
		<img class="title_header_svg" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/title_header_dark.svg">
		<img id="icon_info" class="header_svg icon" title="learn more about the game" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/icon_info.svg">
    <img id="icon_dark_toggle" class="header_svg icon" title="toggle dark mode" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/icon_dark_toggle.svg">
		<img id="icon_shadows_on" class="header_svg icon_shadows icon" title="toggle leaf shadow visibility" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/icon_shadows_on.svg">
		<img id="icon_shadows_off" class="header_svg icon_shadows icon" title="toggle leaf shadow visibility" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/icon_shadows_off.svg">
		<a id="save" title="take a screenshot">
			<img id="icon_camera" class="header_svg icon" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/icon_camera.svg">
		</a>
		<img id="icon_pause" class="header_svg icon_game_run icon" title="pause the game" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/icon_pause.svg">
		<img id="icon_play" class="header_svg icon_game_run icon" title="resume the game" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/icon_play.svg">
		<img id="icon_restart" class="header_svg icon" title="restart" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/icon_restart.svg">
	</div>



	<!-- FOOTER -->

	<div id="footer_div">
		<p id="time" class="footer_text">Year <span id="year_count">1</span><span id="season_left">, Spring</span></p>
		<svg id="pie_svg_left" class="pie_svg" viewBox="0 0 100 100">
      <circle id="pie_circle_left" class="pie_circle" r="25" cx="50" cy="50">
    </svg>
		<!-- ambient mode -->
		<div id="ambient_footer_right">
			<p id="season_right" class="footer_text">Spring</p>
			<svg id="pie_svg_right" class="pie_svg" viewBox="0 0 100 100"><circle id="pie_circle_right" class="pie_circle" r="25" cx="50" cy="50"></svg>
		</div>
		<!-- game mode -->
		<p id="height_text" class="footer_text">Highest Red Flower So Far</p>
		<div id="tag_div">
			<img id="tag_svg" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/tag.svg">
			<p id="tag_content"><span id="height_number">0</span><span id="percent">%</span></p>
		</div>
	</div>



	<!-- SCREEN -->

  <div id="canvas_container_div">


  	<!-- canvas (large width/height for higher resolution) -->

  	<canvas id="canvas" width=1000" height="1000"></canvas> 


  	<!-- announcements -->

  	<p id="year_announcement" class="announcement">YEAR 1</p>
  	<p id="season_announcement" class="announcement">SPRING</p>

  	<p id="height_announcement" class="announcement">0%</p>
  	<p id="hundred_pct_large_height_announcement" class="announcement">100%</p>

  	<div id="demo_elimination_div" class="announcement">
	  	<img id="demo_elimination_bg" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/demo_elimination_bg.svg">
	    <div id="mask">
	      <img id="demo_plant_small_1" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/demo_plant_small.svg">
	      <img id="demo_plant_small_2" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/demo_plant_small.svg">
	    </div>
	    <img id="demo_hand" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/demo_hand.svg">
	  </div>

		<div id="demo_change_div" class="announcement">
			<img id="demo_change_bg" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/demo_change_bg.svg">
			<img id="demo_flower_red" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/demo_flower_red.svg">
			<img id="demo_leaf" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/demo_leaf.svg">
		</div>

    <img id="milestone_first_red_flower" class="milestone announcement" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/milestone_first_red_flower.svg">
    <img id="milestone_third" class="milestone announcement" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/milestone_third.svg">
    <img id="milestone_half" class="milestone announcement" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/milestone_half.svg">
    <img id="milestone_two_thirds" class="milestone announcement" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/milestone_two_thirds.svg">
    <img id="milestone_90" class="milestone announcement" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/milestone_90.svg">


		<!-- modals -->

  	<img id="modal_play" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/modal_play.svg">

  	<img id="modal_card_gameplay_screen" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/modal_card_gameplay_screen.svg">
  	<div id="modal_card_gameplay_screen_inner_div">
	  	<img id="modal_ambient_text_gameplay_screen" class="modal_gameplay_screen_text" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/modal_ambient_text.svg">
	  	<img id="modal_game_text_gameplay_screen" class="modal_gameplay_screen_text" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/modal_game_text.svg">
	  </div>

  	<img id="icon_exit_modal_gameplay_info" class="icon_exit_modal" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/icon_exit_modal.svg">

  	<div id="modal_restart_div">
  		<img id="modal_restart" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/modal_restart.svg">
  		<img id="button_restart_confirm_hover" class="button_restart" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/button_restart_confirm_hover.svg">
  		<img id="button_restart_cancel_hover" class="button_restart" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/button_restart_cancel_hover.svg">
  	</div>


	  <!-- options screen overlays -->

	  <div id="overlay_game_mode_options_div" class="overlay_div">
		  <img id="overlay_game_mode_options_bg" class="overlay_bg" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/overlay_game_mode_options_bg.svg">
		  <img id="helper_game_info" class="helper" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/helper.svg">
		  <img id="option_beginner" class="option" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/option_beginner.svg">
		  <img id="option_intermediate" class="option" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/option_intermediate.svg">
		  <img id="option_expert" class="option" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/option_expert.svg">
			<img class="button_sow" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/button_sow.svg">
			<img id="modal_card_game_screen" class="modal_card_options_screens" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/modal_card_options_screens.svg">
			<img id="modal_game_text" class="modal_options_text" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/modal_game_text.svg">
			<img id="icon_exit_modal_game_info" class="icon_exit_modal" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/icon_exit_modal.svg">
		</div>

		<div id="overlay_ambient_mode_options_div" class="overlay_div">
		  <img id="overlay_ambient_mode_options_bg" class="overlay_bg" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/overlay_ambient_mode_options_bg.svg">
			<img id="helper_ambient_info" class="helper" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/helper.svg">
		  <img id="option_first" class="option" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/option_first.svg">
		  <img id="option_second" class="option" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/option_second.svg">
		  <img id="option_third" class="option" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/option_third.svg">
			<img class="button_sow" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/button_sow.svg">
			<img id="modal_card_ambient_screen" class="modal_card_options_screens" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/modal_card_options_screens.svg">
			<img id="modal_ambient_text" class="modal_options_text" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/modal_ambient_text.svg">
			<img id="icon_exit_modal_ambient_info" class="icon_exit_modal" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/icon_exit_modal.svg">
		</div>


		<!-- end-of-game overlays -->

		<div id="game_over_div" class="end_of_game_div">
			<img id="overlay_game_over" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/overlay_game_over.svg">
			<img id="button_try_again_hover" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/button_try_again_hover.svg">
		</div>

		<div id="game_win_div" class="end_of_game_div">
			<img id="game_win_YOU" class="game_win_svg" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/overlay_game_win_YOU.svg">
			<img id="game_win_KISSED" class="game_win_svg" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/overlay_game_win_KISSED.svg">
			<img id="game_win_THE" class="game_win_svg" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/overlay_game_win_THE.svg">
			<img id="game_win_SKY" class="game_win_svg" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/overlay_game_win_SKY.svg">
			<p id="game_win_gen_count_text" class="game_win_text">AFTER <span id="game_win_gen_number"></span> GENERATIONS</p>
			<p id="game_win_mode_text" class="game_win_text">IN <span id="game_win_mode"></span> MODE</p>
			<img id="button_play_again" class="button_game_win_play_again" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/button_play_again.svg">
			<img id="button_play_again_hover" class="button_game_win_play_again" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/button_play_again_hover.svg">
		</div>

	</div>

	<a href="https://matthewma.in/" id="copyright">© Matthew Main <span id="this_year"></span></a>

                                                                                
                                                                                
</body>

</html>

body {
	background: #202020;
	font-family: "Dosis", Avenir, sans-serif;
	font-weight: 200;
	margin: 0;
	user-select: none;
}

#loader_div {
  position: fixed;
  width: 100%;
  height: 100%;
  background: #202020;
  opacity: 1;
  z-index: 3;
}

#copyright {
  position: absolute;
  right: 0;
  bottom: 0;
  font-size: 6pt;
  letter-spacing: 0.5px;
  padding: 0 5px 5px 0;
  text-decoration: none;
  color: #777777;
  z-index: 2;
}

.hover {
  opacity: 0;
}
.hover:hover { 
  opacity: 1;
}





/*** LANDING ***/


#landing_page_div {
  position: fixed;
  width: 100%;
  height: 100%;
  background: #202020;
  z-index: 1;
}

#landing_content_div {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
  width: 80%;
  height: 80%;
  /*(further height and width handled by scaleLanding() in ui.js)*/
}

#landing_svg_bg {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  height: 100%;
  /*(further height and width handled by scaleLanding() in ui.js)*/
}

.button_landing {
  position: absolute;
  width: 30.4%;
  top: 70.5%;
  cursor: pointer;
}
.button_landing:hover {
  background: #333333;
}
#button_game_mode {
  left: 11%;
}
#button_ambient_mode {
  left: 60.1%;
}






/*** HEADER ***/

#header_div {
	position: absolute;
  left: 50%;
  transform: translate(-50%);
}

.title_header_svg {
	height: 86%;
}

.icon {
	position: absolute;
	bottom: 27%;
	height: 20%;
	cursor: pointer;
	display: none;
}

#icon_info {
	right: 32%;
	height: 21.5%;
	bottom: 25.5%;
}

#icon_dark_toggle {
  right: 25.75%;
}

#icon_shadows_on {
	right: 19%;
	visibility: visible;
}

#icon_shadows_off {
	right: 19%;
	visibility: hidden;
}

#icon_camera {
	right: 12.5%;
}

#icon_pause {
	right: 6%;
	visibility: visible;
}

#icon_play {
	right: 6%;
	visibility: hidden;
}

#icon_restart {
	right: 0;
	display: block;
}







/*** GAME SCREEN ***/


#canvas_container_div {
  position: absolute;
  top: 52%;
  left: 50%;
  transform: translate(-50%,-50%);
/*   box-shadow: 0 0 3px 0 rgba(0,0,0,0.4); */
}

#canvas {
	position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
	width: 100%;
  box-shadow: 0 0 3px 0 rgba(0,0,0,0.4);
}






/*** FOOTER ***/


#footer_div {
	position: absolute;
  left: 50%;
  transform: translate(-50%);
  display: none;
}

.footer_text {
	position: relative;
	font-size: 9pt;
	letter-spacing: 0.4pt;
	color: #D5D8C5;
	margin-top: 5px;
	top: 12px;
}

#time {
	float: left;
}

.pie_circle {
	fill: none;
  stroke: #D5D8C5;
  stroke-width: 50;
}

.pie_svg {
  position: absolute;
  transform: rotate(-90deg);
  background-color: rgba(213,216,197,.25);
  border-radius: 50%;
}

#pie_svg_left {
  position: relative;
  top: 15px;
  left: 6px;
  height: 12px;
}

#ambient_footer_right {
	display: none;
}

#season_right {
  position: absolute;
  right: 20px;
}

#pie_svg_right {
	top: 17px;
	right: 0;
	height: 13px;
}

#height_text {
	float: right;
	margin: 5px 12% 0 0;
}

#tag_div {
	position: absolute;
	top: -1px;
	right: 1%;
  width: 9%;
  max-width: 45px;
}

#tag_svg {
	position: absolute;
	width: 100%;
}

#tag_content {
  font-family: "Nunito", Avenir, sans-serif;
  position: absolute;
  margin: 0;
  top: 24.5px;
  left: 50%;
  transform: translate(-50%, -50%);
  font-weight: 700;
  font-size: 10pt;
  color: #FFFFFF;
}

#percent {
	position: relative;
	font-weight: 400;
  padding-left: 1px;
}






/*** SCREEN OVERLAYS ***/


.overlay_div {
	visibility: hidden;
}

.overlay_bg {
	position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
	width: 100%;
}

.helper {
	position: absolute;
	top: 17.5%;
  left: 50%;
  transform: translate(-50%,-50%);
  width: 11%;
  cursor: pointer;
}

.option {
	position: absolute;
  left: 49.7%;
  transform: translate(-50%,-50%);
  width: 50%;
  cursor: pointer;
}

#option_beginner {
	top: 38.5%;
}

#option_intermediate {
	top: 52.4%;
	opacity: 0;
}

#option_expert {
	top: 65.8%;
	opacity: 0;
}

#option_first {
	left: 50.15%;
	top: 38.7%;
}

#option_second {
	left: 50.15%;
	top: 52.6%;
	opacity: 0;
}

#option_third {
	left: 50.15%;
	top: 66%;
	opacity: 0;
}

.button_sow {
  position: absolute;
  top: 85%;
  left: 50%;
  transform: translate(-50%,-50%);
  width: 18%;
  background-color: rgba(17, 17, 17, 0.15);
  cursor: pointer;
}
.button_sow:hover {
	background-color: rgba(17, 17, 17, 0.3);
}

.end_of_game_div {
  position: absolute;
  width: 100%;
  height: 100%;
  visibility: hidden;
  opacity: 0;
}

#overlay_game_over {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%,0);
  width: 100%;
}

#button_try_again_hover {
  position: absolute;
  top: 83.8%;
  left: 50%;
  transform: translate(-50%,-50%);
  width: 52%;
  opacity: 0;
  cursor: pointer;
}
#button_try_again_hover:hover {
  opacity: 1;
}

.game_win_svg {
  position: absolute;
  display: none;
}

#game_win_YOU {
  top: 1%;
  left: 50%;
  transform: translate(-50%);
  width: 20%;
}

#game_win_KISSED {
  top: 14%;
  left: 50%;
  transform: translate(-50%); 
  width: 90%; 
}

#game_win_THE {  
  top: 42.5%;
  left: 5%;
  height: 20%;
}

#game_win_SKY {
  top: 42.5%;
  right: 5%;
  height: 20%;
}

.game_win_text {
  position: absolute;
  color: #ffffff;
  font-size: 17pt;
  font-weight: 600;
  letter-spacing: 1.4px;
  text-shadow: 0 0 3px #000000, 0 0 2px #000000;
  width: 100%;
  text-align: center;
  display: none;
}

#game_win_gen_count_text {
  top: 60.5%;
}

#game_win_mode_text {
  top: 68%;
}

#game_win_gen_number {
  font-size: 23pt;
}

#game_win_mode {
  font-size: 19pt;
}

#button_play_again {
  position: absolute;
  top: 89.5%;
  left: 50%;
  transform: translate(-50%,-50%);
  width: 51.4%;
  display: none;
}

#button_play_again_hover {
  position: absolute;
  top: 89.5%;
  left: 50%;
  transform: translate(-50%,-50%);
  width: 51.4%;
  opacity: 0;
  cursor: pointer;
  display: none;
  opacity: 0;
}
#button_play_again_hover:hover {
  opacity: 1;
}






/*** MODALS ***/


.modal_card_options_screens {
	position: absolute;
  top: 55.1%;
  left: 50%;
  transform: translate(-50%,-50%);
  width: 86%;
  visibility: hidden;
}

.modal_options_text {
	position: absolute;
  left: 50%;
  transform: translate(-50%,-50%);
  width: 69%;
  visibility: hidden;
}

#modal_game_text {
	top: 56%;
}

#modal_ambient_text {
	top: 53.5%;
}

#modal_card_gameplay_screen {
	position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
  width: 86%;
  visibility: hidden;
}

#modal_card_gameplay_screen_inner_div {
	position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
  width: 80%;
  height: 80%;
  direction: rtl; /*(orders elements right to left so scroll bar is on left)*/
  overflow: auto;
  visibility: hidden;
}

.modal_gameplay_screen_text {
	position: absolute;
  left: 50%;
  transform: translate(-50%,-50%);
  width: 85%;
  visibility: hidden;
}

#modal_ambient_text_gameplay_screen {
	top: 38%;
}

#modal_game_text_gameplay_screen {
  top: 113%;
  margin-bottom: 15%;
}

.icon_exit_modal {
	position: absolute;
  top: 28%;
  left: 82.5%;
  transform: translate(-50%,-50%);
  width: 8%;
  visibility: hidden;
  cursor: pointer;
}

#icon_exit_modal_gameplay_info {
	top: 13%;
  left: 88%;
  width: 7%;
}

#modal_play {
	position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
  width: 20%;	
  visibility: hidden;
  cursor: pointer;
}

#modal_restart_div {
	position: absolute;
  top: 2%;
  left: 58%;
  width: 40%;	
  visibility: hidden;
}

#modal_restart {
	width: 100%;
}

.button_restart {
	position: absolute;
  left: 50.05%;
  transform: translate(-50%,-50%);
  width: 72%;
  opacity: 0;
  cursor: pointer;
}
.button_restart:hover {
	opacity: 1;
}

#button_restart_confirm_hover {
	top: 45%;
}

#button_restart_cancel_hover {
	top: 74.5%;
}







/*** ANNOUNCEMENTS ***/


.announcement {
  pointer-events: none;
}

#year_announcement {
	position: absolute;
	top: 30%;
  left: 50%;
  transform: translate(-50%,-50%);
  margin: 0;
  width: 100%;
  text-align: center;
  font-size: 35pt;
  letter-spacing: 2.5pt;
  opacity: 0;
}

#season_announcement {
	position: absolute;
	top: 42%;
  left: 50%;
  transform: translate(-50%,-50%);
  margin: 0;
  width: 100%;
  text-align: center;
  font-size: 16pt;
  font-weight: 400;
  letter-spacing: 1.25pt;
  opacity: 0;
}

#height_announcement {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
  margin: 0;
  width: 100%;
  font-family: "Nunito", Avenir, sans-serif;
  text-align: center;
  font-size: 10pt;
  font-weight: 700;
  letter-spacing: 0;
  opacity: 0;
}

#hundred_pct_large_height_announcement {
  position: absolute;
  top: 30%;
  left: 50%;
  transform: translate(-50%,-50%);
  margin: 0;
  width: 100%;
  font-family: "Nunito", Avenir, sans-serif;
  color: rgb( 130, 0, 0 );
  text-align: center;
  font-size: 100pt;
  font-weight: 600;
  letter-spacing: 0;
  opacity: 0;
}




#demo_elimination_div {
  position: absolute;
  width: 100%;
  height: 100%;
  visibility: hidden;
  opacity: 0;
}

#demo_elimination_bg {
  position: absolute;
  left: 50%;
  top: 30%;
  transform: translate(-50%,-50%);
  width: 65%;
}

#demo_hand {
  position: absolute;
  width: 8%;
  left: 30%;
  top: 40%;
  animation-name: swipe;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-duration: 3s;
}

#mask {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  clip-path: inset( 25% 45% 53.95% 36% ); 
/*   background: rgba( 0, 0, 0, 0.2 ); */
}

#demo_plant_small_1 {
  position: absolute;
  width: 8.5%; 
  top: 32.65%;
  left: 36.6%;
  opacity: 0.5;
  animation-name: fall_1;
  animation-timing-function: ease-in;
  animation-iteration-count: infinite;
  animation-duration: 3s;
}

#demo_plant_small_2 {
  position: absolute;
  width: 8.5%;
  top: 32.65%;
  left: 45.5%;
  opacity: 0.5;
  animation-name: fall_2;
  animation-timing-function: ease-in;
  animation-iteration-count: infinite;
  animation-duration: 3s;
}

#demo_change_div {
  position: absolute;
  width: 100%;
  height: 100%;
  visibility: hidden;
  opacity: 0;
}

#demo_change_bg {
  position: absolute;
  left: 50%;
  top: 30%;
  transform: translate(-50%,-50%);
  width: 65%;
}

#demo_flower_red {
  position: absolute;
  width: 4.8%;
  left: 57.9%;
  top: 32.1%;
  opacity: 0.5;
  transform: rotate( -5deg );
  animation-name: twist;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-duration: 2s;
}

#demo_leaf {
  position: absolute;
  width: 6.5%;
  left: 57.1%;
  top: 41%;
  transform: rotate( -5deg );
  opacity: 0.5;
  animation-name: twist;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-duration: 2s;
}




.milestone {
  position: absolute;
  top: 14%;
  left: 50%;
  transform: translate(-50%);
  visibility: hidden;
  opacity: 0;
}

#milestone_first_red_flower {
  width: 50%;
}

#milestone_third {
  width: 77%;
}

#milestone_half {
  width: 62%;
}

#milestone_two_thirds {
  width: 76%;
}

#milestone_90 {
  width: 45%; 
}









/*** ANIMATIONS ***/


@keyframes swipe {
  0%   { left: 49.5%; }
  40%  { left: 29%;   }
  100% { left: 29%;   }
}

@keyframes fall_1 {
  0%   { top: 32.65%; opacity: 0.5 }
  20%  { top: 32.65%;              }
  60%  {              opacity: 0   }
  67%  { top: 47%;                 }
  100% { top: 47%;    opacity: 0   }
}

@keyframes fall_2 {
  0%   { top: 32.65%; opacity: 0.5 }
  13%  { top: 32.65%;              }
  53%  {              opacity: 0   }
  60%  { top: 47%;                 }
  100% { top: 47%;    opacity: 0   }
}

@keyframes twist {
  0%   { transform: rotate( -10deg ); }
  50%  { transform: rotate( 10deg ); }
  100% { transform: rotate( -10deg ); }
}


















//////////////////////////////////////////////////////////// 
////////////      KISS THE SKY: Version 1.1      ///////////
////////////////////////////////////////////////////////////

// © Matthew Main 2019 
// https://github.com/matthewmain/kiss_the_sky/tree/master/builds/v1.1






///////// TOOLS /////////



var Tl = {

	//random integer between two numbers (min/max inclusive)
	rib: function( min, max ) {
 		return Math.floor( Math.random() * ( Math.floor(max) - Math.ceil(min) + 1 ) ) + Math.ceil(min);
	},

	//random float between two numbers
	rfb: function( min, max ) {
 		return Math.random() * ( max - min ) + min;
	},

	//random element from array
	refa: function( array ) {
		return array[ Math.floor( Math.random() * array.length ) ];
	},

	//converts radians to degrees
	radToDeg: function( radian ) {
	  return radian * 180 / Math.PI;
	},

	//converts degrees to radians
	degToRad: function( degree ) {
	  return degree / 180 * Math.PI;
	},

	//pauses program
	pause: function( milliseconds ) {
  	var then = Date.now(); 
  	var now;
  	do { now = Date.now(); } while ( now - then < milliseconds );
	},

	//draws a simple symmetrical arc between two points
	arcFromTo: function( startPoint, endPoint, arcHeight ) {
	  var ah = arcHeight;  // arc height as ratio of distance between points
	  var p1 = { x: startPoint.cx, y: startPoint.cy };
	  var p2 = { x: endPoint.cx, y: endPoint.cy };
	  var mp = { x: ( p1.x + p2.x ) / 2, y: ( p1.y + p2.y ) / 2 } ;  // mid point
	  var ccp = { x: mp.x + ( p2.y - p1.y ) * ah, y: mp.y + ( p1.x - p2.x ) * ah };  // curve control point
	  return ctx.quadraticCurveTo( ccp.x, ccp.y, p2.x, p2.y );
	},

	///rgba color shift (shifts an rgba color gradually over a designated number of iterations)
	rgbaCs: function( startColor, endColor, currentColor, totalIterations ) {
		var sc = startColor;
		var ec = endColor;
		var cc = currentColor;
		var ti = totalIterations;
		var rsi = (ec.r-sc.r)/ti;  // red shift increment
		var gsi = (ec.g-sc.g)/ti;  // green shift increment
		var bsi = (ec.b-sc.b)/ti;  // blue shift increment
		var asi = (ec.a-sc.a)/ti;  // alpha shift increment
	  var r = Math.abs(ec.r-cc.r) < Math.abs(rsi) ? ec.r : cc.r + rsi;  // redshift
	  var g = Math.abs(ec.g-cc.g) < Math.abs(gsi) ? ec.g : cc.g + gsi;  // greenshift
	  var b = Math.abs(ec.b-cc.b) < Math.abs(bsi) ? ec.b : cc.b + bsi;  // blueshift
	  var a = Math.abs(ec.a-cc.a) < Math.abs(asi) ? ec.a : cc.a + asi;  // alphashift
	  return { r: r, g: g, b: b, a: a };
	}


};












///////// VERLET /////////




////---INITIATION---////


///canvas  (*canvas width & height must be equal to retain aspect ratio)
var canvasContainerDiv = document.getElementById("canvas_container_div");
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var canvRatio = 0.75;  // canvas ratio, as canvas size to lowest of window width or height
var canvasPositionLeft = canvas.getBoundingClientRect().left + window.scrollX;
var canvasPositionTop = canvas.getBoundingClientRect().top + window.scrollY;

///trackers
var points = [], pointCount = 0;
var spans = [], spanCount = 0;
var skins = [], skinCount = 0;
var worldTime = 0;  // time as frame count

///settings
var viewPoints = false;  // (point visibility)
var viewSpans = false;  // (span visibility)
var viewScaffolding = false; // (scaffolding visibility)
var viewSkins = false; // (skin visibility)
var gravity = 0.01;  // (rate of y-velocity increase per frame per point mass of 1)
var rigidity = 10;  // global span rigidity (as iterations of position accuracy refinement)
var friction = 0.999;  // (proportion of previous velocity after frame refresh)
var bounceLoss = 0.9;  // (proportion of previous velocity after bouncing)
var skidLoss = 0.9;  // (proportion of previous velocity if touching the ground)
var breeze = 0.4;  // breeziness level (applied as brief left & right gusts)




////---OBJECTS---////


///point constructor
function Point(current_x, current_y, materiality="material") {  // materiality can be "material" or "immaterial"
  this.cx = current_x;
  this.cy = current_y; 
  this.px = this.cx;  // previous x value
  this.py = this.cy;  // previous y value
  this.mass = 1;  // (as ratio of gravity)
  this.width = 0;
  this.materiality = materiality;
  this.fixed = false;
  this.grabbed = false;
  this.mxd = null;  // mouse x distance (upon grab)
  this.myd = null;  // mouse y distance (upon grab)
  this.id = pointCount;
  pointCount += 1;
}

///span constructor
function Span(point_1, point_2, visibility="visible") {  // visibility can be "visible" or "hidden"
  this.p1 = point_1;
  this.p2 = point_2;
  this.l = distance(this.p1,this.p2); // length
  this.strength = 1;  // (as ratio of rigidity)
  this.visibility = visibility;
  this.id = spanCount;
  spanCount += 1;
}

///skins constructor
function Skin(points_array,color) {
  this.points = points_array;  // an array of points for skin outline path
  this.color = color;
  this.id = skinCount;
  skinCount += 1;
}




////---FUNCTIONS---////


///scales canvas to window
function scaleToWindow() {
  if (window.innerWidth > window.innerHeight) {
    canvasContainerDiv.style.height = window.innerHeight*canvRatio+"px";
    canvasContainerDiv.style.width = canvasContainerDiv.style.height;
  } else {
    canvasContainerDiv.style.width = window.innerWidth*canvRatio+"px";
    canvasContainerDiv.style.height = canvasContainerDiv.style.width;
  }
  canvasPositionLeft = canvas.getBoundingClientRect().left + window.scrollX;
  canvasPositionTop = canvas.getBoundingClientRect().top + window.scrollY;
}

///converts percentage to canvas x value
function xValFromPct(percent) {
  return percent * canvas.width / 100;
}

///converts percentage to canvas y value
function yValFromPct(percent) {
  return percent * canvas.height / 100;
}

///converts canvas x value to percentage of canvas width
function pctFromXVal(xValue) {
  return xValue * 100 / canvas.width;
}

///converts canvas y value to percentage of canvas height
function pctFromYVal(yValue) {
  return yValue * 100 / canvas.height;
}

///gets a point by id number
function getPt(id) {
  for (var i=0; i<points.length; i++) { 
    if (points[i].id == id) { return points[i]; }
  }
}

///gets distance between two points (pythogorian theorum)
function distance(point1, point2) {
  var xDiff = point2.cx - point1.cx;
  var	yDiff = point2.cy - point1.cy;
  return Math.sqrt( xDiff*xDiff + yDiff*yDiff);
}

///gets a span's mid point (returns object: { x: <value>, y: <value> } )
function spanMidPoint( span ) {
  var mx = ( span.p1.cx + span.p2.cx ) / 2;  // mid x value
  var my = ( span.p1.cy + span.p2.cy ) / 2;  // mid y value
  return { x: mx, y: my};
}

///gets the mid point between two points (returns object: { x: <value>, y: <value> } )
function midPoint( point1, point2 ) {
  var mx = ( point1.cx + point2.cx ) / 2;  // mid x value
  var my = ( point1.cy + point2.cy ) / 2;  // mid y value
  return { x: mx, y: my};
}

///creates a point object instance
function addPt(xPercent,yPercent,materiality="material") {
  points.push( new Point( xValFromPct(xPercent), yValFromPct(yPercent), materiality ) );
  return points[points.length-1];
}

///creates a span object instance
function addSp(p1,p2,visibility="visible") {
  spans.push( new Span( getPt(p1), getPt(p2), visibility ) );
  return spans[spans.length-1];
}

///creates a skin object instance
function addSk(idPathArray, color) {
  var skinPointsArray = [];
  for ( var i=0; i<idPathArray.length; i++) {
    for( var j=0; j<points.length; j++){ 
      if ( points[j].id === idPathArray[i] ) { 
        skinPointsArray.push( points[j] ); 
      }
    }
  }
  skins.push( new Skin(skinPointsArray,color) );
  return skins[skins.length-1];
}

///removes a point by id  
function removePoint(id) {
  for( var i=0; i<points.length; i++){ 
    if ( points[i].id === id) { points.splice(i,1); }
  }
}

///removes a span by id
function removeSpan(id) {
  for( var i=0; i<spans.length; i++){ 
    if ( spans[i].id === id) { spans.splice(i,1); }
  }
}

///removes a span by id
function removeSkin(id) {
  for( var i=0; i<skins.length; i++){ 
    if ( skins[i].id === id) { skins.splice(i,1); }
  }
}

///updates point positions based on verlet velocity (i.e., current coord minus previous coord)
function updatePoints() {
  for(var i=0; i<points.length; i++) {
    var p = points[i];  // point object
    if (!p.fixed) {
      var	xv = (p.cx - p.px) * friction;	// x velocity
      var	yv = (p.cy - p.py) * friction;	// y velocity
      if (p.py >= canvas.height-p.width/2-0.1) { xv *= skidLoss; }
      p.px = p.cx;  // updates previous x as current x
      p.py = p.cy;  // updates previous y as current y
      p.cx += xv;  // updates current x with new velocity
      p.cy += yv;  // updates current y with new velocity
      p.cy += gravity * p.mass;  // add gravity to y
      if (worldTime % Tl.rib( 100, 200 ) === 0) { p.cx += Tl.rfb( -breeze, breeze ); }  // apply breeze to x
    }
  } 
}

///applies constrains
function applyConstraints( currentIteration ) {
  for (var i=0; i<points.length; i++) {
    var p = points[i];
    var pr = p.width/2;  // point radius
    var xv = p.cx - p.px;  // x velocity
    var yv = p.cy - p.py;  // y velocity
    //wall constraints (inverts velocity if point moves beyond a canvas edge)
    if (p.materiality === "material") {
      if (p.cx > canvas.width - pr) { 
        p.cx = canvas.width - pr;  // move point back to wall
        p.px = p.cx + xv * bounceLoss;  // reverse velocity
      }
      if (p.cx < 0 + pr) {
        p.cx = 0 + pr;
        p.px = p.cx + xv * bounceLoss;
      }
      if (p.cy > canvas.height - pr) {
        p.cy = canvas.height - pr;
        p.py = p.cy + yv * bounceLoss;
      }
      // if (p.cy < 0 + pr) {  // (ceiling constraints omitted so flowers can pass through without bouncing)
      //   p.cy = pr;
      //   p.py = p.cy + yv * bounceLoss;
      // }
    }
  }
}

///updates span positions and adjusts associated points
function updateSpans( currentIteration ) {
  for (var i=0; i<spans.length; i++) {
    var thisSpanIterations = Math.round( rigidity * spans[i].strength );
    if ( currentIteration+1 <= thisSpanIterations ) {
      var s = spans[i];
      var dx = s.p2.cx - s.p1.cx;  // distance between x values
      var	dy = s.p2.cy - s.p1.cy;  // distance between y values
      var d = Math.sqrt( dx*dx + dy*dy);  // distance between the points
      var	r = s.l / d;	// ratio (span length over distance between points)
      var	mx = s.p1.cx + dx / 2;  // midpoint between x values 
      var my = s.p1.cy + dy / 2;  // midpoint between y values
      var ox = dx / 2 * r;  // offset of each x value (compared to span length)
      var oy = dy / 2 * r;  // offset of each y value (compared to span length)
      if (!s.p1.fixed) {
        s.p1.cx = mx - ox;  // updates span's first point x value
        s.p1.cy = my - oy;  // updates span's first point y value
      }
      if (!s.p2.fixed) {
        s.p2.cx = mx + ox;  // updates span's second point x value
        s.p2.cy = my + oy;  // updates span's second point y value
      }
    }
  }
}

///refines points for position accuracy & shape rigidity by updating spans and applying constraints iteratively
function refinePositions() {
  var requiredIterations = rigidity;
  for (var i=0; i<spans.length; i++) {
    var thisSpanIterations = Math.round( rigidity * spans[i].strength );
    if ( thisSpanIterations > requiredIterations ) {
      requiredIterations = thisSpanIterations;
    }
  }
  for (var j=0; j<requiredIterations; j++) {
    updateSpans(j);
    applyConstraints(i);
  }
}

///displays points
function renderPoints() {
  for (var i=0; i<points.length; i++) {
    var p = points[i];
    var radius = p.width >= 2 ? p.width/2 : 1;
    ctx.beginPath();
    ctx.fillStyle = "red";
    ctx.arc( p.cx, p.cy, radius, 0 , Math.PI*2 );
    ctx.fill(); 
  }
}

///displays spans
function renderSpans() {
  for (var i=0; i<spans.length; i++) {
    var s = spans[i];
    if (s.visibility == "visible") {
      ctx.beginPath();
      ctx.lineWidth = 1;
      ctx.strokeStyle = "blue";
      ctx.moveTo(s.p1.cx, s.p1.cy);
      ctx.lineTo(s.p2.cx, s.p2.cy);
      ctx.stroke(); 
    }
  }
}

///displays scaffolding & binding spans (i.e., "hidden" spans)
function renderScaffolding() {
  ctx.beginPath();
  for (var i=0; i<spans.length; i++) {
    var s = spans[i];
    if (s.visibility === "hidden") {
      ctx.strokeStyle="pink";
      ctx.moveTo(s.p1.cx, s.p1.cy);
      ctx.lineTo(s.p2.cx, s.p2.cy);
    }
  }
  ctx.stroke();
}

///displays skins 
function renderSkins() {
  for(var i=0; i<skins.length; i++) {
    var s = skins[i];
    ctx.beginPath();
    ctx.strokeStyle = s.color;
    ctx.lineWidth = 0;
    ctx.lineJoin = "round";
    ctx.lineCap = "round";
    ctx.fillStyle = s.color;
    ctx.moveTo(s.points[0].cx, s.points[0].cy);
    for(var j=1; j<s.points.length; j++) { ctx.lineTo(s.points[j].cx, s.points[j].cy); }
    ctx.lineTo(s.points[0].cx, s.points[0].cy);
    ctx.stroke();
    ctx.fill();  
  }
}

///clears canvas frame
function clearCanvas() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
}

///renders all visible components
function renderImages() {
  //if ( viewSkins ) { renderSkins(); }  // disabled here so plants can be rendered sequentially in plants.js
  if ( viewSpans ) { renderSpans(); }
  if ( viewPoints ) { renderPoints(); }
  if ( viewScaffolding ) { renderScaffolding(); }
}




////---EVENTS---////


///scaling
window.addEventListener('resize', scaleToWindow);




////---RUN---////


function runVerlet() {
	scaleToWindow();
  updatePoints();
  refinePositions();
  //clearCanvas();  // canvas clearing handled by renderBackground() in season_handler.js
  renderImages();
  worldTime++;
}









// ******* jQuery Easing v1.3 - Copyright © 2008 George McGinley Smith ********* //

jQuery.easing['jswing'] = jQuery.easing['swing'];

jQuery.extend( jQuery.easing,
{
	def: 'easeOutQuad',
	swing: function (x, t, b, c, d) {
		//alert(jQuery.easing.default);
		return jQuery.easing[jQuery.easing.def](x, t, b, c, d);
	},
	easeInQuad: function (x, t, b, c, d) {
		return c*(t/=d)*t + b;
	},
	easeOutQuad: function (x, t, b, c, d) {
		return -c *(t/=d)*(t-2) + b;
	},
	easeInOutQuad: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t + b;
		return -c/2 * ((--t)*(t-2) - 1) + b;
	},
	easeInCubic: function (x, t, b, c, d) {
		return c*(t/=d)*t*t + b;
	},
	easeOutCubic: function (x, t, b, c, d) {
		return c*((t=t/d-1)*t*t + 1) + b;
	},
	easeInOutCubic: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t + b;
		return c/2*((t-=2)*t*t + 2) + b;
	},
	easeInQuart: function (x, t, b, c, d) {
		return c*(t/=d)*t*t*t + b;
	},
	easeOutQuart: function (x, t, b, c, d) {
		return -c * ((t=t/d-1)*t*t*t - 1) + b;
	},
	easeInOutQuart: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
		return -c/2 * ((t-=2)*t*t*t - 2) + b;
	},
	easeInQuint: function (x, t, b, c, d) {
		return c*(t/=d)*t*t*t*t + b;
	},
	easeOutQuint: function (x, t, b, c, d) {
		return c*((t=t/d-1)*t*t*t*t + 1) + b;
	},
	easeInOutQuint: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
		return c/2*((t-=2)*t*t*t*t + 2) + b;
	},
	easeInSine: function (x, t, b, c, d) {
		return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
	},
	easeOutSine: function (x, t, b, c, d) {
		return c * Math.sin(t/d * (Math.PI/2)) + b;
	},
	easeInOutSine: function (x, t, b, c, d) {
		return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
	},
	easeInExpo: function (x, t, b, c, d) {
		return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
	},
	easeOutExpo: function (x, t, b, c, d) {
		return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
	},
	easeInOutExpo: function (x, t, b, c, d) {
		if (t==0) return b;
		if (t==d) return b+c;
		if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
		return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
	},
	easeInCirc: function (x, t, b, c, d) {
		return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
	},
	easeOutCirc: function (x, t, b, c, d) {
		return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
	},
	easeInOutCirc: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
		return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
	},
	easeInElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
	},
	easeOutElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
	},
	easeInOutElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
		return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
	},
	easeInBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158;
		return c*(t/=d)*t*((s+1)*t - s) + b;
	},
	easeOutBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158;
		return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
	},
	easeInOutBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158; 
		if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
		return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
	},
	easeInBounce: function (x, t, b, c, d) {
		return c - jQuery.easing.easeOutBounce (x, d-t, 0, c, d) + b;
	},
	easeOutBounce: function (x, t, b, c, d) {
		if ((t/=d) < (1/2.75)) {
			return c*(7.5625*t*t) + b;
		} else if (t < (2/2.75)) {
			return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
		} else if (t < (2.5/2.75)) {
			return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
		} else {
			return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
		}
	},
	easeInOutBounce: function (x, t, b, c, d) {
		if (t < d/2) return jQuery.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b;
		return jQuery.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b;
	}
});













//////////////////////////  UI  //////////////////////////////




var headerDiv = document.getElementById("header_div");
var footerDiv = document.getElementById("footer_div");
//var landingSvgRatio = $('#landing_svg_bg')[0].naturalHeight / $('#landing_svg_bg')[0].naturalWidth;

var mouseCanvasXPct;
var mouseCanvasYPct;

var sunShadeY = yValFromPct(8);  // sun shade elevation as canvas Y value
var sunShadeHandleRadiusPct = 1.75;  // sun shade handle radius as percentage of canvas width
var grabbedHandle = null;  // grabbed handle object (assigned when handle is clicked/touched)

var selectRadius = canvas.width*0.015;  // radius from cursor where point is detected (updated every summer)
var plantsAreBeingEliminated = false;

var gameDifficulty = "beginner";
var ambientMode = false;
var infoModalOpen = false;
var infoModalOpenWhilePaused = false;
var restartModalOpen = false;
var endOfGameAnnouncementDisplayed = false;




/////---OBJECTS---/////


///sun shade handle constructor
function SunShadeHandle( xPositionPct ) {
  this.x = xValFromPct(xPositionPct); 
  this.y = sunShadeY;
}

///sun shade constructor
function SunShade( handle1, handle2 ) {
  this.h1 = handle1;
  this.h2 = handle2; 
}



/////---FUNCTIONS---/////


///scales landing to window
function scaleLanding() {
  $("#landing_content_div").css({ height: "90%", width: "90%" });
  $("#landing_svg_bg").css({ height: "100%", width: "100%" });  
  if ( $("#landing_content_div").height() > $("#landing_content_div").width() ) {
    $("#landing_svg_bg").height( $("#landing_content_div").width() );
    $("#landing_content_div").height( $("#landing_svg_bg").height() );
  } else {
    $("#landing_svg_bg").width( $("#landing_content_div").height() );
    $("#landing_content_div").width( $("#landing_svg_bg").width() );
  }
}

///attaches header and footer to canvas (after canvas has been resized to window dimensions in verlet.js)
function attachHeaderAndFooter() {
  var canvasHeight = parseFloat(canvasContainerDiv.style.height);
  var canvasTop = canvasContainerDiv.offsetTop - canvasHeight/2;
  headerDiv.style.width = canvasContainerDiv.style.width;
  headerDiv.style.height = canvasHeight*0.15+"px";
  headerDiv.style.top = canvasTop-parseFloat(headerDiv.style.height)+"px";
  footerDiv.style.width = canvasContainerDiv.style.width;
  footerDiv.style.height = canvasHeight*0.075+"px";
  footerDiv.style.top = canvasTop+canvasHeight+"px";
  scaleFooterContent();
}

///scales text content down for smaller window sizes
function scaleFooterContent() {
  if ( $("#canvas_container_div").width() < 230 ) {
    $(".footer_text").css({ fontSize: "4pt", top: "1px" });
    $("#pie_svg_left").css({ top: "7.4px" });
    $("#tag_content").css({ fontSize: "5pt", top: "12px" });
    $("#pie_svg_right").css({ top: "6.35px" });
    $("#hundred_pct_large_height_announcement").css({ fontSize: "15pt"});
    $(".game_win_text").css({ fontSize: "8pt"});
    $("#game_win_gen_number").css({ fontSize: "12pt"});
    $("#game_win_mode").css({ fontSize: "10pt"});
  } else if ( $("#canvas_container_div").width() < 300 ) {
    $(".footer_text").css({ fontSize: "7pt", top: "3px" });
    $("#pie_svg_left").css({ top: "7.4px" });
    $("#tag_content").css({ fontSize: "7pt", top: "15.5px" });
    $("#pie_svg_right").css({ top: "8.35px" });
    $("#hundred_pct_large_height_announcement").css({ fontSize: "20pt"});
    $(".game_win_text").css({ fontSize: "10pt"});
    $("#game_win_gen_number").css({ fontSize: "14pt"});
    $("#game_win_mode").css({ fontSize: "12pt"});
  } else if ( $("#canvas_container_div").width() < 400 ){ 
    $(".footer_text").css({ fontSize: "9pt", top: "7px" });
    $("#pie_svg_left").css({ top: "11.4px" });
    $("#tag_content").css({ fontSize: "8pt", top: "19.5px" });
    $("#pie_svg_right").css({ top: "12.35px" });
    $("#hundred_pct_large_height_announcement").css({ fontSize: "50pt"});
    $(".game_win_text").css({ fontSize: "12pt"});
    $("#game_win_gen_number").css({ fontSize: "16pt"});
    $("#game_win_mode").css({ fontSize: "15pt"});
  } else {  // > 400
    $(".footer_text").css({ fontSize: "9pt", top: "12px" });
    $("#pie_svg_left").css({ top: "15x" });
    $("#tag_content").css({ fontSize: "10pt", top: "24.5px" });
    $("#pie_svg_right").css({ top: "17px" });
    $("#hundred_pct_large_height_announcement").css({ fontSize: "80pt"});
    $(".game_win_text").css({ fontSize: "17pt"});
    $("#game_win_gen_number").css({ fontSize: "23pt"});
    $("#game_win_mode").css({ fontSize: "19pt"});
  }
}

///creates a new sun shade handle
function createSunShadeHandle( xPositionPct ) {
  sunShadeHandles.push( new SunShadeHandle( xPositionPct ) );
  sunShadeHandleCount++;
  return sunShadeHandles[sunShadeHandles.length-1];
}

///creates a new sun shade
function createSunShade( handle1XPct, handle2XPct ) {
  var handle1 = createSunShadeHandle( handle1XPct );
  var handle2 = createSunShadeHandle( handle2XPct );
  sunShades.push ( new SunShade( handle1, handle2 ) );
  sunShadeCount++;
  return sunShades[sunShades.length-1];
}

///places sun shade
function placeSunShades( leftCount, rightCount ) {
  for ( var i=0; i<leftCount; i++ ) { createSunShade( 0, 0 ); }
  for ( var j=0; j<rightCount; j++ ) { createSunShade( 100, 100 ); }
}

///scales elimination cursor based on average plant width
function scaleEliminationCursor() {
  var baseWidthsSum = 0;
  var activePlantCount = 0;
  for ( var i=0; i<plants.length; i++ ) { 
    if ( plants[i].sourceSeed.hasGerminated && plants[i].isAlive ) { 
      baseWidthsSum += plants[i].spB.l; 
      activePlantCount++;
    } 
  }
  baseWidthAvg = baseWidthsSum/activePlantCount;
  selectRadius = baseWidthAvg*1.5 > canvas.width*0.015 ? baseWidthAvg*1.5 : canvas.width*0.015 ;
}



//// Event Functions ////

///updates mouse position coordinates as percentages
function updateMouse(e) {
  var canvasWidthOnScreen = parseFloat(canvasContainerDiv.style.width);
  var canvasHeightOnScreen = parseFloat(canvasContainerDiv.style.height);
  mouseCanvasXPct = (e.pageX-canvasPositionLeft)*100/canvasWidthOnScreen;  // mouse canvas x percent
  mouseCanvasYPct = (e.pageY-canvasPositionTop)*100/canvasHeightOnScreen;  // mouse canvas y percent
}

///grabs handle
function grabHandle(e) {
  for ( var i=0; i<sunShadeHandles.length; i++ ) {
    var h = sunShadeHandles[i];
    var xDiffPct = pctFromXVal( h.x ) - mouseCanvasXPct;
    var yDiffPct = pctFromYVal( h.y ) - mouseCanvasYPct;
    var distancePct = Math.sqrt( xDiffPct*xDiffPct + yDiffPct*yDiffPct );
    if ( distancePct <= sunShadeHandleRadiusPct ) {
      grabbedHandle = h;
    }
  }
}

///moves handle
function moveHandle(e) {
  if ( grabbedHandle ) {
    if ( mouseCanvasXPct < 0 ) {
      grabbedHandle.x = 0;
    } else if ( mouseCanvasXPct > 100 ) {
      grabbedHandle.x = xValFromPct( 100 );
    } else {
      grabbedHandle.x = xValFromPct( mouseCanvasXPct );
    }
  }
}

///drops handle
function dropHandle() {
  grabbedHandle = null;
}

///activates plant elimination mode
function startEliminatingPlants() {
  if ( !ambientMode ) plantsAreBeingEliminated = true;
}

///deactivates plant elimination mode
function stopEliminatingPlants() {
  if ( !ambientMode ) plantsAreBeingEliminated = false;
}

///eliminates plants (kills them and knocks them over)
function eliminatePlants( e, plant ) {
  if ( !ambientMode ) {
    for ( var i=0; i<plants.length; i++ ) {
      var p = plants[i];
      if ( plantsAreBeingEliminated && ( p.isAlive || (!p.hasCollapsed && !p.hasBeenEliminatedByPlayer) ) ) {
        for ( var j=0; j<p.segments.length; j++) {
          var s = p.segments[j];
          var xDiffPct1 = pctFromXVal( s.ptE1.cx ) - mouseCanvasXPct;
          var yDiffPct1 = pctFromYVal( s.ptE1.cy ) - mouseCanvasYPct;
          var distancePct1 = Math.sqrt( xDiffPct1*xDiffPct1 + yDiffPct1*yDiffPct1 );  // distance from seg ext pt 1
          var xDiffPct2 = pctFromXVal( s.ptE2.cx ) - mouseCanvasXPct;
          var yDiffPct2 = pctFromYVal( s.ptE2.cy ) - mouseCanvasYPct;
          var distancePct2 = Math.sqrt( xDiffPct2*xDiffPct2 + yDiffPct2*yDiffPct2 );  // distance from seg ext pt 2
          var selectRadiusPct = selectRadius*100/canvas.width;
          if ( distancePct1 <= selectRadiusPct || distancePct2 <= selectRadiusPct ) {
            s.ptE1.px += distancePct1 > distancePct2 ? 10 : -10;  // impact burst effect
            p.energy = p.energy > energyStoreFactor*-1 ? energyStoreFactor*-1 : p.energy;  // drops plant energy
            killPlant(p);
            for (var k=0; k<p.segments.length; k++) {
              var s2 = p.segments[k];
              s2.ptE1.mass = s2.ptE2.mass = 15;  // increases plant mass for faster collapse
              if (!s2.isBaseSegment) {
                removeSpan(s2.spCdP.id);  // downward (l to r) cross span to parent
                removeSpan(s2.spCuP.id);  // upward (l to r) cross span to parent
              }
              removeSpan(s2.spCd.id);  // downward (l to r) cross span
              removeSpan(s2.spCu.id);  // upward (l to r) cross span
            }
            p.hasBeenEliminatedByPlayer = true;
          }
        }
      }
    }
  }
}

///pause game
function pause() {
  document.getElementById("icon_pause").style.visibility = "hidden";
  document.getElementById("icon_play").style.visibility = "visible";
  gamePaused = true;
}

///resume game
function resume() {
  if ( !endOfGameAnnouncementDisplayed ) {
    document.getElementById("icon_pause").style.visibility = "visible";
    document.getElementById("icon_play").style.visibility = "hidden";
    document.getElementById("modal_play").style.visibility = "hidden";
    gamePaused = false;
    display();
  }
}

///remove modals
function removeModals() {
  $(".modal_card_options_screens").css("visibility", "hidden");
  $(".modal_options_text").css("visibility", "hidden");
  $("#modal_card_gameplay_screen").css("visibility", "hidden");
  $("#modal_card_gameplay_screen_inner_div").css("visibility", "hidden");
  $(".modal_gameplay_screen_text").css("visibility", "hidden");
  $(".icon_exit_modal").css("visibility", "hidden");
  infoModalOpen = false;
  if ( !infoModalOpenWhilePaused ) { resume(); }
  infoModalOpenWhilePaused = false;
}

function omitRedFlowerFooterContent() {
  $("#height_text, #tag_div, #tag_svg, #tag_content, #percent").css("visibility", "hidden");
}



//// Rendering Functions ////


///renders eliminate plant icon at cursor location
function displayEliminatePlantIconWithCursor(e) {
  if ( !ambientMode ) { 
    var displayIcon = false;
    for ( var i=0; i<plants.length; i++ ) {
      var p = plants[i];
      if ( p.isAlive || (!p.hasCollapsed && !p.hasBeenEliminatedByPlayer) ) {
        for ( var j=0; j<p.segments.length; j++) {
          var s = p.segments[j];
          var xDiffPct1 = pctFromXVal( s.ptE1.cx ) - mouseCanvasXPct;
          var yDiffPct1 = pctFromYVal( s.ptE1.cy ) - mouseCanvasYPct;
          var distancePct1 = Math.sqrt( xDiffPct1*xDiffPct1 + yDiffPct1*yDiffPct1 );
          var xDiffPct2 = pctFromXVal( s.ptE2.cx ) - mouseCanvasXPct;
          var yDiffPct2 = pctFromYVal( s.ptE2.cy ) - mouseCanvasYPct;
          var distancePct2 = Math.sqrt( xDiffPct2*xDiffPct2 + yDiffPct2*yDiffPct2 );
          var selectRadiusPct = selectRadius*100/canvas.width;
          if ( distancePct1 <= selectRadiusPct*2 || distancePct2 <= selectRadiusPct*2 ) {
            displayIcon = true;
          }
        }
      }
    }
    if ( displayIcon ) {
      var xo = selectRadius*0.06;  // x offset
      var yo = selectRadius*0.1;  // y offset
      ctx.fillStyle = "rgba(232,73,0,0.5)";
      ctx.strokeStyle = "rgba(232,73,0,1)";
      //circle
      ctx.beginPath();
      ctx.lineWidth = 1;
      ctx.arc( xValFromPct(mouseCanvasXPct), yValFromPct(mouseCanvasYPct-yo), selectRadius, 0, 2*Math.PI );
      ctx.fill();
      ctx.stroke();
      //bar
      ctx.beginPath();
      ctx.lineWidth = selectRadius*0.3;
      ctx.lineCap = "butt";
      ctx.moveTo( xValFromPct(mouseCanvasXPct-xo), yValFromPct(mouseCanvasYPct-yo) );
      ctx.lineTo( xValFromPct(mouseCanvasXPct+xo), yValFromPct(mouseCanvasYPct-yo) );
      ctx.fill();
      ctx.stroke();
    }
  }
}

///renders sun shades
function renderSunShades() {
  var y = sunShadeY;  // sun shade y value
  var r = xValFromPct( sunShadeHandleRadiusPct );  // handle radius
  var c = "#111111";  // color
  for ( var i=0; i<sunShades.length; i++ ) {
    var s = sunShades[i];
    //shadow
    if ( viewShadows ) {
      ctx.fillStyle = "rgba( 0, 0, 0, 0.333 )";
      ctx.beginPath();
      ctx.moveTo( s.h1.x, y );
      ctx.lineTo( s.h2.x, y ); 
      ctx.lineTo( s.h2.x, yValFromPct(100) );
      ctx.lineTo( s.h1.x, yValFromPct(100) );
      ctx.fill();  
    }
    //line
    ctx.beginPath();
    ctx.lineWidth = xValFromPct( sunShadeHandleRadiusPct*0.75 );
    ctx.strokeStyle = c;
    ctx.moveTo(s.h1.x,y);
    ctx.lineTo(s.h2.x,y);
    ctx.stroke();
    //handles
    for ( var j=1; j<=2; j++) {
      var hx = s["h"+j].x;
      if ( hx === 0 || hx === canvas.width ) {
        //tab (outer circle)
        ctx.beginPath();
        ctx.fillStyle = c;
        ctx.arc( hx, y, r*1.1, 0, 2*Math.PI );
        ctx.fill();
        ctx.beginPath();
        //arrow (diamond)
        ctx.fillStyle = "rgba( 213, 215, 197, 0.5 )";
        ctx.moveTo( hx, y-r*0.5 );
        ctx.lineTo( hx+r*0.7, y );
        ctx.lineTo( hx, y+r*0.5 );
        ctx.lineTo( hx-r*0.7, y );
        ctx.fill();
      } else {
        //outer circle
        ctx.beginPath();
        ctx.fillStyle = c;
        ctx.arc( hx, y, r, 0, 2*Math.PI );
        ctx.fill();
        ctx.beginPath();
        //inner circle
        ctx.fillStyle = "rgba( 213, 215, 197, 0.15 )";
        ctx.arc( hx, y, r*0.666, 0, 2*Math.PI );
        ctx.fill();
      }
    }
  }
}




/////--- EVENT LISTENERS ---/////


///keeps UI elements scaled when window is resized (even when game is paused)
$(window).resize(function() {
  scaleLanding();
  updateUI();
});

///choose game mode on landing
$("#button_game_mode").click(function(){
  $("#landing_page_div").hide();
  $("#overlay_game_mode_options_div").css("visibility", "visible");
});

///choose ambient mode on landing
$("#button_ambient_mode").click(function(){
  $("#landing_page_div").hide();
  $("#overlay_ambient_mode_options_div").css("visibility", "visible");
  ambientMode = true;
  viewRedFlowerIndicator = false;
  omitRedFlowerFooterContent();
  $("#season_left, #pie_svg_left").css("display", "none");
  $("#ambient_footer_right").css("display", "block");
});

///select beginner on game options overlay
$("#option_beginner").click(function(){
  $("#option_beginner").css("opacity", "1");
  $("#option_intermediate").css("opacity", "0");
  $("#option_expert").css("opacity", "0");
  gameDifficulty = "beginner";
});

///select intermediate on game options overlay
$("#option_intermediate").click(function(){
  $("#option_beginner").css("opacity", "0");
  $("#option_intermediate").css("opacity", "1");
  $("#option_expert").css("opacity", "0");
  gameDifficulty = "intermediate";
});

///select expert on game options overlay
$("#option_expert").click(function(){
  $("#option_beginner").css("opacity", "0");
  $("#option_intermediate").css("opacity", "0");
  $("#option_expert").css("opacity", "1");
  gameDifficulty = "expert";
});

///select first option on ambient options overlay
$("#option_first").click(function(){
  $("#option_first").css("opacity", "1");
  $("#option_second").css("opacity", "0");
  $("#option_third").css("opacity", "0");
  gameDifficulty = "beginner";
});

///select second option on ambient options overlay
$("#option_second").click(function(){
  $("#option_first").css("opacity", "0");
  $("#option_second").css("opacity", "1");
  $("#option_third").css("opacity", "0");
  gameDifficulty = "intermediate";
});

///select third option on ambient options overlay
$("#option_third").click(function(){
  $("#option_first").css("opacity", "0");
  $("#option_second").css("opacity", "0");
  $("#option_third").css("opacity", "1");
  gameDifficulty = "expert";
});

///get game info on game options screen
$("#helper_game_info").click(function(){
  if ( $("#modal_card_game_screen").css("visibility") === "hidden" ) {
    $("#modal_card_game_screen").css("visibility", "visible");
    $("#modal_game_text").css("visibility", "visible");
    $("#icon_exit_modal_game_info").css("visibility", "visible");
  } else {
    removeModals();
  }
});

///get ambient mode info on ambient options screen
$("#helper_ambient_info").click(function(){
  if ( $("#modal_card_ambient_screen").css("visibility") === "hidden" ) {
    $("#modal_card_ambient_screen").css("visibility", "visible");
    $("#modal_ambient_text").css("visibility", "visible");
    $("#icon_exit_modal_ambient_info").css("visibility", "visible");
  } else {
    removeModals();
  }
});

///exit modal
$(".icon_exit_modal").click(function(){
  removeModals();
});

///on-screen play button (displayed when paused)
$("#modal_play").click(function(){
  resume();
});

///activates game when "sow" button is clicked
$(".button_sow").click(function(){
  switch( gameDifficulty ) {
    case "beginner":
      for ( var i=0; i<15; i++ ) { createSeed( null, generateRandomRedFlowerPlantGenotype() ); } 
      break;
    case "intermediate":
      for ( var j=0; j<20; j++ ) { createSeed( null, generateRandomNewPlantGenotype() ); }
      for ( var k=0; k<5; k++ ) { createSeed( null, generateRandomRedFlowerPlantGenotype() ); }
      break;
    case "expert":
      for ( var l=0; l<1; l++ ) { createSeed( null, generateTinyWhiteFlowerPlantGenotype() ); }
  }
  for ( var m=0; m<seeds.length; m++ ) { scatterSeed( seeds[m] ); }
  $("#overlay_game_mode_options_div, #overlay_ambient_mode_options_div").fadeOut(500, function(){ 
    $(".icon").fadeIn(5000);
    $("#footer_div").fadeIn(5000);
  });
  gameHasBegun = true;
});

///info icon (toggles info modal)
$("#icon_info").click(function() {
  if ( !infoModalOpen ) {
    $("#modal_card_gameplay_screen").css("visibility", "visible");
    $("#modal_card_gameplay_screen_inner_div").scrollTop(0).css("visibility", "visible");
    $(".modal_gameplay_screen_text").css("visibility", "visible");
    $("#icon_exit_modal_gameplay_info").css("visibility", "visible");
    infoModalOpen = true;
    if ( gamePaused ) { infoModalOpenWhilePaused = true; }
    pause();
  } else {
    removeModals();
  }
});

///dark mode toggle icon (toggles dark/light modes)
$("#icon_dark_toggle").click(function() {
  if ( darkMode ) {
    $("body").css("background","#FFFFFF");
    $(".title_header_svg").attr("src","https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/title_header_light.svg");
    $("#icon_dark_toggle").css("transform","rotate(180deg)");
    $(".footer_text").css("color","#4A4A4A");
    $(".pie_circle").css("stroke","#4A4A4A");
    $(".pie_svg").css("background-color","rgba(213,216,197,1");
    darkMode = false;
  } else {
    $("body").css("background","#202020");
    $(".title_header_svg").attr("src","https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/title_header_dark.svg");
    $("#icon_dark_toggle").css("transform","rotate(0deg)");
    $(".footer_text, .pie_circle").css("color","#D5D8C5");
    $(".pie_circle").css("stroke","#D5D8C5");
    $(".pie_svg").css("background-color","rgba(213,216,197,.25");
    darkMode = true;
  }
});

///shadows icon (toggles shadows)
$(".icon_shadows").click(function(){
  if ( viewShadows === true ) {
    $("#icon_shadows_on").css("visibility", "hidden");
    $("#icon_shadows_off").css("visibility", "visible");
    viewShadows = false;
  } else {
    $("#icon_shadows_on").css("visibility", "visible");
    $("#icon_shadows_off").css("visibility", "hidden");
    viewShadows = true;
  }
  if ( gamePaused ) { clearCanvas(); renderBackground(); renderPlants(); }
});

///camera icon (takes a screenshot)
$("#save").click(function(){
  downloadScreenshot();
});

///pause/resume icons
$(".icon_game_run").click(function(){
  if ( !gamePaused ) { 
    pause(); 
    $("#modal_play").css("visibility", "visible");
  } else { 
    removeModals();  // removes any modals if visible
    resume(); 
  }
});

///restart icon (restarts and returns to landing screen)
$("#icon_restart").click(function() {
  if ( !gameHasBegun ) {
    //location.reload();
    window.location.href = window.location.href;
  } else {
    if ( !restartModalOpen ) {
      $("#modal_restart_div").css("visibility", "visible");
      restartModalOpen = true;
    } else {
      $("#modal_restart_div").css("visibility", "hidden");
      restartModalOpen = false;
    }
  }
});

///restart confirmation button
$("#button_restart_confirm_hover").click(function() {
  //location.reload();
  window.location.href = window.location.href;
});

///restart cancel button
$("#button_restart_cancel_hover").click(function() {
  $("#modal_restart_div").css("visibility", "hidden");
  restartModalOpen = false;
});

///eliminate plants on a single click
document.addEventListener("click", function(e) { 
  startEliminatingPlants(); 
  eliminatePlants(e); 
  stopEliminatingPlants();
});

///game over try again button & game win play again buttons
$("#button_try_again_hover, .button_game_win_play_again").click(function(){
  //location.reload();
  window.location.href = window.location.href;
});

///copyright
$("#this_year").text( new Date().getFullYear().toString().replace(/0/g,"O") );

///grabs/moves sun shade handle and eliminates plants on cursor drag
document.addEventListener("mousedown", function(e) { grabHandle(e); startEliminatingPlants(); });
document.addEventListener("mousemove", function(e) {  updateMouse(e); moveHandle(e); eliminatePlants(e); });
document.addEventListener("mouseup", function() {  dropHandle(); stopEliminatingPlants(); });
document.addEventListener("touchstart", function(e) { grabHandle(e); startEliminatingPlants(); });
document.addEventListener("touchmove", function(e) {  moveHandle(e); eliminatePlants(e); });
document.addEventListener("touchup", function() {  dropHandle(); stopEliminatingPlants(); });




/////---UPDATE---/////

scaleLanding();

///updates UI (runs every iteration)
function updateUI() {
  attachHeaderAndFooter();
  if ( useSunShades ) { renderSunShades(); }
  $("#year_count").text( currentYear.toString().replace(/0/g,"O") );  // replace gets rid of Nunito font dotted zero
  $("#season_left").text( ", " + currentSeason );
  $("#season_right").text( currentSeason );
  updateSeasonPieChart();
}












///////// LIGHT HANDLER /////////




/////---OBJECTS---/////


///sun ray constructor
function SunRay() {
  this.id = sunRayCount;
  this.x = xValFromPct( this.id );
  this.intensity = sunRayIntensity;
  this.leafContacts = [];  // (as array of objects: { y: <leaf contact y value>, plant: <plant> })
}

///shadow constructor
function Shadow( segment ) {
  this.s = segment;
  this.p1t = segment.spLf1.p2;
  this.p2t = segment.spLf1.p1;
  this.p3t = segment.spLf2.p1;
  this.p4t = segment.spLf2.p2;
  this.p1b = { cx: this.p1t.cx, cy: yValFromPct( 100 ) };
  this.p2b = { cx: this.p2t.cx, cy: yValFromPct( 100 ) };
  this.p3b = { cx: this.p3t.cx, cy: yValFromPct( 100 ) };
  this.p4b = { cx: this.p4t.cx, cy: yValFromPct( 100 ) };
}




/////---FUNCTIONS---/////


///creates a new sun ray (one for each x value as an integer percentage of the canvas's width)
function createSunRays() {
  for ( var i=0; i<101; i++ ) {
    sunRays.push( new SunRay() );
    sunRayCount++;
  }
}

///sheds sunlight
function shedSunlight() {
  markRayLeafIntersections();
  photosynthesize(); 
}

///marks points where sun rays intersect with leaves 
function markRayLeafIntersections() {
  for ( var i=0; i<plants.length; i++ ) {
    var p = plants[i];
    if ( p.isAlive ) {
      for ( var j=0; j<p.segments.length; j++ ) {
        var s = p.segments[j];
        if ( s.hasLeaves ) {
          var p1, p2, lcy;
          //leaf 1
          //(assigns p1 as leftmost leaf span point and p2 as rightmost leaf span point)
          if ( s.ptLf1.cx < s.ptB1.cx ) { p1 = s.ptLf1; p2 = s.ptB1; } else { p1 = s.ptB1; p2 = s.ptLf1; }
          var xPctMin = Math.ceil( pctFromXVal( p1.cx ) );
          var xPctMax = Math.floor( pctFromXVal( p2.cx ) );
          for ( var lcx=xPctMin; lcx<=xPctMax; lcx++ ) {  // leaf contact x value
            lcy = p1.cy + (xValFromPct(lcx)-p1.cx) * (p2.cy-p1.cy) / (p2.cx-p1.cx);  // leaf contact y value
            //pushes corresponding y value and plant instance to associated sun ray instance
            sunRays[lcx].leafContacts.push( { y: lcy, plant: p } );
          }
          //leaf 2
          //(assigns p2 as leftmost leaf span point and p1 as rightmost leaf span point)
          if ( s.ptLf2.cx < s.ptB2.cx ) { p1 = s.ptLf2; p2 = s.ptB2; } else { p1 = s.ptB2; p2 = s.ptLf2; }
          xPctMin = Math.ceil( pctFromXVal( p1.cx ) );
          xPctMax = Math.floor( pctFromXVal( p2.cx ) );  
          for ( lcx=xPctMin; lcx<=xPctMax; lcx++ ) {  // leaf contact x value
            lcy = p1.cy + (xValFromPct(lcx)-p1.cx) * ( p2.cy - p1.cy) / ( p2.cx - p1.cx ); // leaf contact y value
            //pushes corresponding y value and plant instance to associated sun ray instance
            sunRays[lcx].leafContacts.push( { y: lcy, plant: p } );
          }
        }
      } 
    }
  }
}

///transfers energy from sun rays to leaves
function photosynthesize() {
  for ( var i=0; i<sunRays.length; i++ ) {
    var sr = sunRays[i];  // sun ray
    //first, reduces a sun ray's intensity if it intersects a sun shade (see ui.js for sun shades)
    for ( var j=0; j<sunShades.length; j++ ) {
      var ss = sunShades[j];
      var lhx = ss.h1.x <= ss.h2.x ? ss.h1.x : ss.h2.x;  // leftmost handle x value
      var rhx = ss.h1.x <= ss.h2.x ? ss.h2.x : ss.h1.x;  // rightmost handle x value
      if ( sr.x >= lhx && sr.x <= rhx ) { sr.intensity *= 0.0; }
    }
    //sorts leaf contact points from highest to lowest elevation (increasing y value)
    sr.leafContacts.sort( function( a, b ) { return a.y - b.y; } );
    //when a sun ray hits a leaf, transfers half of the ray's intensity to the plant as energy
    for ( var k=0; k<sr.leafContacts.length; k++) {
      var lc = sr.leafContacts[k];  // leaf contact ({ y: <leaf contact y value>, plant: <plant> })
      sr.intensity *= 0.5;
      lc.plant.energy += sr.intensity * photosynthesisRatio;
    }
    sr.leafContacts = []; sr.intensity = sunRayIntensity;  // resets sun ray's leaf contact points & intensity
  }
}

///marks shadow positions (based on position of leaf spans)
function addLeafShadows( segment ) {
  shadows.push( new Shadow( segment ) );
}

///renders shadows (from highest to lowest elevation)
function renderLeafShadows() {
  for ( var i=0; i<shadows.length; i++ ) {
    var sh = shadows[i];
    ctx.fillStyle = "rgba("+sh.s.clLS.r+","+sh.s.clLS.g+","+sh.s.clLS.b+","+sh.s.clLS.a+")";
    ctx.beginPath();  // left leaf
    ctx.moveTo( sh.p1b.cx, sh.p1b.cy );
    ctx.lineTo( sh.p1t.cx, sh.p1t.cy );
    ctx.lineTo( sh.p2t.cx, sh.p2t.cy );
    ctx.lineTo( sh.p2b.cx, sh.p2b.cy );
    ctx.fill();
    ctx.beginPath();  // between leaves
    ctx.fillStyle = "rgba("+sh.s.clLS.r+","+sh.s.clLS.g+","+sh.s.clLS.b+","+sh.s.clLS.a+")";
    ctx.moveTo( sh.p2b.cx, sh.p2b.cy );
    ctx.lineTo( sh.p2t.cx, sh.p2t.cy );
    ctx.lineTo( sh.p3t.cx, sh.p3t.cy );
    ctx.lineTo( sh.p3b.cx, sh.p3b.cy );
    ctx.fill();
    ctx.beginPath();  // right leaf
    ctx.fillStyle = "rgba("+sh.s.clLS.r+","+sh.s.clLS.g+","+sh.s.clLS.b+","+sh.s.clLS.a+")";
    ctx.moveTo( sh.p3b.cx, sh.p3b.cy );
    ctx.lineTo( sh.p3t.cx, sh.p3t.cy );
    ctx.lineTo( sh.p4t.cx, sh.p4t.cy );
    ctx.lineTo( sh.p4b.cx, sh.p4b.cy );
    ctx.fill();
  }
  //resets shadows
  shadows = []; shadowCount = 0;
}












/////////////////// FLOWER HANDLER /////////////////////




/////---TRACKERS---/////


var pollinationAnimations = []; pollinationAnimationCount = 0;

var readyForNextMilestoneAnnouncement = true;
var milestoneFirstRedHasBeenRun = false;
var milestoneThirdHasBeenRun = false;
var milestoneHalfHasBeenRun = false;
var milestoneTwoThirdsHasBeenRun = false;
var milestone90HasBeenRun = false;




/////---OBJECTS---/////


///flower constructor 
function Flower( plant, parentSegment, basePoint1, basePoint2 ) {
  this.id = plant.flowerCount;
  this.plantId = plant.id;
  this.parentPlant = plant;
  this.generation = this.parentPlant.generation;
  this.parentSegment = parentSegment;
  this.zygoteGenotypes = [];
  this.seeds = [];
  this.mass = 0;
  this.bloomRatio = 0; 
  this.podOpenRatio = 0;
  this.podOpacity = 0;
  this.visible = true;
  this.hasFullyBloomed = false;
  this.ageSinceBlooming = 0;  // flower age since blooming in worldtime units
  this.pollenDispersalAnimationTimeTracker = 0;  // pollen dispersal animation time tracker
  this.isPollinated = false;
  this.hasReachedMaxSeeds = false;
  this.hasFullyClosed = false;
  this.hasSeeds = false;
  this.seedPodIsMature = false;
  this.podHasOpened = false;
  this.hasReleasedSeeds = false;
  //ovule points
  this.ptBL = basePoint1;  // base point left
  this.ptBR = basePoint2;  // base point right
  //hex (polinator pad) points
  var pfsmp = spanMidPoint( this.parentSegment.spF );  // parent forward span mid point
  var pbsmp = midPoint( this.parentSegment.ptB1, this.parentSegment.ptB2 );  // parent base span mid point
  var hexOriginX = pfsmp.x + ( pfsmp.x - pbsmp.x ) * 0.25;  // hex's origin x position ahead of growth
  var hexOriginY = pfsmp.y + ( pfsmp.y - pbsmp.y ) * 0.25;  // hex's origin y position ahead of growth
  this.ptHbL = addPt( pctFromXVal( hexOriginX ), pctFromYVal( hexOriginY ) );  // hex bottom left point
  this.ptHbR = addPt( pctFromXVal( hexOriginX ), pctFromYVal( hexOriginY ) );  // hex bottom right point
  this.ptHoL = addPt( pctFromXVal( hexOriginX ), pctFromYVal( hexOriginY ) );  // hex outer left point
  this.ptHoR = addPt( pctFromXVal( hexOriginX ), pctFromYVal( hexOriginY ) );  // hex outer right point
  this.ptHtL = addPt( pctFromXVal( hexOriginX ), pctFromYVal( hexOriginY ) );  // hex top left point
  this.ptHtR = addPt( pctFromXVal( hexOriginX ), pctFromYVal( hexOriginY ) );  // hex top right point
  this.ptHbL.mass = this.ptHbR.mass = this.mass;
  this.ptHoL.mass = this.ptHoR.mass = this.mass;
  this.ptHtL.mass = this.ptHtR.mass = this.mass;
  //ovule spans
  this.spOiL = addSp( this.ptBL.id, this.ptHbL.id );  // ovule inner left span
  this.spOiR = addSp( this.ptBR.id, this.ptHbR.id );  // ovule inner right span
  this.spCd = addSp( this.ptHbL.id, this.ptBR.id );  // downward (l to r) cross span
  this.spCu = addSp( this.ptBL.id, this.ptHbR.id );  // upward (l to r) cross span
  this.spCdP = addSp( this.ptHbL.id, this.parentSegment.ptB2.id );  // downward (l to r) cross span to parent
  this.spCuP = addSp( this.parentSegment.ptB1.id, this.ptHbR.id );  // upward (l to r) cross span to parent
  this.spOoL = addSp( this.ptBL.id, this.ptHoL.id );  // ovule outer left span 
  this.spOoR = addSp( this.ptBR.id, this.ptHoR.id );  // ovule outer right span 
  //hex (polinator pad) spans
  this.spHbM = addSp( this.ptHbL.id, this.ptHbR.id );  // hex bottom middle span
  this.spHbL = addSp( this.ptHbL.id, this.ptHoL.id );  // hex bottom left span
  this.spHbR = addSp( this.ptHbR.id, this.ptHoR.id );  // hex bottom right span
  this.spHtL = addSp( this.ptHoL.id, this.ptHtL.id );  // hex top left span
  this.spHtR = addSp( this.ptHtR.id, this.ptHoR.id );  // hex top right span
  this.spHtM = addSp( this.ptHtL.id, this.ptHtR.id );  // hex top middle span
  this.spHcH = addSp( this.ptHoL.id, this.ptHoR.id );  // hex cross horizontal span
  this.spHcDB = addSp( this.ptHtL.id, this.ptBR.id );  // hex cross downward span to flower base
  this.spHcUB = addSp( this.ptBL.id, this.ptHtR.id );  // hex cross upward span to flower base
  //bud tip point & scaffolding
  var htmmp = spanMidPoint( this.spHtM );  // hex top middle span mid point
  this.ptBudTip = addPt( pctFromXVal( htmmp.x ), pctFromYVal( htmmp.y ), "immaterial" );  // bud tip point
  this.ptBudTip.mass = this.mass;
  this.spBTSL = addSp( this.ptBudTip.id, this.ptHoL.id, "hidden" );  // bud tip scaffolding left span
  this.spBTSR = addSp( this.ptBudTip.id, this.ptHoR.id, "hidden" );  // bud tip scaffolding right span
  //petals
  this.ptPtL = addPt( pctFromXVal(this.ptBudTip.cx), pctFromYVal(this.ptBudTip.cy) );  // petal top left point  
  this.ptPtM = addPt( pctFromXVal(this.ptBudTip.cx), pctFromYVal(this.ptBudTip.cy) );  // petal top middle point  
  this.ptPtR = addPt( pctFromXVal(this.ptBudTip.cx), pctFromYVal(this.ptBudTip.cy) );  // petal top right point  
  this.ptPbL = addPt( pctFromXVal(this.ptBudTip.cx), pctFromYVal(this.ptBudTip.cy) );  // petal bottom left point  
  this.ptPbM = addPt( pctFromXVal(this.ptBudTip.cx), pctFromYVal(this.ptBudTip.cy) );  // petal bottom middle point  
  this.ptPbR = addPt( pctFromXVal(this.ptBudTip.cx), pctFromYVal(this.ptBudTip.cy) );  // petal bottom right point  
  this.ptPtL.mass = this.ptPtM.mass = this.ptPtR.mass = this.mass;
  this.ptPbL.mass = this.ptPbM.mass = this.ptPbR.mass = this.mass;
  //pod
  this.ptPodTipL = addPt( pctFromXVal(this.ptBudTip.cx), pctFromYVal(this.ptBudTip.cy) );  // pod tip left point 
  this.ptPodTipR = addPt( pctFromXVal(this.ptBudTip.cx), pctFromYVal(this.ptBudTip.cy) );  // pod tip right point
  this.ptPodTipL.mass = this.ptPodTipR.mass = this.mass;
  //colors
  this.clP = plant.flowerColor; // petal color (hue & lightness)
  this.clH = plant.pollenPadColor;  // hex (pollen pad) color
  this.clOv = C.hdf;  // ovule color (dark green when healthy)
  this.clO = C.hol;  // outline color (very dark brown when healthy)
  this.isRed = checkForRedPetals( this.clP );  // true if petals are a red hue
}

///pollination animation object constructor
function PollinationAnimation( pollinatorFlower, pollinatedFlower ) {
  this.id = pollinationAnimationCount;
  this.f1 = pollinatorFlower;  // flower 1 (pollinator)
  this.f2 = pollinatedFlower;  // flower 2 (pollinated)
  this.bls = [];  // burst lines collection
  this.pls = [];  // pollination lines collection
  this.op = 0.9;  // pollination animation opacity (base number)
  this.burstOrigin = spanMidPoint( this.f1.spHcH );
  this.burstDuration = 120;  // pollen burst animation duration in iterations
  this.iterationCount = 0;  // number of iterations since beginning of animation
  this.glowIterationCount = 0;  // number of iterations since pollination glow began
  this.pollenBurstComplete = false; 
  this.pollinatorLinesHaveArrived = false;
  this.pollenPadGlowHasBegun = false;
  this.pollenPadGlowComplete = false;
  //burst lines (pollen lines that burst randomly from flower 1)
  for ( var i=0; i<5; i++) {  // creates a burst line each iteration
    var totalDistance = Tl.rfb( canvas.width*0.2, canvas.width*0.1 );  // total distance burst line will travel
    var xVal = Tl.rfb( 0, totalDistance );  // base x value (random)
    var xDist = Tl.rib(1,2) == 1 ? xVal : -xVal;  // x distance burst line will travel 
    var yVal = Math.sqrt( totalDistance*totalDistance - xDist*xDist );  // base y value (based on x total distance)
    var yDist = Tl.rib(1,2) == 1 ? yVal : -yVal;  // y distance burst line will travel
    var blxa = [];  // burst line x positions array 
    var blya = [];  // burst line y positions array
    for ( var j=0; j<8; j++ ) {  // populates burst line x & y positions arrays
      blxa.push( this.burstOrigin.x ); 
      blya.push( this.burstOrigin.y ); 
    }
    this.bls.push({  // burst line instances
      dist: totalDistance,  // total distance burst point will travel
      dx: this.burstOrigin.x + xDist,  // destination x position of burst line
      dy: this.burstOrigin.y + yDist,  // destination y position of burst line
      xa: blxa,  // x positions array 
      ya: blya,  // y positions array 
    });
  }
  //pollination lines (pollen lines that travel from flower 1 to flower 2)
  for ( var k=0; k<1; k++) {  // creates a pollination line each iteration
    var plxa = [];  // pollination line x positions array 
    var plya = [];  // pollination line y positions array
    for ( var l=0; l<1; l++ ) {  // populates pollination line x & y positions arrays
      plxa.push( this.burstOrigin.x ); 
      plya.push( this.burstOrigin.y ); 
    }
    this.pls.push({  // pollination line instances
      xa: plxa,  // x positions array 
      ya: plya,  // y positions array 
    });
  }
}

var HeightMarker = {
  w: canvas.width*0.025,  // marker width 
  h: canvas.width*0.025,  // marker height
  y: canvas.height,  // marker position y value (at point)
  chrfx: null,  // current highest red flower x value
  baa: false,  // bounce animation active
  bat: 0,  // bounce animation time
  laa: false,  // line animation active
  lat: 0,  // line animation time
};



/////---FUNCTIONS---/////


///creates a new flower
function createFlower( plant, parentSegment, basePoint1, basePoint2 ) {
  plant.flowerCount++;
  plant.flowers.push( new Flower( plant, parentSegment, basePoint1, basePoint2 ) );
  plant.hasFlowers = true;
  parentSegment.child = plant.flowers[plant.flowers.length-1];
  parentSegment.hasChild = true;
}

///creates a new pollination animation
function createPollinationAnimation( pollinatorFlower, pollinatedFlower ) {
  pollinationAnimationCount++;
  pollinationAnimations.push( new PollinationAnimation( pollinatorFlower, pollinatedFlower ) );
}

///checks whether a segment is ready to generate a flower
function readyForFlower( plant, segment ) {
  var segmentIsLastSegment = segment.id === plant.maxTotalSegments;
  var plantDoesNotHaveFlowers = !plant.hasFlowers;
  var segmentIsReadyForFlowerBud = segment.spF.l > plant.maxSegmentWidth*0.333;
  return segmentIsLastSegment && plantDoesNotHaveFlowers && segmentIsReadyForFlowerBud;
}

///checks whether a flower's petals are red
function checkForRedPetals( color ) {  // color as hue & lightness object: { h: <value>, l: <value> }
  var hq = color.h <= 15 || color.h >= 345;  // hue qualifies
  var lq = color.l >= 30 && color.l <= 50;  // lightness qualifies
  return hq && lq;
}

///expands flower bud
function expandFlowerBud( plant, flower) {
  var p = plant;
  var f = flower;
  var pfgr = p.forwardGrowthRate;  // plant forward growth rate
  var fbw = p.maxSegmentWidth * p.maxFlowerBaseWidth;  // flower base width (at maturity)
  var bhgr = f.spHbM.l < fbw*0.3 ? pfgr*1.5 : pfgr*0.5;  // bud height growth rate (slows when stability established)
  var fogr = p.outwardGrowthRate*1.5;  // flower outward growth rate
  var htmp = spanMidPoint( f.spHtM );  // hex top middle span mid point
  var hbmp = spanMidPoint( f.spHbM );  // hex bottom middle span mid point
  var hcp = spanMidPoint( f.spHcH );  // hex center point
  var budTipX = htmp.x + ( htmp.x - hbmp.x ) * p.flowerBudHeight;  // bud tip x value
  var budTipY = htmp.y + ( htmp.y - hbmp.y ) * p.flowerBudHeight;  // bud tip y value
  // (ovule)
  f.spOiL.l = distance( f.ptBL, f.ptHbL);  // ovule inner left span
  f.spOiR.l = f.spOiL.l;  // ovule inner right span
  f.spCd.l = distance( f.ptHbL, f.ptBR );  // downward cross span
  f.spCu.l = f.spCd.l;  // upward cross span
  f.spCdP.l = distance( f.ptHbL, f.parentSegment.ptB2 ) + pfgr;  // downward cross span to parent
  f.spCuP.l = f.spCdP.l;  // upward cross span to parent
  f.spOoL.l = distance( f.ptBL, f.ptHoL );  // ovule outer left span 
  f.spOoR.l = f.spOoL.l;  // ovule outer right span  
  // (hex/polinator pad)
  f.spHbM.l += fogr;  // hex bottom middle span
  f.spHbL.l = f.spHbM.l;  // hex lower left span
  f.spHbR.l = f.spHbM.l;  // hex lower right span
  f.spHtL.l = f.spHbM.l;  // hex top left span
  f.spHtR.l = f.spHbM.l;  // hex top right span
  f.spHtM.l = f.spHbM.l;  // hex top middle span
  f.spHcH.l = f.spHbM.l*2;  // hex cross horizontal span
  f.spHcDB.l = distance( f.ptHtL, f.ptBR ) + bhgr;  // hex cross downward span to flower base
  f.spHcUB.l = f.spHcDB.l;  // hex cross upward span to flower base
  // (bud tip point & scaffolding)
  f.ptBudTip.cx = budTipX;  // bud tip x value
  f.ptBudTip.cy = budTipY;  // bud tip y value
  f.spBTSL.l = distance( f.ptBudTip, f.ptHoL );  // petal bottom middle left span
  f.spBTSR.l = distance( f.ptBudTip, f.ptHoR );  // petal bottom middle right span
  // (petals)
  f.ptPtL.cx = budTipX; f.ptPtL.cy = budTipY;  // syncs top left petal tip with bud tip during bud growth
  f.ptPtL.px = f.ptPtL.cx; f.ptPtL.py = f.ptPtL.cy;
  f.ptPtM.cx = budTipX; f.ptPtM.cy = budTipY;  // syncs top middle petal tip with bud tip during bud growth
  f.ptPtM.px = f.ptPtM.cx; f.ptPtM.py = f.ptPtM.cy;
  f.ptPtR.cx = budTipX; f.ptPtR.cy = budTipY;  // syncs top right petal tip with bud tip during bud growth
  f.ptPtR.px = f.ptPtR.cx; f.ptPtR.py = f.ptPtR.cy;
  f.ptPbL.cx = budTipX; f.ptPbL.cy = budTipY;  // syncs bottom left petal tip with bud tip during bud growth
  f.ptPbL.px = f.ptPbL.cx; f.ptPbL.py = f.ptPbL.cy;
  f.ptPbM.cx = budTipX; f.ptPbM.cy = budTipY;  // syncs bottom middle petal tip with bud tip during bud growth
  f.ptPbM.px = f.ptPbM.cx; f.ptPbM.py = f.ptPbM.cy;
  f.ptPbR.cx = budTipX; f.ptPbR.cy = budTipY;  // syncs bottom right petal tip with bud tip during bud growth
  f.ptPbR.px = f.ptPbR.cx; f.ptPbR.py = f.ptPbR.cy;
  // (pod)
  f.ptPodTipL.cx = budTipX; f.ptPodTipL.cy = budTipY;  // syncs left pod tip with bud tip during bud growth
  f.ptPodTipL.px = f.ptPodTipL.cx; f.ptPodTipL.py = f.ptPodTipL.cy;
  f.ptPodTipR.cx = budTipX; f.ptPodTipR.cy = budTipY;  // syncs right pod tip with bud tip during bud growth
  f.ptPodTipR.px = f.ptPodTipR.cx; f.ptPodTipR.py = f.ptPodTipR.cy;
}

///adjusts petal arc throughout flower bloom
function petalArcAdjustment( flower, basePoint1, basePoint2, petalTipPoint, expansionStart, arcHeightEnd) {
  var f = flower;
  var a = basePoint1;  // point a (leftmost petal base point)
  var b = basePoint2;  // point b (rightmost petal base point)
  var c = petalTipPoint;  // point c (petal tip point)
  if ( (b.cy - a.cy)*(c.cx-b.cx) - (c.cy-b.cy)*(b.cx-a.cx) >= 0 ) {  // counterclockwise point orientation
    return f.bloomRatio < expansionStart ? arcHeightEnd * expansionStart : arcHeightEnd * f.bloomRatio;
  } else {  // clockwise point orientation
    return f.bloomRatio < expansionStart ? -arcHeightEnd * expansionStart : -arcHeightEnd * f.bloomRatio;
  }
}

///unfolds petals ( xShift and yShift are in units of hex height in relation to bud tip )
function positionPetal( plant, flower, petalTipPoint, xShift, yShift ) {
  var p = plant;
  var f = flower;
  var ptp = petalTipPoint;
  var hexHeight = distance( f.ptHtL, f.ptHbL );
  var htmp = spanMidPoint( f.spHtM );  // hex top middle span mid point
  var hbmp = spanMidPoint( f.spHbM );  // hex bottom middle span mid point
  var hcp = spanMidPoint( f.spHcH );  // hex center point
  var bh = p.flowerBudHeight;  // bud height
  var budTip = { x: ( htmp.x + ( htmp.x - hbmp.x ) * bh ), y: (htmp.y + ( htmp.y - hbmp.y ) * bh) };  // bud tip
  var fullBloomX = hbmp.x + (hexHeight*-xShift) * (hcp.y-hbmp.y)/(hexHeight*0.5) + (hbmp.x-hcp.x)*(yShift-2.5)*2;
  var fullBloomY = hbmp.y - (hexHeight*-xShift) * (hcp.x-hbmp.x)/(hexHeight*0.5) + (hbmp.y-hcp.y)*(yShift-2.5)*2;
  ptp.cx = ptp.px = budTip.x + (fullBloomX - budTip.x) * f.bloomRatio;
  ptp.cy = ptp.py = budTip.y + (fullBloomY - budTip.y) * f.bloomRatio;
}

///positions all petals
function positionAllPetals( plant, flower ) {
  positionPetal( plant, flower, flower.ptPtL, -1.75, 1 );  // top left petal
  positionPetal( plant, flower, flower.ptPtM, 0, 0.25 );  // top middle petal
  positionPetal( plant, flower, flower.ptPtR, 1.75, 1 );  // top right petal
  positionPetal( plant, flower, flower.ptPbL, -1.75, 3 );  // bottom left petal
  positionPetal( plant, flower, flower.ptPbM, 0, 3.75 );  // bottom middle petal
  positionPetal( plant, flower, flower.ptPbR, 1.75, 3 );  // bottom right petal
}

///readies flower to accept pollination
function acceptPollination( pollinatedFlower ) { 
  var availablePollinatorFlowers = [];
  if ( Tl.rib( 1, Math.round(suL/pollinationFrequency) ) === 1 ) {
    for ( var i=0; i<plants.length; i++ ) {
      for ( var j=0; j<plants[i].flowers.length; j++ ) {
        var ppf = plants[i].flowers[j];  // potential pollinator flower
        var ppfIsElegible = allowSelfPollination || ppf != pollinatedFlower;
        var ppfIsReady = ppf.bloomRatio === 1 && plants[i].energy > plants[i].maxEnergyLevel*minPollEnLevRatio;
        if ( ppfIsElegible && ppfIsReady ) { availablePollinatorFlowers.push( ppf ); }
      }
    }
  }
  if ( availablePollinatorFlowers.length > 0 ) {
    var pollinatorFlower = Tl.refa( availablePollinatorFlowers );
    pollinateFlower( pollinatedFlower, pollinatorFlower );
  }
  var maxSeeds = Math.floor( pollinatedFlower.parentPlant.maxTotalSegments * maxSeedsPerFlowerRatio ); 
  maxSeeds = maxSeeds < 3 ? 3 : maxSeeds > 7 ? 7 : maxSeeds;
  if ( pollinatedFlower.zygoteGenotypes.length === maxSeeds ) { pollinatedFlower.hasReachedMaxSeeds = true; }
}

///pollinates flower
function pollinateFlower( pollinatedFlower, pollinatorFlower ) {
  if ( runPollinationAnimations ) { createPollinationAnimation( pollinatorFlower, pollinatedFlower ); }
  var zygoteGenotype = meiosis( pollinatedFlower.parentPlant.genotype, pollinatorFlower.parentPlant.genotype );
  pollinatedFlower.zygoteGenotypes.push( zygoteGenotype );
  pollinatedFlower.isPollinated = true;
}

///places seeds in pod
function placeSeedsInPod( flower ) {
  if ( !flower.hasSeeds ) {
    for ( var i=0; i<flower.zygoteGenotypes.length; i++ ) {
      createSeed( flower, flower.zygoteGenotypes[i] ); 
    }
    flower.hasSeeds = true;
  }
}

function keepSeedsInPod( flower) {
  if ( flower.hasSeeds ) {
    for ( var j=0; j<flower.seeds.length; j++ ) {
      var seed = flower.seeds[j];
      seed.p1.cx = seed.p1.px = spanMidPoint(flower.spHbM).x;
      seed.p1.cy = seed.p1.py = spanMidPoint(flower.spHbM).y;
      seed.p2.cx = seed.p2.px = spanMidPoint(flower.spHtM).x;
      seed.p2.cy = seed.p2.py = spanMidPoint(flower.spHtM).y;
    }
  }
}

///checks if pod is ready to release seeds
function podReadyToOpen( plant ) {
  return plant.energy < plant.maxEnergyLevel*podOpenEnergyLevelRatio;
}

///unfolds pod halves ( xShift and yShift are in units of hex height in relation to bud tip )
function positionPodHalf( plant, flower, podTipPoint, xShift, yShift ) {
  var p = plant;
  var f = flower;
  var ptp = podTipPoint;
  var hexHeight = distance( f.ptHtL, f.ptHbL );
  var htmp = spanMidPoint( f.spHtM );  // hex top middle span mid point
  var hbmp = spanMidPoint( f.spHbM );  // hex bottom middle span mid point
  var hcp = spanMidPoint( f.spHcH );  // hex center point
  var bh = p.flowerBudHeight;  // bud height
  var budTip = { x: ( htmp.x + ( htmp.x - hbmp.x ) * bh ), y: (htmp.y + ( htmp.y - hbmp.y ) * bh) };  // bud tip
  var fullyOpenX = hbmp.x + (hexHeight*-xShift) * (hcp.y-hbmp.y)/(hexHeight*0.5) + (hbmp.x-hcp.x)*(yShift-2.5)*2;
  var fullyOpenY = hbmp.y - (hexHeight*-xShift) * (hcp.x-hbmp.x)/(hexHeight*0.5) + (hbmp.y-hcp.y)*(yShift-2.5)*2;
  ptp.cx = ptp.px = budTip.x + (fullyOpenX - budTip.x) * f.podOpenRatio;
  ptp.cy = ptp.py = budTip.y + (fullyOpenY - budTip.y) * f.podOpenRatio;
}

///positions pod halves
function positionBothPodHalves( plant, flower ) {
  positionPodHalf( plant, flower, flower.ptPodTipL, -0.5, 0.5 );  // left pod half
  positionPodHalf( plant, flower, flower.ptPodTipR, 0.5, 0.5 );  // right pod half
}

///lengthens flower spans for bud growth & blooming
function developFlower( plant, flower ) {
  var p = plant;
  var f = flower;
  if ( f.hasFullyBloomed ) { f.ageSinceBlooming++; } 
  //if bud is not fully grown and has enough energy for growth, it continues to grow until mature
  if ( !f.budHasFullyMatured && p.energy > 0 ) {
    expandFlowerBud( p, f );
    f.budHasFullyMatured = f.spHbM.l >= p.maxSegmentWidth*p.maxFlowerBaseWidth;
  //otherwise, if bud has not fully bloomed, it continues to bloom
  } else if ( f.budHasFullyMatured && !f.hasFullyBloomed && p.energy > p.maxEnergyLevel*minBloomEnLevRatio ) {
    if ( f.bloomRatio < 1 ) { 
      f.bloomRatio += 0.01; 
    } else { 
      f.bloomRatio = 1;
      f.hasFullyBloomed = true; }
  //otherwise, if fully bloomed, flower accepts pollination until zygote count reaches max seed count
  } else if ( f.hasFullyBloomed && p.energy > p.maxEnergyLevel*minPollEnLevRatio && !f.hasReachedMaxSeeds ) {    
    acceptPollination( f );
  //otherwise, if flower is pollinated and has not fully closed, it closes
  } else if ( f.isPollinated && !f.hasFullyClosed ) { 
    if ( f.bloomRatio > 0 ) { f.bloomRatio -= 0.03; } else { f.hasFullyClosed = true; } // closes petals
  //otherise, if flower has fully closed, it develops into a seed pod
  } else if ( f.hasFullyClosed && !f.seedPodIsMature ) {
    placeSeedsInPod( f );
    keepSeedsInPod( f );
    f.podOpacity = f.podOpacity < 1 ? f.podOpacity + 0.1 : 1;
    if ( f.podOpacity === 1 ) { f.visible = false; f.seedPodIsMature = true; }
  //otherwise, if seed pod is mature but not ready to release seeds, the seeds stay in the pod
  } else if ( f.seedPodIsMature && !podReadyToOpen( p ) ) {
    keepSeedsInPod( f );
  //otherwise, if the seed pod hasn't released seeds and pod is ready to open, it opens
  } else if ( !f.podHasOpened && podReadyToOpen( p ) ) {
    if ( f.podOpenRatio < 1 ) { f.podOpenRatio += 0.1; } else { f.podHasOpened = true; } // opens pod 
    keepSeedsInPod( f );
  //otherwise, if the plant is still alive, hold seeds in the opened pod
  } else if ( p.isAlive ) {
    keepSeedsInPod( f );
  //otherwise, if the pod has opened, the seeds haven't been released, and the plant is dead, release seeds
  } else if ( !f.hasReleasedSeeds ) {
    for ( var i=0; i<f.seeds.length; i++ ) { dropSeed( flower.seeds[i] ); }
    f.hasReleasedSeeds = true;
  }
}

///track highest flower heights
function trackMaxRedFlowerHeights( flower ) {
  var f = flower;
  if ( f.isRed && f.bloomRatio === 1 ) {
    var heightPct = (canvas.height-f.ptPtM.cy)*100/canvas.height ;
    if ( heightPct > highestRedFlowerPct ) { 
      highestRedFlowerPct = heightPct; 
      if ( highestRedFlowerPct > 100 ) { highestRedFlowerPct = 100; }  // caps highest red flower percentage at 100%
      HeightMarker.chrfx = f.ptPtM.cx;  // updates flower's top petal tip x value
    }
  }
}

///removes a polination animation by id
function removePollinationAnimation( id ) {
  for ( var i=0; i<pollinationAnimations.length; i++){ 
    if ( pollinationAnimations[i].id === id) { pollinationAnimations.splice(i,1); }
  }
}

///removes all flower points & spans
function removeAllflowerPointsAndSpans( plant ) {
  for ( var i=0; i<plant.flowers.length; i++ ) {
    var f = plant.flowers[i];
    removePoint( f.ptHbL.id );  // flower hex bottom left point
    removePoint( f.ptHbR.id );  // flower hex bottom right point
    removePoint( f.ptHoL.id );  // flower hex outer left point
    removePoint( f.ptHoR.id );  // flower hex outer right point
    removePoint( f.ptHtL.id );  // flower hex top left point
    removePoint( f.ptHtR.id );  // flower hex top right point
    removePoint( f.ptBudTip.id );  // flower bud tip point
    removePoint( f.ptPtL.id );  // flower petal top left point  
    removePoint( f.ptPtM.id );  // flower petal top middle point  
    removePoint( f.ptPtR.id );  // flower petal top right point  
    removePoint( f.ptPbL.id );  // flower petal bottom left point  
    removePoint( f.ptPbM.id );  // flower petal bottom middle point  
    removePoint( f.ptPbR.id );  // flower petal bottom right point  
    removePoint( f.ptPodTipL.id );  // flower pod tip left point 
    removePoint( f.ptPodTipR.id );  // flower pod tip right point
    removeSpan( f.spOiL.id );  // flower ovule inner left span
    removeSpan( f.spOiR.id );  // flower ovule inner right span
    removeSpan( f.spCd.id );  // flower downward (l to r) cross span
    removeSpan( f.spCu.id );  // flower upward (l to r) cross span
    removeSpan( f.spCdP.id );  // flower downward (l to r) cross span to parent
    removeSpan( f.spCuP.id );  // flower upward (l to r) cross span to parent
    removeSpan( f.spOoL.id );  // flower ovule outer left span 
    removeSpan( f.spOoR.id );  // flower ovule outer right span 
    removeSpan( f.spHbM.id );  // flower hex bottom middle span
    removeSpan( f.spHbL.id );  // flower hex bottom left span
    removeSpan( f.spHbR.id );  // flower hex bottom right span
    removeSpan( f.spHtL.id );  // flower hex top left span
    removeSpan( f.spHtR.id );  // flower hex top right span
    removeSpan( f.spHtM.id );  // flower hex top middle span
    removeSpan( f.spHcH.id );  // flower hex cross horizontal span
    removeSpan( f.spHcDB.id );  // flower hex cross downward span to flower base
    removeSpan( f.spHcUB.id );  // flower hex cross upward span to flower base
    removeSpan( f.spBTSL.id );  // flower bud tip scaffolding left span
    removeSpan( f.spBTSR.id );  // flower bud tip scaffolding right span
  }
}




/////---RENDERERS---/////


///renders flowers
function renderFlowers( plant ) {
  var p = plant;
  if ( p.hasFlowers ) {
    for ( var i=0; i<p.flowers.length; i++) {
      var f = p.flowers[i];
      positionBothPodHalves(p,f);  // ensures pod half positions are updated every iteration
      if ( f.visible ) {
        var pah;  // petal arc height
        positionAllPetals(p,f);  // ensures flower petal positions are updated every iteration
        ctx.lineJoin = "round";
        ctx.lineCap = "round";
        //pulsing indicator showing that flower color qualifies as red
        if ( viewRedFlowerIndicator && f.isRed && f.bloomRatio === 1 && p.isAlive && p.energy > 0 ) {
          var fcp = spanMidPoint( f.spHcH );  // flower center point (center of hex)
          var pt = 100;  // pulse time (in worldTime units) 
          var ir = f.spHcH.l * (worldTime%pt)*0.011 + f.spHcH.l*0.75;  // indicator radius
          var ia = 1-(worldTime%pt)/pt;  // indicator alpha
          ctx.strokeStyle = "rgba(255,0,0,"+ia+")";
          ctx.beginPath();
          ctx.lineWidth = f.spHcH.l*0.2;
          ctx.arc( fcp.x, fcp.y, ir, 0, 2*Math.PI );
          ctx.stroke();
        }
        //top petals
        ctx.lineWidth = 1;
        ctx.fillStyle = "hsla("+f.clP.h+",100%,"+f.clP.l+"%,"+p.opacity+")"; 
        ctx.strokeStyle = "rgba("+f.clO.r+","+f.clO.g+","+f.clO.b+","+p.opacity+")";
        ctx.beginPath();  // top middle petal
        ctx.moveTo( f.ptHtL.cx, f.ptHtL.cy ); 
        pah = petalArcAdjustment( f, f.ptHtL, f.ptHtR, f.ptPtM, 0.2, 0.45);
        Tl.arcFromTo( f.ptHtL, f.ptPtM, pah ); Tl.arcFromTo( f.ptPtM, f.ptHtR, pah );
        ctx.fill(); ctx.stroke();
        ctx.beginPath();  // top left petal
        ctx.moveTo( f.ptHoL.cx, f.ptHoL.cy );
        pah = petalArcAdjustment( f, f.ptHoL, f.ptHtL, f.ptPtL, 0.2, 0.35);
        Tl.arcFromTo( f.ptHoL, f.ptPtL, pah ); Tl.arcFromTo( f.ptPtL, f.ptHtL, pah );
        ctx.fill(); ctx.stroke();
        ctx.beginPath();  // top right petal
        ctx.moveTo(f.ptHtR.cx, f.ptHtR.cy); 
        pah = petalArcAdjustment( f, f.ptHtR, f.ptHoR, f.ptPtR, 0.2, 0.35);
        Tl.arcFromTo( f.ptHtR, f.ptPtR, pah ); Tl.arcFromTo( f.ptPtR, f.ptHoR, pah );
        ctx.fill(); ctx.stroke();
        //ovule
        ctx.beginPath();
        ctx.fillStyle = "rgba("+f.clOv.r+","+f.clOv.g+","+f.clOv.b+","+p.opacity+")"; 
        ctx.strokeStyle = "rgba("+f.clO.r+","+f.clO.g+","+f.clO.b+","+p.opacity+")";  
        ctx.moveTo(f.ptBL.cx, f.ptBL.cy);
        Tl.arcFromTo( f.ptBL, f.ptHoL, 0.1 );
        ctx.lineTo(f.ptHoR.cx, f.ptHoR.cy);
        Tl.arcFromTo( f.ptHoR, f.ptBR, 0.1 );
        ctx.fill();
        ctx.stroke();     
        //hex (polinator pad)
        ctx.beginPath();
        ctx.fillStyle = "rgba("+ Math.round(f.clH.r)+","+
                                 Math.round(f.clH.g)+","+
                                 Math.round(f.clH.b)+","+
                                 p.opacity+")"; 
        ctx.moveTo(f.ptHtR.cx, f.ptHtR.cy);
        ctx.lineTo(f.ptHoR.cx, f.ptHoR.cy);
        ctx.lineTo(f.ptHbR.cx, f.ptHbR.cy);
        ctx.lineTo(f.ptHbL.cx, f.ptHbL.cy);
        ctx.lineTo(f.ptHoL.cx, f.ptHoL.cy);
        ctx.lineTo(f.ptHtL.cx, f.ptHtL.cy);
        ctx.fill();
        ctx.beginPath();
        ctx.strokeStyle = "rgba("+f.clO.r+","+f.clO.g+","+f.clO.b+","+p.opacity+")";  
        ctx.moveTo(f.ptHoL.cx, f.ptHoL.cy);
        ctx.lineTo(f.ptHtL.cx, f.ptHtL.cy);
        ctx.lineTo(f.ptHtR.cx, f.ptHtR.cy);
        ctx.lineTo(f.ptHoR.cx, f.ptHoR.cy);
        ctx.lineTo(f.ptHbR.cx, f.ptHbR.cy);
        ctx.lineTo(f.ptHbR.cx, f.ptHbR.cy);
        ctx.lineTo(f.ptHbL.cx, f.ptHbL.cy);
        ctx.lineTo(f.ptHoL.cx, f.ptHoL.cy);
        ctx.stroke();
        //bottom petals
        ctx.fillStyle = "hsla("+f.clP.h+",100%,"+f.clP.l+"%,"+p.opacity+")";  
        ctx.strokeStyle = "rgba("+f.clO.r+","+f.clO.g+","+f.clO.b+","+p.opacity+")";
        ctx.beginPath();  // bottom left petal
        ctx.moveTo( f.ptHoL.cx, f.ptHoL.cy );
        pah = petalArcAdjustment( f, f.ptHoL, f.ptHbL, f.ptPbL, 0.35, 0.35);
        Tl.arcFromTo( f.ptHoL, f.ptPbL, pah ); Tl.arcFromTo( f.ptPbL, f.ptHbL, pah );
        ctx.fill(); ctx.stroke();
        ctx.beginPath();  // bottom right petal
        ctx.moveTo(f.ptHbR.cx, f.ptHbR.cy);
        pah = petalArcAdjustment( f, f.ptHbR, f.ptHoR, f.ptPbR, 0.35, 0.35);
        Tl.arcFromTo( f.ptHbR, f.ptPbR, pah ); Tl.arcFromTo( f.ptPbR, f.ptHoR, pah );
        ctx.fill(); ctx.stroke();
        ctx.beginPath();  // bottom middle petal
        ctx.moveTo( f.ptHbL.cx, f.ptHbL.cy );
        pah = petalArcAdjustment( f, f.ptHbL, f.ptHbR, f.ptPbM, 0.2, 0.45);
        Tl.arcFromTo( f.ptHbL, f.ptPbM, pah ); Tl.arcFromTo( f.ptPbM, f.ptHbR, pah );
        ctx.fill(); ctx.stroke();
      }
      if ( viewPods ) { renderPods( f ); }
      trackMaxRedFlowerHeights(f);
    }
  }
}

///render pollen burst
function renderPollenBurst( pollinationAnimation ) {
  var pa = pollinationAnimation;
  var blsqi = 1;  // burst line squiggle intensity
  for ( var i=0; i<pa.bls.length; i++ ) {
    var bl = pa.bls[i];  // burst line
    var blInc = bl.dist / pa.burstDuration;  // increment of burst line movement per iteration
    var blXDiff = bl.dx - pa.burstOrigin.x;  // x difference between flower 1 center and burst destination point
    var blYDiff = bl.dy - pa.burstOrigin.y;  // y difference between flower 1 center and burst destination point
    var blIncRat = blInc / bl.dist;  // increment ratio (ratio of increment to current total distance)
    var blXInc = blXDiff*blIncRat;  // burst line x increment this iteration
    var blYInc = blYDiff*blIncRat;  // burst line y increment this iteration
    var blo = (1-pa.iterationCount/pa.burstDuration)*pa.op;  // burst line opacity (reduces gradually)
    var burstComplete = ( pa.iterationCount >= pa.burstDuration );
    blXInc += Tl.rfb(-blsqi,blsqi);  // adds x squiggle
    blYInc += Tl.rfb(-blsqi,blsqi);  // adds y squiggle
    ctx.lineJoin = "round";
    ctx.lineCap = "round";
    ctx.lineWidth = canvas.width*0.0015;
    if ( pa.iterationCount <= pa.burstDuration ) { 
      bl.xa.unshift( bl.xa[0] + blXInc );  // adds new x value to burst line x values array as first element
      bl.xa.pop();  // removes last element of burst line x values array
      bl.ya.unshift( bl.ya[0] + blYInc );  // adds new y value to burst line y values array as first element
      bl.ya.pop();  // removes last element of burst line y values array
      ctx.beginPath();
      ctx.moveTo( bl.xa[0], bl.ya[0]);
      var blto = blo;  // burst line tail opacity at head
      for ( var j=1; j<bl.xa.length; j++) {  // draws burst line tail
        blto -= blo/bl.xa.length;  
        ctx.strokeStyle = "rgba( "+C.pl.r+", "+C.pl.g+", "+C.pl.b+", "+blto+" )";
        ctx.lineTo( bl.xa[j], bl.ya[j] );
        ctx.stroke();
      }
    } 
  }
  if ( pa.iterationCount >= pa.burstDuration ) { pa.pollenBurstComplete = true; }
}

///render pollination glow (temporary polinator pad glow indicating flower has been pollinated)
function renderPollinationGlow( pollinationAnimation ) {
  var pa = pollinationAnimation;
  var glowDuration = pa.f1 === pa.f2 ? 240 : 120; 
  if ( !pa.pollenPadGlowHasBegun ) {
    pa.f2.clH = C.pg;  // changes pollen pad color to temporary glow color
    pa.pollenPadGlowHasBegun = true;
  }
  if ( pa.pollenPadGlowHasBegun && !pa.pollenPadGlowComplete ) {
    pa.f2.clH = Tl.rgbaCs( C.pg, C.pp, pa.f2.clH, glowDuration );  // fades pollen pad color back to normal
    pa.glowIterationCount++;
    if ( pa.glowIterationCount === glowDuration ) { 
      pa.pollenPadGlowComplete = true; 
    }
  }
}

///render pollinator lines
function renderPollinatorLines( pollinationAnimation ) {
  var pa = pollinationAnimation;
  var plInc = canvas.width*0.0075;  // increment of pollination line movement per iteration
  var plsqi = 2;  // pollination line squiggle intensity
  for ( var i=0; i<pa.pls.length; i++ ) {
    var pl = pa.pls[i];  // pollination line
    var f2cp = spanMidPoint( pa.f2.spHcH );  // flower 2 (pollinated flower) current center point position 
    var plXDiff = f2cp.x - pl.xa[0];  // x difference between pollination line head and flower 2
    var plYDiff = f2cp.y - pl.ya[0];  // y difference between pollination line head and flower 2
    var plDist =  Math.sqrt( plXDiff*plXDiff + plYDiff*plYDiff);  // distance from pollination line head to flower 2
    var plIncRat = plInc/plDist;  // increment ratio (ratio of increment to current total distance)
    var plXInc = plXDiff*plIncRat;  // pollination line head x increment this iteration
    var plYInc = plYDiff*plIncRat;  // pollination line head y increment this iteration
    plXInc += Tl.rfb(-plsqi,plsqi);  // adds x squiggle
    plYInc += Tl.rfb(-plsqi,plsqi);  // adds y squiggle
    if ( plDist > plInc ) { 
      pl.xa.unshift( pl.xa[0] + plXInc );  // adds new x value to pollination line x values array as first element
      pl.ya.unshift( pl.ya[0] + plYInc );  // adds new y value to pollination line y values array as first element
    } 
    pl.xa.pop();  // removes last element of pollination line x values array
    pl.ya.pop();  // removes last element of pollination line y values array
    ctx.beginPath();
    if ( pl.xa.length > 0 ) {
      ctx.moveTo( pl.xa[0], pl.ya[0]);
      var plto = pa.op;  // pollination line tail opacity at head
      for ( var j=1; j<pl.xa.length; j++ ) {
        plto -= pa.op/pl.xa.length;  
        ctx.strokeStyle = ctx.strokeStyle = "rgba( "+C.pl.r+", "+C.pl.g+", "+C.pl.b+", "+plto+" )";
        ctx.lineTo( pl.xa[j], pl.ya[j] );
        ctx.stroke();
      }
    } else {
      pa.pollinatorLinesHaveArrived = true;
    }
    if ( viewPollinationGlow && pa.pollinatorLinesHaveArrived ) {
      renderPollinationGlow( pa );  // pollination pad glow
    }
  }
}

///render pollination animations
function renderPollinationAnimations() {
  var idsForRemoval = [];
  for ( var i=0; i<pollinationAnimations.length; i++ ) {
    var pa = pollinationAnimations[i];
    // burst lines 
    if ( viewPollenBursts ) { renderPollenBurst( pa ); } else { pa.pollenBurstComplete = true; }  
    // pollination lines 
    if ( viewPollinatorLines ) { renderPollinatorLines( pa ); } else { pa.pollinatorLinesHaveArrived = true; } 
    // pollination glow (if pollinator lines haven been turned off; otherwise handled in renderPollinatorLines)
    if ( !viewPollinatorLines && viewPollinationGlow  ) {
      renderPollinationGlow( pa );
    } else if ( !viewPollinationGlow ) {
      pa.pollenPadGlowComplete = true;
    }
    pa.iterationCount++;
    var animationComplete = ( pa.pollenBurstComplete && pa.pollinatorLinesHaveArrived && pa.pollenPadGlowComplete );
    if ( animationComplete ) { idsForRemoval.push( pa.id ); }  // removes animation instance after complete
  }
  for ( var j=0; j<idsForRemoval.length; j++) { removePollinationAnimation( idsForRemoval[j] ); }
}

///renders new best height announcements
function renderHeightAnnouncement() {
  var fsi = 2.5;  // font size max increase
  var td = 0.5;  // top decrease (per animation segment)
  var ha = -3;  // height adjustment
  var dur = 300;  // duration (of each animation segment)
  var c = "rgba( 130, 0, 0, 1 )";  // color (default to dark red)
  if ( highestRedFlowerPct >= 80) {
    td = -0.5; 
    ha = 15;
    c = "rgba(17, 17, 17, 1)";
  }
  $("#height_announcement").finish(); // clears the previous height announcement animation if it hasn't completed yet
  $("#height_announcement")
    .text( Math.floor( highestRedFlowerPct ) + "%" )
    .css({  
      top: 100-highestRedFlowerPct+ha + "%",
      left: pctFromXVal( HeightMarker.chrfx ) + "%",
      opacity: 1,
      color: c,
    })
    .animate({ 
      fontSize: "+="+fsi+"pt",
      top: "-="+td+"%",
      opacity: 1,
    }, dur, "linear")
    .animate({ 
      fontSize: "-="+fsi+"pt",
      top: "-="+td*2+"%",
      opacity: 0,    
    }, dur*2, "easeOutQuart", function() {  // (uses easing plugin)
      //callback resets original values
      $("#height_announcement").css({
        fontSize: "10pt",
      }); 
    }
  );
}

///fades out milestone announcements in and out
function fadeMilestoneInOut( idString ) {
  readyForNextMilestoneAnnouncement = false;
  $(idString)
    .css( "visibility", "visible" )
    .animate({ opacity: 1 }, 2000)
    .animate({ opacity: 1 }, 5000)
    .animate({ opacity: 0 }, 1000, function(){
      readyForNextMilestoneAnnouncement = true;
    });
}

///renders milestone announcements 
function renderMilestones() {
  if ( allDemosHaveRun && readyForNextMilestoneAnnouncement ) {
    if ( !milestoneFirstRedHasBeenRun && highestRedFlowerPct > 0 ) {
      fadeMilestoneInOut("#milestone_first_red_flower");
      milestoneFirstRedHasBeenRun = true;
    } else if ( !milestoneThirdHasBeenRun && highestRedFlowerPct >= 34 ) {
      fadeMilestoneInOut("#milestone_third");
      milestoneThirdHasBeenRun = true;          
    } else if ( !milestoneHalfHasBeenRun && highestRedFlowerPct >= 50 ) {
      fadeMilestoneInOut("#milestone_half");
      milestoneHalfHasBeenRun = true;          
    } else if ( !milestoneTwoThirdsHasBeenRun && highestRedFlowerPct >= 67 ) {
      fadeMilestoneInOut("#milestone_two_thirds");
      milestoneTwoThirdsHasBeenRun = true;          
    } else if ( !milestone90HasBeenRun && highestRedFlowerPct >= 90 ) {
      fadeMilestoneInOut("#milestone_90");
      milestone90HasBeenRun = true;          
    } 
  }
}

///renders markers that track the highest red flower height so far
function renderHeightMarker() {
  var hrfy = canvas.height - yValFromPct( highestRedFlowerPct );  // highest red flower y value currently
  var chmp = 100-pctFromYVal(HeightMarker.y);  // current height marker percentage
  if ( Math.floor( highestRedFlowerPct ) > Math.floor(chmp) ) {   // initializes animations if new highest red flower
    HeightMarker.y = hrfy;  // y value
    HeightMarker.baa = true;  // bounce animation active
    HeightMarker.bat = 0;  // bounce animation time elapsed
    HeightMarker.laa = true;  // line animation active
    HeightMarker.lat = 0;  // line animation time elapsed
    $("#height_number").text( Math.floor( highestRedFlowerPct ) );
    renderHeightAnnouncement();
  }
  //new highest height marker bounce animation (size expansion & contraction)
  if ( HeightMarker.baa ) {  
    HeightMarker.bat++;
    var a = -0.12;  // corresponds to animation duration ( higher value is longer duration; 0 is infinite)
    var b = 2;  // extent of expansion ( higher value is greater expansion )
    var x = HeightMarker.bat; 
    var y = a*Math.pow(x,2) + b*x;  // current marker expansion extent (quadratic formula; y = ax^2 + bx + c)
    HeightMarker.w = canvas.width*0.025 + y;
    if ( y <= 0 ) { HeightMarker.baa = false; HeightMarker.bat = 0; }
  }
  //new highest height line animation
  if ( HeightMarker.laa ) {  
    HeightMarker.lat++;
    var lad = 40;  // line animation duration
    var o = 1 - HeightMarker.lat/lad;  // opacity
    ctx.beginPath();
    ctx.lineWidth = 2;
    var lGrad = ctx.createLinearGradient( HeightMarker.chrfx-canvas.width, HeightMarker.y, HeightMarker.chrfx+canvas.width, HeightMarker.y );
    lGrad.addColorStop("0", "rgba( 161, 0, 0, 0 )");
    lGrad.addColorStop("0.4", "rgba( 161, 0, 0, " + 0.3*o + ")");
    lGrad.addColorStop("0.5", "rgba( 161, 0, 0, " + 1*o + ")");
    lGrad.addColorStop("0.6", "rgba( 161, 0, 0, " +0.3*o + ")");
    lGrad.addColorStop("1", "rgba( 161, 0, 0, 0 )");
    ctx.strokeStyle = lGrad;
    ctx.moveTo( HeightMarker.chrfx-canvas.width, HeightMarker.y );
    ctx.lineTo( HeightMarker.chrfx+canvas.width, HeightMarker.y );
    ctx.stroke();
    if ( HeightMarker.lat > lad ) { HeightMarker.laa = false; HeightMarker.lat = 0; }
  }
  //draws marker
  if ( highestRedFlowerPct > 0 ) {  
    ctx.beginPath();  // top triangle
    ctx.fillStyle = "#D32100";
    ctx.moveTo( canvas.width, HeightMarker.y );  
    ctx.lineTo( canvas.width, HeightMarker.y - HeightMarker.h/2 ); 
    ctx.lineTo( canvas.width-HeightMarker.w, HeightMarker.y ); 
    ctx.fill();  
    ctx.beginPath();  // bottom triangle
    ctx.fillStyle = "#A10000";
    ctx.moveTo( canvas.width, HeightMarker.y );  
    ctx.lineTo( canvas.width, HeightMarker.y + HeightMarker.h/2 ); 
    ctx.lineTo( canvas.width-HeightMarker.w, HeightMarker.y ); 
    ctx.fill();
  }
}

///renders pods
function renderPods( flower ) {
  var f = flower;
  var opacity = f.podOpacity <= f.parentPlant.opacity ? f.podOpacity : f.parentPlant.opacity; 
  ctx.lineJoin = "round";
  ctx.lineCap = "round";
  ctx.lineWidth = 2.5;
  ctx.fillStyle = "rgba("+f.clOv.r+","+f.clOv.g+","+f.clOv.b+","+opacity+")";  // dark green
  ctx.strokeStyle = "rgba("+f.clO.r+","+f.clO.g+","+f.clO.b+","+opacity+")";  // dark brown
  ctx.beginPath();
  ctx.moveTo(f.ptBL.cx, f.ptBL.cy);
  Tl.arcFromTo( f.ptBL, f.ptHoL, 0.1 );
  Tl.arcFromTo( f.ptHoL, f.ptPodTipL, 0.07 );
  if ( flower.podOpenRatio > 0 ) {
    var hp = spanMidPoint( f.spHbM );  // hinge point
    ctx.lineTo( hp.x, hp.y );
    ctx.lineTo( f.ptPodTipR.cx, f.ptPodTipR .cy );
  }
  Tl.arcFromTo( f.ptPodTipR, f.ptHoR, 0.1 );
  Tl.arcFromTo( f.ptHoR, f.ptBR, 0.07 );
  ctx.stroke();
  ctx.fill();
}











///////// SEASONS HANDLER /////////



///trackers
var currentYear = 1;
var yearTime = 0;
var currentSeason;
var currentGreatestMaxSegment;

///season lengths
var spL = 1000;  // spring length
var suL;  // summer length (updated ever year in trackSeasons() based on max plant segment count)
var faL = 200;  // fall length
var wiL = 300;  // winter length

///background gradient colors
var BgG = {
  sp: {  // spring
    cs1: { r: 244, g: 244, b: 244, a: 1 },  // color stop 1 ... 
    cs2: { r: 242, g: 247, b: 250, a: 1 }, 
    cs3: { r: 240, g: 250, b: 255, a: 1 }, 
    cs4: { r: 167, g: 223, b: 255, a: 1 }
  },
  su: {  // summer
    cs1: { r: 251, g: 252, b: 244, a: 1 }, 
    cs2: { r: 128, g: 209, b: 255, a: 1 }, 
    cs3: { r: 112, g: 203, b: 255, a: 1 }, 
    cs4: { r: 167, g: 223, b: 255, a: 1 }
  },
  fa: {  // fall
    cs1: { r: 248, g: 232, b: 209, a: 1 }, 
    cs2: { r: 254, g: 248, b: 240, a: 1 }, 
    cs3: { r: 235, g: 247, b: 255, a: 1 }, 
    cs4: { r: 245, g: 250, b: 252, a: 1 }
  },
  wi: {  // winter
    cs1: { r: 29,  g: 25,  b: 20,  a: 1 }, 
    cs2: { r: 114, g: 105, b: 97,  a: 1 }, 
    cs3: { r: 163, g: 157, b: 150, a: 1 }, 
    cs4: { r: 180, g: 179, b: 179, a: 1 }
  }
};

///current and previous season background color stop collection objects
var csbg = BgG.sp;  // current season background
var psbg = BgG.sp;  // previous season background

///current color stop rgba objects
var ccs1 = psbg.cs1;  // current color stop 1
var ccs2 = psbg.cs2;  // current color stop 2
var ccs3 = psbg.cs3;  // current color stop 3
var ccs4 = psbg.cs4;  // current color stop 4


///tracks seasons
function trackSeasons() {
  yearTime++;
  //spring
  if ( yearTime === 1 ) { 
    currentSeason = "Spring"; photosynthesisRatio = 1; livEnExp = 0.75;
    renderYearAnnouncement(); renderSeasonAnnouncement(); 
  //summer
  } else if ( yearTime === spL+1 ) {
    currentSeason = "Summer"; photosynthesisRatio = 1; livEnExp = 1;
    if ( currentYear === 1 ) {  // sets first year summer length to ensure enough time for demo animation
      suL = 800;
    } else { // adjusts summer length based on tallest plant's height
      suL = 85*currentGreatestMaxSegment() > 300 ? 85*currentGreatestMaxSegment() : 300;
    }
    scaleEliminationCursor();  // scales elimination cursor based on avg plant base width; 
    renderSeasonAnnouncement();
  //fall
  } else if ( yearTime === spL+suL+1 ) {
    currentSeason = "Fall"; photosynthesisRatio = 0; livEnExp = 10;
    renderSeasonAnnouncement();
  //winter
  } else if ( yearTime === spL+suL+faL+1 ) {
    currentSeason = "Winter"; photosynthesisRatio = 0; livEnExp = 20;
    renderSeasonAnnouncement();
  }
  if ( yearTime === spL+suL+faL+wiL ) {
    currentYear++;
    yearTime = 0;
  }
}

/// gets greatest of all current plants' maximum total segment count
function currentGreatestMaxSegment() {
  cgms = 0;  // current greatest max segments
  for ( i=0; i<plants.length; i++) {
    var p = plants[i];
    if (p.isAlive ) {
      cgms = p.maxTotalSegments > cgms ? p.maxTotalSegments : cgms;
    }
  }
  return cgms;
}

///renders seasons meter UI
function updateSeasonPieChart() {
  var dateDeg;  // degree corresponding to time of year on meter
  switch( currentSeason ) {  // marker position, calibrated different season lengths to uniform season arcs on meter
    case "Spring": dateDeg = yearTime*360 / spL; break;
    case "Summer": dateDeg = (yearTime-spL) * 360 / suL; break;
    case "Fall": dateDeg = (yearTime-spL-suL) * 360 / faL; break;
    case "Winter": dateDeg = (yearTime-spL-suL-faL) * 360 / wiL;
  }
  var pieValueAsDegree = dateDeg;
  var pieCircumference = 2*Math.PI*25;  // (svg circle radius is 25)
  var pieValue = pieCircumference*(pieValueAsDegree/360);
  document.querySelector("#pie_circle_left").style.strokeDasharray = pieValue + " " + pieCircumference;
  document.querySelector("#pie_circle_right").style.strokeDasharray = pieValue + " " + pieCircumference;
}

///renders background
function renderBackground() {
  switch( currentSeason ) {  // updates current and previous season 
    case "Spring": csbg = BgG.sp; psbg = BgG.wi; break;
    case "Summer": csbg = BgG.su; psbg = BgG.sp; break;
    case "Fall": csbg = BgG.fa; psbg = BgG.su; break;
    case "Winter": csbg = BgG.wi; psbg = BgG.fa; break;
  }
  if ( currentYear === 1 && currentSeason === "Spring") {
    csbg = BgG.sp; psbg = BgG.sp;  // starts game off with full spring background, not in mid-transition from winter
  }
  if ( gameHasBegun ) {
    ccs1 = Tl.rgbaCs( psbg.cs1, csbg.cs1, ccs1, 200/renderFactor );  // current color stop redshift 
    ccs2 = Tl.rgbaCs( psbg.cs2, csbg.cs2, ccs2, 200/renderFactor );  // current color stop greenshift
    ccs3 = Tl.rgbaCs( psbg.cs3, csbg.cs3, ccs3, 200/renderFactor );  // current color stop blueshift
    ccs4 = Tl.rgbaCs( psbg.cs4, csbg.cs4, ccs4, 200/renderFactor );  // current color stop alphashift
  } 
  var grd=ctx.createLinearGradient( 0, 0, 0, canvas.height );
  grd.addColorStop( 0,
    "rgba("+ Math.round(ccs1.r)+","+
             Math.round(ccs1.g)+","+
             Math.round(ccs1.b)+","+
             Math.round(ccs1.a)+")");
  grd.addColorStop( 0.4,
    "rgba("+ Math.round(ccs2.r)+","+
             Math.round(ccs2.g)+","+
             Math.round(ccs2.b)+","+
             Math.round(ccs2.a)+")");
  grd.addColorStop( 0.6,
    "rgba("+ Math.round(ccs3.r)+","+
             Math.round(ccs3.g)+","+
             Math.round(ccs3.b)+","+
             Math.round(ccs3.a)+")");
  grd.addColorStop( 1,
    "rgba("+ Math.round(ccs4.r)+","+
             Math.round(ccs4.g)+","+
             Math.round(ccs4.b)+","+
             Math.round(ccs4.a)+")");
  ctx.fillStyle=grd;
  ctx.fillRect(0, 0, canvas.width, canvas.height);
}

///renders year change announcements 
function renderYearAnnouncement() {
  var fsi = 1.5;  // font size increase
  var lsi = 1.5;  // letter spacing increase
  var om = 1;  // opacity maximum
  var dur = 1800;  // duration (of each animation segment)
  var del = 0;  // delay  
  if ( currentYear === 1 ) { del = 1000; }
  $("#year_announcement")
    .text("YEAR "+currentYear.toString().replace(/0/g,"O") )  // replace gets rid of Nunito font dotted zero
    .delay(del)
    .animate({ 
      fontSize: "+="+fsi+"pt", 
      letterSpacing: "+="+lsi+"pt",
      opacity: om*0.5, 
    }, dur, "linear" )
    .animate({ 
      fontSize: "+="+fsi+"pt", 
      letterSpacing: "+="+lsi+"pt",
      opacity: om,
    }, dur, "linear" )
    .animate({ 
      fontSize: "+="+fsi+"pt", 
      letterSpacing: "+="+lsi+"pt",
      opacity: 0, 
    }, dur, "linear", function() {
      //callback resets original values
      $("#year_announcement").css({
        fontSize: "35pt",
        letterSpacing: "2.5pt"
      });
    }
  );
}

///renders new season announcement at change of seasons
function renderSeasonAnnouncement() {
  var fsi = 1.2;  // font size increase
  var lsi = 0.5;  // letter spacing increase
  var om = 1;  // opacity maximum
  var dur = 1200;  // duration (of each animation segment)
  var del = 0;  // delay
  if ( currentYear === 1 && yearTime === 1 ) { del = 5000; } else if ( yearTime === 1 ) { del = 4000; }
  $("#season_announcement").finish(); // clears the previous season announcement animation if it hasn't completed yet
  $("#season_announcement")
    .text(currentSeason.toUpperCase())
    .delay(del)
    .animate({ 
      fontSize: "+="+fsi+"pt", 
      letterSpacing: "+="+lsi+"pt",
      opacity: om, 
    }, dur, "linear" )
    .animate({ 
      fontSize: "+="+fsi+"pt", 
      letterSpacing: "+="+lsi+"pt",
    }, dur, "linear" )
    .animate({ 
      fontSize: "+="+fsi+"pt", 
      letterSpacing: "+="+lsi+"pt",
      opacity: 0, 
    }, dur, "linear", function() {
      $("#season_announcement").css({  // resets original values
        fontSize: "16pt",
        letterSpacing: "1.25pt"
      }); 
      if ( currentSeason === "Spring" && currentYear === 1 ) readyForEliminationDemo = true;
      if ( currentSeason === "Summer" && currentYear === 1 ) readyForChangeDemo = true;
    });
}











///////// GENETICS HANDLER /////////



/////---GENES---/////


///genome (an object collection of all plant genes)
var Genome = {
  maxTotalSegments:   { initialValue: function(){ return Tl.rib(4,12); },
                        mutationParameter: { range: 6, min: 2, max: null }, expressionType: "complete" },
  maxSegmentWidth:    { initialValue: function(){ return Tl.rfb(8,12); },
                        mutationParameter: { range: 4, min: 8, max: null }, expressionType: "partial" },
  stalkStrength:      { initialValue: function(){ return Tl.rfb(0.7,0.8); },
                        mutationParameter: { range: 0.2, min: 0.7, max: 1 }, expressionType: "partial" },
  firstLeafSegment:   { initialValue: function(){ return Tl.rib(2,3); },
                        mutationParameter: { range: 4, min: 2, max: null }, expressionType: "complete" },
  leafFrequency:      { initialValue: function(){ return Tl.rib(2,3); },
                        mutationParameter: { range: 2, min: 2, max: null }, expressionType: "complete" },
  maxLeafLength:      { initialValue: function(){ return Tl.rfb(4,7); },
                        mutationParameter: { range: 3, min: 4, max: null}, expressionType: "partial" },
  flowerHue:          { initialValue: function(){ return Tl.rib(0,260); },  // (corrected for greens in Plant())
                        mutationParameter: { range: 50, min: 0, max: 260 }, expressionType: "complete" },
  flowerLightness:    { initialValue: function(){ return Tl.rib(30,75); },  // (corrected for offwhites in Plant())   
                        mutationParameter: { range: 30, min: 30, max: 75 }, expressionType: "complete" }
};




/////---OBJECT CONSTRUCTORS---/////


///allele object constructor (trait)
function Allele( value, dominanceIndex ) {
  this.value = value;
  this.dominanceIndex = dominanceIndex;
}

///gene locus object constructor (allele pair)
function Gene( allele1, allele2, mutationParameter, expressionType ) {
  this.allele1 = allele1;
  this.allele2 = allele2;
  this.mutationParameter = mutationParameter;  // (as object: { range: <range>, min: <min>, max: <max> } )
  this.expressionType = expressionType;  // (can be "complete", "co", or "partial")
}

///genotype object constructor (entire genotype contained on a single autosome)
function Genotype( genome ) {  // genome as object collection of genes as { traitName: <geneObject>, ... }
  for ( var gene in genome ) {
    this[gene] = genome[gene];
  } 
}

///phenotype (collection of expressed traits) object constructor
function Phenotype( genotype ) {  // object collection of genes as { traitName: <value>, ... }
  for ( var gene in genotype ) {
    if ( genotype[gene].expressionType === "complete" ) {  // expresses dominant allele value only (1,2 -> 2)
      var dominanceDifference = genotype[gene].allele1.dominanceIndex - genotype[gene].allele2.dominanceIndex;
      this[gene+"Value"] = dominanceDifference >= 0 ? genotype[gene].allele1.value : genotype[gene].allele2.value;
    } else if ( genotype[gene].expressionType === "partial" ) {  // expresses average of allele values (1,2 -> 1.5)
      this[gene+"Value"] = ( genotype[gene].allele1.value + genotype[gene].allele2.value ) / 2;
    } else if ( genotype[gene].expressionType === "co" ) {  // expresses combination of allele values (1,2 -> 1&2)
      //(handle case by case when/if need arises...)
    }
  }
}




/////---FUNCTIONS---/////


///creates a new gene ( with random allele dominance indexes )
function createGene( value, mutationParameter, expressionType ) {
  var dominanceIndex = Tl.rfb(0,1);
  return new Gene( new Allele( value, dominanceIndex ),
                   new Allele( value, dominanceIndex ),
                   mutationParameter,
                   expressionType );
}

function mutate( geneName, alelleValue ) {
  var ra = Genome[geneName].mutationParameter.range;  // range
  var mn = Genome[geneName].mutationParameter.min;  // min
  var mx = Genome[geneName].mutationParameter.max;  // max
  var et = Genome[geneName].expressionType;  // expression type
  var mutatedAlelleVal;
  if (et === "complete") {
    mutatedAlelleVal = Tl.rib( alelleValue-ra/2, alelleValue+ra/2 );
    if ( mutatedAlelleVal >= mn && ( mx === null || mutatedAlelleVal <= mx ) ) { alelleValue = mutatedAlelleVal; }
  } else if ( et === "partial" || et === "co") {
    mutatedAlelleVal = Tl.rfb( alelleValue-ra/2, alelleValue+ra/2 );
    if ( mutatedAlelleVal >= mn && ( mx === null || mutatedAlelleVal <= mx ) ) { alelleValue = mutatedAlelleVal; }
  }
  return alelleValue;
}

///performs meiosis (creates new child genotype from parent genotypes)
function meiosis( parentGenotype1, parentGenotype2 ) {
  var geneCollection = {};
  for ( var geneName in Genome ) {  // randomly selects one allele per gene from each parent genotype
    var parent1Allele = Tl.rib(1,2) === 1 ? parentGenotype1[geneName].allele1 : parentGenotype1[geneName].allele2;
    var parent2Allele = Tl.rib(1,2) === 1 ? parentGenotype2[geneName].allele1 : parentGenotype2[geneName].allele2;
    var newAllele1 = new Allele( parent1Allele.value, parent1Allele.dominanceIndex );
    var newAllele2 = new Allele( parent2Allele.value, parent2Allele.dominanceIndex );
    if ( Tl.rib( 1, mutationRate ) === 1 ) {  // handle mutations
      if ( Tl.rib(1,2) === 1 ) {
        newAllele1.value = mutate( geneName, newAllele1.value );
      } else {
        newAllele2.value = mutate( geneName, newAllele2.value );
      }
    }
    var newMutationParameter = parentGenotype1[geneName].mutationParameter;
    var newExpressionType = parentGenotype1[geneName].expressionType;
    geneCollection[geneName] = new Gene( newAllele1, newAllele2, newMutationParameter, newExpressionType );
  }
  var childGenotype = new Genotype( geneCollection );
  return childGenotype;
}




/////---GENOTYPE GENERATORS---/////


///random genotype
function generateRandomNewPlantGenotype() {
  var newGenotype = {};
  for ( var geneName in Genome ) {
    newGenotype[geneName] = createGene( Genome[geneName].initialValue(), 
                                        Genome[geneName].mutationParameter, 
                                        Genome[geneName].expressionType );
  }
  return newGenotype;
}

///random red flower plant genotype
function generateRandomRedFlowerPlantGenotype() {
  var redHue = Tl.rib(1,2) === 1 ? Tl.rib(0,5) : Tl.rib(255, 260);
  var newGenotype = {};
  newGenotype.maxTotalSegments = createGene( Tl.rib(4,12), { range: 6, min: 2, max: null }, "complete" );
  newGenotype.maxSegmentWidth = createGene( Tl.rfb(8,12), { range: 4, min: 8, max: null }, "partial" );
  newGenotype.stalkStrength = createGene( Tl.rfb(0.7,0.8), { range: 0.2, min: 0.7, max: 1 }, "partial" );
  newGenotype.firstLeafSegment = createGene( Tl.rib(2,3), { range: 4, min: 2, max: null }, "complete" );
  newGenotype.leafFrequency = createGene( Tl.rib(2,3), { range: 2, min: 2, max: null }, "complete" );
  newGenotype.maxLeafLength = createGene( Tl.rfb(4,7), { range: 4, min: 0, max: null }, "partial" );
  newGenotype.flowerHue = createGene( redHue, { range: 50, min: 0, max: 260 }, "complete" );
  newGenotype.flowerLightness = createGene( Tl.rib(30,40), { range: 30, min: 30, max: 75 }, "complete" );
  return newGenotype;
}

///tiny white flower plant genotype
function generateTinyWhiteFlowerPlantGenotype() {
  var newGenotype = {};
  newGenotype.maxTotalSegments = createGene( 2, { range: 6, min: 2, max: null }, "complete" );
  newGenotype.maxSegmentWidth = createGene( 8, { range: 4, min: 8, max: null }, "partial" );
  newGenotype.stalkStrength = createGene( 0.8, { range: 0.2, min: 0.7, max: 1 }, "partial" );
  newGenotype.firstLeafSegment = createGene( 2, { range: 4, min: 2, max: null }, "complete" );
  newGenotype.leafFrequency = createGene( 2, { range: 2, min: 2, max: null }, "complete" );
  newGenotype.maxLeafLength = createGene( 4, { range: 4, min: 0, max: null }, "partial" );
  newGenotype.flowerHue = createGene( Tl.rib(0,260), { range: 50, min: 0, max: 260 }, "complete" );
  newGenotype.flowerLightness = createGene( 75, { range: 30, min: 30, max: 75 }, "complete" );
  return newGenotype;
}

///smallest plant genotype possible within size minimums
function generateSmallPlantGenotype() {
  var newGenotype = {};
  newGenotype.maxTotalSegments = createGene( 2, { range: 6, min: 2, max: null }, "complete" );
  newGenotype.maxSegmentWidth = createGene( 8, { range: 4, min: 8, max: null }, "partial" );
  newGenotype.stalkStrength = createGene( 0.8, { range: 0.2, min: 0.7, max: 1 }, "partial" );
  newGenotype.firstLeafSegment = createGene( 2, { range: 4, min: 2, max: null }, "complete" );
  newGenotype.leafFrequency = createGene( 2, { range: 2, min: 2, max: null }, "complete" );
  newGenotype.maxLeafLength = createGene( 4, { range: 4, min: 0, max: null }, "partial" );
  newGenotype.flowerHue = createGene( Tl.rib(0,260), { range: 50, min: 0, max: 260 }, "complete" );
  newGenotype.flowerLightness = createGene( Tl.rib(30,75), { range: 30, min: 30, max: 75 }, "complete" );
  return newGenotype;
}

///mid values of initial size ranges
function generateMediumPlantGenotype() {
  var newGenotype = {};
  newGenotype.maxTotalSegments = createGene( 7, { range: 6, min: 2, max: null }, "complete" );
  newGenotype.maxSegmentWidth = createGene( 10, { range: 4, min: 8, max: null }, "partial" );
  newGenotype.stalkStrength = createGene( 0.8, { range: 0.2, min: 0.7, max: 1 }, "partial" );
  newGenotype.firstLeafSegment = createGene( 2, { range: 4, min: 2, max: null }, "complete" );
  newGenotype.leafFrequency = createGene( 2, { range: 2, min: 2, max: null }, "complete" );
  newGenotype.maxLeafLength = createGene( 5.5, { range: 4, min: 0, max: null }, "partial" );
  newGenotype.flowerHue = createGene( Tl.rib(0,260), { range: 50, min: 0, max: 260 }, "complete" );
  newGenotype.flowerLightness = createGene( Tl.rib(30,75), { range: 30, min: 30, max: 75 }, "complete" );
  return newGenotype;
}

///largest plant genotype possible within initial size ranges
function generateLargePlantGenotype() {
  var newGenotype = {};
  newGenotype.maxTotalSegments = createGene( 12, { range: 6, min: 2, max: null }, "complete" );
  newGenotype.maxSegmentWidth = createGene( 12, { range: 4, min: 8, max: null }, "partial" );
  newGenotype.stalkStrength = createGene( 0.8, { range: 0.2, min: 0.7, max: 1 }, "partial" );
  newGenotype.firstLeafSegment = createGene( 3, { range: 4, min: 2, max: null }, "complete" );
  newGenotype.leafFrequency = createGene( 2, { range: 2, min: 2, max: null }, "complete" );
  newGenotype.maxLeafLength = createGene( 7, { range: 4, min: 0, max: null }, "partial" );
  newGenotype.flowerHue = createGene( Tl.rib(0,260), { range: 50, min: 0, max: 260 }, "complete" );
  newGenotype.flowerLightness = createGene( Tl.rib(30,75), { range: 30, min: 30, max: 75 }, "complete" );
  return newGenotype;
}

///tall plant genotype
function generateTallPlantGenotype( stalkStrength ) {
  var newGenotype = {};
  newGenotype.maxTotalSegments = createGene( 25, { range: 6, min: 2, max: null }, "complete" );
  newGenotype.maxSegmentWidth = createGene( 10, { range: 4, min: 8, max: null }, "partial" );
  newGenotype.stalkStrength = createGene( stalkStrength, { range: 0.2, min: 0.7, max: 1 }, "partial" );
  newGenotype.firstLeafSegment = createGene( 2, { range: 4, min: 2, max: null }, "complete" );
  newGenotype.leafFrequency = createGene( 2, { range: 2, min: 2, max: null }, "complete" );
  newGenotype.maxLeafLength = createGene( 5, { range: 4, min: 0, max: null }, "partial" );
  newGenotype.flowerHue = createGene( Tl.rib(0,260), { range: 50, min: 0, max: 260 }, "complete" );
  newGenotype.flowerLightness = createGene( Tl.rib(30,75), { range: 30, min: 30, max: 75 }, "complete" );
  return newGenotype;
}

///huge plant genotype
function generateHugePlantGenotype() {
  var newGenotype = {};
  newGenotype.maxTotalSegments = createGene( 10, { range: 6, min: 2, max: null }, "complete" );
  newGenotype.maxSegmentWidth = createGene( 30, { range: 4, min: 8, max: null }, "partial" );
  newGenotype.stalkStrength = createGene( 0.8, { range: 0.2, min: 0.7, max: 1 }, "partial" );
  newGenotype.firstLeafSegment = createGene( 2, { range: 4, min: 2, max: null }, "complete" );
  newGenotype.leafFrequency = createGene( 3, { range: 4, min: 1, max: null }, "complete" );
  newGenotype.maxLeafLength = createGene( 9, { range: 4, min: 0, max: null }, "partial" );
  newGenotype.flowerHue = createGene( Tl.rib(0,260), { range: 50, min: 0, max: 260 }, "complete" );
  newGenotype.flowerLightness = createGene( Tl.rib(30,75), { range: 30, min: 30, max: 75 }, "complete" );
  return newGenotype;
}

///huge red plant genotype
function generateHugeRedPlantGenotype() {
  var redHue = Tl.rib(1,2) === 1 ? Tl.rib(0,5) : Tl.rib(255, 260);
  var newGenotype = {};
  newGenotype.maxTotalSegments = createGene( 10, { range: 6, min: 2, max: null }, "complete" );
  newGenotype.maxSegmentWidth = createGene( 30, { range: 4, min: 8, max: null }, "partial" );
  newGenotype.stalkStrength = createGene( 0.8, { range: 0.2, min: 0.7, max: 1 }, "partial" );
  newGenotype.firstLeafSegment = createGene( 2, { range: 4, min: 2, max: null }, "complete" );
  newGenotype.leafFrequency = createGene( 3, { range: 4, min: 1, max: null }, "complete" );
  newGenotype.maxLeafLength = createGene( 9, { range: 4, min: 0, max: null }, "partial" );
  newGenotype.flowerHue = createGene( redHue, { range: 50, min: 0, max: 260 }, "complete" );
  newGenotype.flowerLightness = createGene( Tl.rib(30,50), { range: 30, min: 30, max: 75 }, "complete" );
  return newGenotype;
}










/////////////// PLANTS //////////////////



/////---INITIATION---/////


///settings
var renderFactor = 3;  // factor of verlet iterations (worldTime) when scenes are rendered (less is more frequent)
var useSunShades = false;  // (whether to place extendable sun shades)
var darkMode = true;  // UI dark mode (on by default)
var viewShadows = true;  // (shadow visibility)
var viewStalks = true;  // (stalk visibility) 
var viewLeaves = true;  // (leaf visibility)
var viewFlowers = true;  // (flower visibility)
var viewPods = true;  // (pod visibilty)
var viewRedFlowerIndicator = true;  // (red flower indicator animation visibility)
var viewPollenBursts = true;  // (pollen burst visibility)
var viewPollinatorLines = true;  // (pollination line visibility; i.e., pollen particals travelling between flowers)
var viewPollinationGlow = true;  // (pollination glow visibility)
var runPollinationAnimations = true;  // (whether to run pollination animations; overrides pollination views)
var allowSelfPollination = true;  // allows flowers to pollinate themselves
var pollinationFrequency = 5;  // (as average number of pollination events per open flower per length of summer)
var maxSeedsPerFlowerRatio = 0.334;  // max seeds per flower (as ratio of plant's max total segments)
var mutationRate = 5;  // (as meiosis events per mutation; higher is less frequent)
var restrictGrowthByEnergy = true;  // restricts plant growth by energy level (if false, plants grow freely)
var sunRayIntensity = 3;  // total energy units per sun ray per iteration
var photosynthesisRatio = 1;  // ratio of available sun energy stored by leaf when ray contacts it (varies by season)
var groEnExp = 0.2;  // growth energy expenditure rate (rate energy is expended for growth)
var livEnExp = 0.1;  // living energy expenditure rate (rate energy is expended for living)
var energyStoreFactor = 1000;  // (a plant's maximum storable energy units per segment)
var oldAgeMarker = 20000;  // (age after flower bloom when plant starts dying of old age, in worldtime units)
var oldAgeRate = 0.001;  // (additional energy reduction per iteration after plant reaches old age)
var unhealthyEnergyLevelRatio = 0.075;  // ratio of maximum energy when plant becomes unhealthy (starts yellowing)
var minBloomEnLevRatio = 0;  // min energy level ratio for flower to bloom 
var minPollEnLevRatio = 0;  // min energy level ratio for flower to pollinate or be pollinated 
var flowerFadeEnergyLevelRatio = -0.025;  // ratio of maximum energy when flower begins to fade
var polinatorPadFadeEnergyLevelRatio = -0.075;  // ratio of maximum energy when polinator pad begins to fade
var sickEnergyLevelRatio = -0.2;  // ratio of maximum energy when plant becomes sick (starts darkening)
var podOpenEnergyLevelRatio = -0.3;  // ratio of maximum energy when seed pod disperses seeds
var deathEnergyLevelRatio = -1;  // ratio of maximum energy when plant dies (fully darkened)
var collapseEnergyLevelRatio = -2;  // ratio of maximum energy when plant collapses

///trackers
var seeds = [], seedCount = 0;
var plants = [], plantCount = 0;
var sunRays = [], sunRayCount = 0;
var sunShadeHandles = [], sunShadeHandleCount = 0;
var sunShades = [], sunShadeCount = 0;
var shadows = [], shadowCount = 0;
var initialGeneValueAverages = {};
var highestRedFlowerPct = 0;
var gameHasBegun = false;  // (whether user has initiated game play)
var readyForEliminationDemo = false;  // (whether first year and spring announcement has completed)
var readyForChangeDemo = false;  // (whether first year and summer announcement has completed)
var eliminationDemoHasBegun = false;  // (whether instructional elimination demo has begun running)
var changeDemoHasBegun = false;  // (whether instructional mutation/recessive trait demo has begun running)
var allDemosHaveRun = false;
var gamePaused = false;  // (whether game is paused)



/////---OBJECTS---/////


///colors
var C = {
  //fills
  hdf: { r: 0, g: 100, b: 0, a: 1 },  // healthy dark fill color (dark green)
  hlf: { r: 0, g: 128, b: 0, a: 1 },  // healthy light fill color (green)
  yf: { r: 206, g: 171, b: 45, a: 1 },  // yellowed fill color (sickly yellow)
  df: { r: 94, g: 77, b: 21, a: 1 },  // dead fill color (dark brown)
  //outlines
  hol: { r: 42, g: 32, b: 0, a: 1 },  // healthy outline color (very dark brown)
  yol: { r: 107, g: 90, b: 31, a: 1 },  // yellowed outline color (slightly darker sickly yellow than leaf fill) 
  dol: { r: 42, g: 32, b: 0, a: 1 },  // dead outline color (very dark brown)
  //inner lines
  hil: { r: 0, g: 112, b: 0, a: 1 },  // healthy inner line color (slightly darker green than leaf fill)
  yil: { r: 107, g: 90, b: 31, a: 1 },  // yellowed inner line color (slightly darker sickly yellow than leaf fill) 
  dil: { r: 56, g: 47, b: 12, a: 1 },  // dead inner line color (slightly darker brown than leaf fill)
  //leaf shadows
  hls: { r: 0, g: 0, b: 0, a: 0.1 },  // healthy leaf shadow color
  yls: { r: 0, g: 0, b: 0, a: 0.05 },  // yellowed leaf shadow color
  dls: { r: 0, g: 0, b: 0, a: 0 },  // dead leaf shadow color
  //pollen pad
  pp: { r: 255, g: 217, b: 102, a: 1 },  // pollen pad color
  pl: { r: 255, g: 159, b: 41, a: 1 },  // pollination line color
  pg: { r: 255, g: 98, b: 41, a: 1 },  // pollen pad glow color ( temporary glow when polinated )
};

///seed constructor
function Seed( parentFlower, zygoteGenotype ) {
  this.id = seedCount;
  this.parentFlower = parentFlower;
  if ( parentFlower === null ) {
    this.sw = 14;  // seed width 
    this.p1 = addPt( Tl.rib(33,66), Tl.rib(5,25) );  // seed point 1 (placed in air for scattering at initiation)
    this.p2 = addPt( pctFromXVal( this.p1.cx + this.sw*1.6 ), pctFromYVal( this.p1.cy ) );  // seed point 2
    this.generation = 1;
  } else {
    this.sw = this.parentFlower.spHcH.l/2;  // seed width
    var p1 = spanMidPoint( this.parentFlower.spHbM );  // positions seed p1 at bottom of parent flower's hex
    this.p1 = addPt( pctFromXVal(p1.x), pctFromYVal(p1.y) );  // seed point 1
    var p2 = spanMidPoint( this.parentFlower.spHtM );  // positions seed p2 at top of parent flower's hex
    this.p2 = addPt( pctFromXVal(p2.x), pctFromYVal(p2.y) );  // seed point 2
    this.generation = this.parentFlower.generation + 1;
  }
  this.genotype = zygoteGenotype;  
  this.phenotype = new Phenotype( this.genotype );
  this.p1.width = this.sw*1; 
  this.p1.mass = 5;
  this.p2.width = this.sw*0.35; this.p2.mass = 5;
  this.sp = addSp( this.p1.id, this.p2.id );  // seed span
  this.sp.strength = 2;
  this.opacity = 1;
  this.planted = false;
  this.hasGerminated = false;
  this.resultingPlant = createPlant( this );
}

///plant constructor
function Plant( sourceSeed ) {
  this.sourceSeed = sourceSeed;
  this.sourceSeedHasBeenRemoved = false;
  this.id = plantCount;
  this.generation = sourceSeed.generation;
  this.germinationYear = currentYear;  // germination year
  this.age = 0;  // plant age in worldtime units 
  this.segments = []; this.segmentCount = 0;
  this.flowers = []; this.flowerCount = 0;
  this.xLocation = null;
  this.maxEnergyLevel = this.segmentCount * energyStoreFactor;
  this.hasFlowers = false;
  this.pollenPadColor = C.pp;  // pollen pad color
  this.isAlive = true;
  this.hasBeenEliminatedByPlayer = false;
  this.hasReachedOldAge = false;
  this.oldAgeReduction = 0;  // (energy reduction per plant iteration, when plant is dying of old age)
  this.hasCollapsed = false;
  this.isActive = true;  // (inactive plants are rendered but ignored by all other local plant & light iterations)
  this.hasDecomposed = false;  // decomposed plants are compressed to floor y-value and ready to be removed
  this.opacity = 1;
  this.hasBeenRemoved = false;
  //base segment (values assigned at source seed germination)
  this.xLocation = null;  // x value where plant is rooted to the ground
  this.ptB1 = null;  // base point 1
  this.ptB2 = null;  // base point 2
  this.spB = null;  // adds base span
  //genes
  this.genotype = this.sourceSeed.genotype;
  this.phenotype = this.sourceSeed.phenotype;
  var ph = this.phenotype;
  this.maxSegmentWidth = ph.maxSegmentWidthValue;  // maximum segment width (in pixels)
  this.maxTotalSegments = ph.maxTotalSegmentsValue;  // maximum total number of segments at maturity
  this.stalkStrength = ph.stalkStrengthValue;
  this.firstLeafSegment = ph.firstLeafSegmentValue;  // (segment on which first leaf set grows)
  this.leafFrequency = ph.leafFrequencyValue;  // (number of segments until next leaf set)
  this.maxLeafLength = this.maxSegmentWidth * ph.maxLeafLengthValue;  // maximum leaf length at maturity
  this.fh = ph.flowerHueValue; if ( this.fh > 65 ) { this.fh += 100; }  // flower hue (omits greens)
  this.fl = ph.flowerLightnessValue; if ( this.fl > 70 ) { this.fl += 25; }  // flower lightness
  //gene combinations
  this.flowerColor = { h: this.fh, l: this.fl };  // flower color ( hue, lightness)
  //non-gene qualities
  this.forwardGrowthRate = gravity * this.maxSegmentWidth*2;  // (rate of cross span increase per frame)
  this.outwardGrowthRate = this.forwardGrowthRate * Tl.rfb(0.18,0.22);  // (rate forward span widens / frame)
  this.leafGrowthRate = this.forwardGrowthRate * Tl.rfb(1.4,1.6);  // leaf growth rate
  this.leafArcHeight = Tl.rfb(0.3,0.4);  // arc height (as ratio of leaf length)
  this.maxFlowerBaseWidth = 1;  // max flower base width, in units of plant maxSegmentWidth
  this.flowerBudHeight = 1;  // bud height ( from hex top, in units of hex heights )
  //energy
  this.seedEnergy = this.maxTotalSegments*275;  // energy contained in seed
  this.energy = this.seedEnergy;  // energy (starts with seed energy at germination)
}

///plant stalk segment constructor
function Segment( plant, parentSegment, basePoint1, basePoint2 ) {
  this.plantId = plant.id;
  this.parentPlant = plant;
  this.id = plant.segmentCount;
  this.child = null;
  this.hasChild = false;
  this.parentSegment = parentSegment;
  this.isBaseSegment = false; if (this.parentSegment === null) { this.isBaseSegment = true; }
  this.hasLeaves = false;
  this.hasLeafScaffolding = false;
  //settings
  this.forwardGrowthRateVariation = Tl.rfb(0.97,1.03);  // for left & right span length variation
  this.mass = 1;  // mass of the segment stalk portion ( divided between the two extension points)
  //base points
  this.ptB1 = basePoint1;  // base point 1
  this.ptB2 = basePoint2;  // base point 2
  //extension points
  var originX = ( this.ptB1.cx + this.ptB2.cx ) / 2;  // center of base points x values
  var originY = ( this.ptB1.cy + this.ptB2.cy ) / 2;  // center of base points y values                    
  this.ptE1 = addPt( pctFromXVal( originX ) - 0.1, pctFromYVal( originY ) - 0.1 );  // extension point 1
  this.ptE2 = addPt( pctFromXVal( originX ) + 0.1, pctFromYVal( originY ) - 0.1 );  // extension point 2
  this.ptE1.mass = this.mass / 2;
  this.ptE2.mass = this.mass / 2;
  //spans
  this.spL = addSp( this.ptB1.id, this.ptE1.id );  // left span
  this.spR = addSp( this.ptB2.id, this.ptE2.id );  // right span
  this.spF = addSp( this.ptE1.id, this.ptE2.id );  // forward span
  this.spCd = addSp( this.ptE1.id, this.ptB2.id );  // downward (l to r) cross span
  this.spCu = addSp( this.ptB1.id, this.ptE2.id );  // upward (l to r) cross span
  if (!this.isBaseSegment) {
    this.spCdP = addSp( this.ptE1.id, this.parentSegment.ptB2.id ); // downward (l to r) cross span to parent
    this.spCuP = addSp( this.parentSegment.ptB1.id, this.ptE2.id ); // upward (l to r) cross span to parent
    this.spCdP.strength = plant.stalkStrength;
    this.spCuP.strength = plant.stalkStrength;
  }
  this.spL.strength = plant.stalkStrength;
  this.spR.strength = plant.stalkStrength;
  this.spF.strength = plant.stalkStrength;
  this.spCd.strength = plant.stalkStrength;
  this.spCu.strength = plant.stalkStrength;
  //skins
  this.skins = [];
  this.skins.push( addSk( [ this.ptE1.id, this.ptE2.id, this.ptB2.id, this.ptB1.id ], null ) );
  //leaves
  this.ptLf1 = null;  // leaf point 1 (leaf tip)
  this.ptLf2 = null;  // leaf point 2 (leaf tip)  
  this.spLf1 = null;  // leaf 1 Span
  this.spLf2 = null;  // leaf 2 Span
  //colors
  this.clS = C.hdf;  // stalk color (dark green when healthy)
  this.clO = C.hol;  // outline color (very dark brown when healthy)
  this.clI = C.hil;  // inner line color (slightly darker green than leaf fill when healthy) 
  this.clL = C.hlf;  // leaf color (green when healthy)
  this.clLS = C.hls;  // leaf shadow color (barely opaque black when healthy) 
}




/////---FUNCTIONS---/////


///// Instance Creators /////

///creates a new seed
function createSeed( parentFlower, zygoteGenotype ) {
  seedCount++;
  seeds.push( new Seed( parentFlower, zygoteGenotype ) );
  if ( parentFlower !== null ) { parentFlower.seeds.push( seeds[seeds.length-1] ); }
  return seeds[seeds.length-1];
}

///creates a new plant (while maintaining render ordering)
function createPlant( sourceSeed ) {
  plantCount++;
  if ( sourceSeed.parentFlower === null ) {  // if seed is initiating seed, adds new plant to end of the plants array
    plants.push( new Plant( sourceSeed ) ); 
    return plants[plants.length-1]; 
  } else {
    for ( var i=0; i<plants.length; i++) {  // if not initiating seed, adds new plant before parent in plants array
      if ( sourceSeed.parentFlower.plantId === plants[i].id ) { 
        plants.splice( i, 0, new Plant( sourceSeed ) ); 
        return plants[i];  
      }
    }
  }
}

///creates a new segment
function createSegment( plant, parentSegment, basePoint1, basePoint2 ) {
  plant.segmentCount++;
  plant.maxEnergyLevel = plant.segmentCount * energyStoreFactor;
  plant.segments.unshift( new Segment( plant, parentSegment, basePoint1, basePoint2 ) );
  if (parentSegment !== null) {
    parentSegment.child = plant.segments[0];
    parentSegment.hasChild = true;
  }
}

///removes a seed by id from seeds array
function removeSeed( seedId ) {
  for( var i=0; i<seeds.length; i++){ 
    if ( seeds[i].id === seedId ) { seeds.splice(i, 1); }
  }
}

///removes a plant by id from plants array
function removePlant( plantId ) {
  for( var i=0; i<plants.length; i++){ 
    if ( plants[i].id === plantId ) { plants.splice(i, 1); }
  }
}



//// Component Functions ////

///records initial gene value averages
function recordInitialGeneValueAverages() {
  for ( var gene in Genome ) {
    var alleleAvg = 0;
    for ( i=0; i<plants.length; i++ ) {
      var p = plants[i];
      alleleAvg += p.genotype[gene].allele1.value;
      alleleAvg += p.genotype[gene].allele2.value;      
    }
    alleleAvg = alleleAvg/(plants.length*2);
    initialGeneValueAverages[gene] = alleleAvg;
  }
}

///scatters seeds (for initiation)
function scatterSeed( seed ) {
  seed.p1.px += Tl.rfb(-5,5); seed.p1.py += Tl.rfb(-5,5);
  seed.p2.px += Tl.rfb(-5,5); seed.p2.py += Tl.rfb(-5,5);
}

///drops seeds (for releasing seed from pod)
function dropSeed( seed ) {
  seed.p2.px += Tl.rfb(-5,5);
}

///plants seed (secures its position to ground)
function plantSeed( seed ) {
  seed.p1.fixed = true; 
  seed.p2.materiality = "immaterial";
  var seedUpright = seed.p2.cx > seed.p1.cx-canvas.width*0.005 && seed.p2.cx < seed.p1.cx+canvas.width*0.005;
  var seedSunk = seed.p1.cy >= canvas.height;
  if ( seedUpright ) {
    seed.p2.fixed = true;
    seed.p1.materiality = "immaterial";
    if ( !seedSunk ) {
      seed.p1.cy += canvas.height*0.0005;
      seed.p2.cy += canvas.height*0.0005;
    } else {
      seed.planted = true;
    }
  }
}

///germinates seeds when ready (after it has been planted and spring has arrived)
function germinateSeedWhenReady( seed ) {
  var p1Stable = (canvas.height-seed.p1.width/2) - seed.p1.cy < 0.05;
  var p2Stable = (canvas.height-seed.p2.width/2) - seed.p2.cy < 0.05;
  if ( !seed.planted && p1Stable && p2Stable ) {
    plantSeed( seed );
  }
  if ( seed.planted && currentSeason === "Spring" ) {
    germinateSeed( seed );
  }
}

///germinates seed and establishes plant's base segment, setting growth in motion
function germinateSeed( seed ) {
  var plant = seed.resultingPlant;
  plant.xLocation = pctFromXVal( seed.p1.cx );
  plant.ptB1 = addPt( plant.xLocation - 0.1, 100 );  // base point 1
  plant.ptB2 = addPt( plant.xLocation + 0.1, 100 );  // base point 2
  plant.ptB1.fixed = plant.ptB2.fixed = true;  // fixes base points to ground
  plant.spB = addSp( plant.ptB1.id, plant.ptB2.id );  // adds base span
  createSegment( plant, null, plant.ptB1, plant.ptB2 );  // creates the base segment (with "null" parent)
  seed.hasGerminated = true;
  plant.germinationYear = currentYear;
}

///fades seed out then removes it
function hideAndRemoveSeed( seed ) {
  if ( seed.opacity > 0 ) {
    seed.opacity -= 0.001;
  } else {
    removePoint( seed.p1.id );
    removePoint( seed.p2.id );
    removeSpan( seed.sp.id );
    removeSeed( seed.id );
    seed.resultingPlant.sourceSeedHasBeenRemoved = true;
  } 
}

///lengthens segment spans for growth
function lengthenSegmentSpans( plant, segment ) {
  if (segment.isBaseSegment) {
    segment.ptB1.cx -= plant.outwardGrowthRate / 2;
    segment.ptB2.cx += plant.outwardGrowthRate / 2;
    plant.spB.l = distance( segment.ptB1, segment.ptB2 );
    segment.spCd.l = distance( segment.ptE1, segment.ptB2 ) + plant.forwardGrowthRate / 3;
    segment.spCu.l = segment.spCd.l;
  } else {
    segment.spCdP.l = distance( segment.ptE1, segment.parentSegment.ptB2 ) + plant.forwardGrowthRate;
    segment.spCuP.l = segment.spCdP.l * segment.forwardGrowthRateVariation;
    segment.spCd.l = distance( segment.ptE1, segment.ptB2 );
    segment.spCu.l = distance( segment.ptB1, segment.ptE2 );
  } 
  segment.spF.l += plant.outwardGrowthRate;
  segment.spL.l = distance( segment.ptB1, segment.ptE1 );
  segment.spR.l = distance( segment.ptB2, segment.ptE2 );
}

///checks whether a segment is ready to generate a child segment
function readyForChildSegment( plant, segment ) {
  var segmentForwardSpanReady = segment.spF.l > plant.maxSegmentWidth * 0.333;
  var segmentDoesNotHaveChild = !segment.hasChild;
  var plantIsNotFullyGrown = plant.segmentCount < plant.maxTotalSegments;
  return plantIsNotFullyGrown && segmentDoesNotHaveChild && segmentForwardSpanReady;
}

///checks whether a segment is ready to generate leaves segment
function plantReadyForLeaves( plant, segment ) {
  var segIsFirstLeafSeg = segment.id === plant.firstLeafSegment;
  var plantIsReadyForFirstLeaves = segment.id - plant.firstLeafSegment > 0;
  var plantIsReadyForNextLeaves = ( segment.id - plant.firstLeafSegment ) % plant.leafFrequency === 0;
  var segIsReadyToProduceLeaves = segment.spF.l > plant.maxSegmentWidth * 0.1;
  return segIsFirstLeafSeg || plantIsReadyForFirstLeaves && plantIsReadyForNextLeaves && segIsReadyToProduceLeaves;
}

///generates leaves when segment is ready
function generateLeavesWhenReady( plant, segment ) {
  var p = plant;
  var s = segment;
  if ( plantReadyForLeaves( plant, segment ) ) {
    var fsmp = spanMidPoint( s.spF );  // forward span mid point
    var pbsmp = midPoint( s.parentSegment.ptB1, s.parentSegment.ptB2 );  // parent base span mid point
    var xTip = fsmp.x + ( fsmp.x - pbsmp.x ) * 0.25;  // new leaf tip x location
    var yTip = fsmp.y + ( fsmp.y - pbsmp.y ) * 0.25;  // new leaf tip y location
    s.ptLf1 = addPt( pctFromXVal( xTip ), pctFromYVal( yTip ) );  // leaf 1 tip point (left)
    s.ptLf2 = addPt( pctFromXVal( xTip ), pctFromYVal( yTip ) );  // leaf 2 tip point (right)
    s.spLf1 = addSp( s.ptB1.id, s.ptLf1.id );  // leaf 1 span (left)
    s.spLf2 = addSp( s.ptB2.id, s.ptLf2.id );  // leaf 2 span (right)
    s.leafTipsTetherSpan = addSp( s.ptLf1.id, s.ptLf2.id );  // leaf tip tether span
    s.hasLeaves = true;
  }
}

///adds leaf scaffolding (so leaves stay more or less horizontal, depending on stalk angle)
function addLeafScaffolding( plant, segment ) {
  var p = plant;
  var s = segment;
  removeSpan(s.leafTipsTetherSpan.id);  // removes leaf tips tether
  s.ptLf1.cx -= gravity * 100;  // leaf-unfold booster left
  s.ptLf2.cx += gravity * 100;  // leaf-unfold booster right
  //scaffolding points, leaf 1
  var x = s.ptE1.cx + ( s.ptE1.cx - s.ptE2.cx ) * 0.5;
  var y = s.ptE1.cy + ( s.ptE1.cy - s.ptE2.cy ) * 0.5;
  s.ptLf1ScA = addPt( pctFromXVal( x ), pctFromXVal( y ), "immaterial" ); s.ptLf1ScA.mass = 0;
  x = ( s.ptLf1.cx + s.ptLf1ScA.cx ) / 2 ;
  y = ( s.ptLf1.cy + s.ptLf1ScA.cy ) / 2 ;
  s.ptLf1ScB = addPt( pctFromXVal( x ), pctFromXVal( y ), "immaterial" ); s.ptLf1ScB.mass = 0;
  //scaffolding points, leaf 2
  x = s.ptE2.cx + ( s.ptE2.cx - s.ptE1.cx ) * 0.5;
  y = s.ptE2.cy + ( s.ptE2.cy - s.ptE1.cy ) * 0.5;
  s.ptLf2ScA = addPt( pctFromXVal( x ), pctFromXVal( y ), "immaterial" ); s.ptLf2ScA.mass = 0;
  x = ( s.ptLf2.cx + s.ptLf2ScA.cx ) / 2 ;
  y = ( s.ptLf2.cy + s.ptLf2ScA.cy ) / 2 ;
  s.ptLf2ScB = addPt( pctFromXVal( x ), pctFromXVal( y ), "immaterial" ); s.ptLf2ScB.mass = 0;
  //scaffolding spans, leaf 1
  s.spLf1ScA = addSp( s.ptE1.id, s.ptLf1ScA.id, "hidden" );
  s.spLf1ScB = addSp( s.ptB1.id, s.ptLf1ScA.id, "hidden" ); 
  s.spLf1ScC = addSp( s.ptLf1ScA.id, s.ptLf1ScB.id, "hidden" ); 
  s.spLf1ScD = addSp( s.ptLf1ScB.id, s.ptLf1.id, "hidden" ); 
  //scaffolding spans, leaf 2
  s.spLf2ScA = addSp( s.ptE2.id, s.ptLf2ScA.id, "hidden" ); 
  s.spLf2ScB = addSp( s.ptB2.id, s.ptLf2ScA.id, "hidden" ); 
  s.spLf2ScC = addSp( s.ptLf2ScA.id, s.ptLf2ScB.id, "hidden" ); 
  s.spLf2ScD = addSp( s.ptLf2ScB.id, s.ptLf2.id, "hidden" );
  s.hasLeafScaffolding = true;
}

///grows leaves
function growLeaves( plant, segment ) {
  var p = plant;
  var s = segment;
  s.spLf1.l = s.spLf2.l += p.leafGrowthRate;  // extend leaves
  if ( s.spF.l > p.maxSegmentWidth*0.5 && !s.hasLeafScaffolding ) {
    addLeafScaffolding( plant, segment );  // add scaffolding
  } else if ( s.hasLeafScaffolding ) {  // extend scaffolding
    s.spLf1ScA.l += p.leafGrowthRate * 1.25;
    s.spLf1ScB.l += p.leafGrowthRate * 1.5;
    s.spLf1ScC.l += p.leafGrowthRate * 0.06;
    s.spLf1ScD.l += p.leafGrowthRate * 0.06;
    s.spLf2ScA.l += p.leafGrowthRate * 1.25;
    s.spLf2ScB.l += p.leafGrowthRate * 1.5;
    s.spLf2ScC.l += p.leafGrowthRate * 0.06;
    s.spLf2ScD.l += p.leafGrowthRate * 0.06;
  }
}

///grows all plants
function growPlants() {
  for (var i=0; i<plants.length; i++) {
    var p = plants[i];
    if ( p.isActive ) {
      p.age++;
      if ( !p.sourceSeed.hasGerminated ) { 
        germinateSeedWhenReady( p.sourceSeed );  
      } else if ( !p.sourceSeedHasBeenRemoved ) {
        hideAndRemoveSeed( p.sourceSeed ); 
      }
      if ( p.energy > p.segmentCount*energyStoreFactor && p.energy>p.seedEnergy ) {
        p.energy = p.segmentCount*energyStoreFactor;  // caps plant max energy level based on segment count
      }
      if ( p.hasFlowers ) { 
        for ( var j=0; j<p.flowers.length; j++ ) {
          var flower = p.flowers[j];
          developFlower( p, flower );  
          if ( flower.ageSinceBlooming > oldAgeMarker ) {  // plant starts dying of old age
            p.hasReachedOldAge = true;
          } 
        }
      }
      if ( p.energy > 0 || !restrictGrowthByEnergy && !p.hasReachedOldAge ) { 
        for (var k=0; k<p.segments.length; k++) { 
          var s = p.segments[k];
          if ( s.spF.l < p.maxSegmentWidth ) { 
            lengthenSegmentSpans( p, s );
            p.energy -= s.spCd.l * groEnExp;  // reduces energy by segment width while growing
          }
          if ( readyForChildSegment( p, s ) ) { 
            createSegment( p, s, s.ptE1, s.ptE2 ); 
          }
          if ( !s.hasLeaves ) { 
            generateLeavesWhenReady( p, s ); 
          } else if ( s.spLf1.l < p.maxLeafLength ) {
            growLeaves( p, s ); 
            p.energy -= s.spLf1.l*groEnExp;  // reduces energy by one leaf length while leaves growing
          }
          if ( !p.hasFlowers && readyForFlower( p, s ) ) {
            createFlower( p, s, s.ptE1, s.ptE2 ); 
          }
        }
      }
      if ( p.hasReachedOldAge ) {
        p.oldAgeReduction += oldAgeRate;
        p.energy -= p.oldAgeReduction;  
      }
      if ( p.sourceSeed.hasGerminated ) {
        p.energy -= p.segmentCount * livEnExp;  // cost of living: reduces energy by a ratio of segment count
      } 
      if ( p.isAlive && p.energy < p.maxEnergyLevel*deathEnergyLevelRatio && restrictGrowthByEnergy ) {
        killPlant( p );  // plant dies if energy level falls below minimum to be alive
      }
      if ( !p.hasCollapsed && p.energy<p.maxEnergyLevel*collapseEnergyLevelRatio && restrictGrowthByEnergy ) {
        collapsePlant( p );  // plant collapses if energy level falls below minimum to stay standing
        p.isActive = false;  // removes plant from local plant iterations
      }
    } else if ( p.hasCollapsed && currentYear - p.germinationYear >= 1 ) {
      decomposePlant( p );
    }
    if ( p.hasDecomposed && !p.hasBeenRemoved) {
      fadePlantOutAndRemove( p );
    }
  }
}

///shifts an rgba color between start and end colors scaled proportionally to start and end plant energy levels
function rgbaPlantColorShift( plant, startColor, endColor, startEnergy, endEnergy ) {
  var p = plant;
  var curEn = p.energy;  // current energy level
  var r = Math.round(endColor.r-((curEn-endEnergy)*(endColor.r-startColor.r)/(startEnergy-endEnergy))); // redshift
  var g = Math.round(endColor.g-((curEn-endEnergy)*(endColor.g-startColor.g)/(startEnergy-endEnergy))); // greenshift
  var b = Math.round(endColor.b-((curEn-endEnergy)*(endColor.b-startColor.b)/(startEnergy-endEnergy))); // blueshift
  var a = endColor.a-((curEn-endEnergy)*(endColor.a-startColor.a)/(startEnergy-endEnergy)); // alphashift
  return { r: r, g: g, b: b, a: a };
}

///shifts an hsl color between start and end colors scaled proportionally to start and end plant energy levels
function hslaPlantColorShift( plant, startColor, endColor, startEnergy, endEnergy ) {
  var p = plant;
  var curEn = p.energy;  // current energy level
  var h = Math.round(endColor.h-((curEn-endEnergy)*(endColor.h-startColor.h)/(startEnergy-endEnergy))); // redshift
  var s = Math.round(endColor.s-((curEn-endEnergy)*(endColor.s-startColor.s)/(startEnergy-endEnergy))); // greenshift
  var l = Math.round(endColor.l-((curEn-endEnergy)*(endColor.l-startColor.l)/(startEnergy-endEnergy))); // blueshift
  var a = endColor.a-((curEn-endEnergy)*(endColor.a-startColor.a)/(startEnergy-endEnergy)); // blueshift
  return { h: h, s: s, l: l, a: a };
}

///changes plant colors based on plant health
function applyHealthColoration( plant, segment ) {
  var p = plant;
  var s = segment;
  var cel = p.energy;  // current energy level
  var uel = p.maxEnergyLevel * unhealthyEnergyLevelRatio;  // unhealthy energy level ( starts yellowing)
  var sel = p.maxEnergyLevel * sickEnergyLevelRatio;  // sick energy level (starts darkening)
  var del = p.maxEnergyLevel * deathEnergyLevelRatio;  // death energy level (fully darkened; dead)
  if ( cel <= uel && cel > sel )  {  // unhealthy energy levels (yellowing)
    s.clS = rgbaPlantColorShift( p, C.hdf, C.yf, uel, sel );  // stalks (dark fills)
    s.clL = rgbaPlantColorShift( p, C.hlf, C.yf, uel, sel );  // leaves (light fills)
    s.clO = rgbaPlantColorShift( p, C.hol, C.yol, uel, sel );  // outlines
    s.clI = rgbaPlantColorShift( p, C.hil, C.yil, uel, sel );  // inner lines
    s.clLS = rgbaPlantColorShift( p, C.hls, C.yls, uel, sel );  // leaf shadows
  } else if ( cel <= sel && cel > del ) {  // sick energy levels (darkening)
    s.clS = rgbaPlantColorShift( p, C.yf, C.df, sel, del );  // stalks 
    s.clL = rgbaPlantColorShift( p, C.yf, C.df, sel, del );  // leaves
    s.clO = rgbaPlantColorShift( p, C.yol, C.dol, sel, del );  // outlines
    s.clI = rgbaPlantColorShift( p, C.yil, C.dil, sel, del );  // inner lines
    s.clLS = rgbaPlantColorShift( p, C.yls, C.dls, sel, del );  // leaf shadows
  }
  if ( p.hasFlowers && s.id === 1 ) {
    for ( var i=0; i<p.flowers.length; i++ ) {
      var f = p.flowers[i];
      f.clOv = s.clS;  // flower ovule color (matches stalk color)
      f.clO = s.clO;  // outline color (matches plant dark outline color)
      var fc = plant.flowerColor;
      //(petals)
      var ffel = p.maxEnergyLevel * flowerFadeEnergyLevelRatio;  
      if ( cel <= ffel && cel > sel ) {  // flower fading energy levels
        f.clP = hslaPlantColorShift( p, {h:fc.h,s:100,l:fc.l}, {h:fc.h,s:50,l:100}, ffel, sel );  // fade color
      } else if ( cel <= sel && cel > del ) {  // sick energy levels
        f.clP = hslaPlantColorShift( p, {h:50,s:50,l:100}, {h:45,s:100,l:15}, sel, del );  // darken color
      }      
      //(polinator pad)
      var ppfel = p.maxEnergyLevel * polinatorPadFadeEnergyLevelRatio;
      if ( cel <= ppfel && cel > sel ) {  // polinator pad fading energy levels
        f.clH = rgbaPlantColorShift( p, p.pollenPadColor, {r:77,g:57,b:0,a:1}, ppfel, sel );  // fade color
      } else if ( cel <= sel && cel > del ) {  // sick energy levels 
        f.clH = rgbaPlantColorShift( p, {r:77,g:57,b:0,a:1}, {r:51,g:37,b:0,a:1}, sel, del );  // darken color
      }
    }
  }
}

///kills plant if its energy level falls below minimum to be alive
function killPlant( plant ) {
  var p = plant;
  p.isAlive = false;  
  for (var i=0; i<plant.segments.length; i++) {
    var s = plant.segments[i];
    if ( s.hasLeaves && s.spLf1.l > plant.maxLeafLength/3 ) {
      removeSpan( s.leafTipsTetherSpan.id );  // removes large leaf bud tethers
    }
    if ( s.hasLeafScaffolding ) {  // removes leaf scaffolding
      removeSpan(s.spLf1ScA.id); removeSpan(s.spLf2ScA.id);
      removeSpan(s.spLf1ScB.id); removeSpan(s.spLf2ScB.id);
      removeSpan(s.spLf1ScC.id); removeSpan(s.spLf2ScC.id);
      removeSpan(s.spLf1ScD.id); removeSpan(s.spLf2ScD.id);
      removePoint(s.ptLf1ScA.id); removePoint(s.ptLf2ScA.id);
      removePoint(s.ptLf1ScB.id); removePoint(s.ptLf2ScB.id);
    }
    if ( s.hasLeaves && s.ptLf1.cy>s.ptB1.cy && s.ptLf2.cy>s.ptB2.cy ) {  // prevents dead leaves from swinging
      s.ptLf1.mass = s.ptLf2.mass = 0.5;
      if ( s.ptLf1.cy < s.ptLf1.py ) { s.ptLf1.cy = s.ptLf1.py; s.ptLf1.cx = s.ptLf1.px; }
      if ( s.ptLf2.cy < s.ptLf2.py ) { s.ptLf2.cy = s.ptLf2.py; s.ptLf2.cx = s.ptLf2.px; }
    }
  }
}

///collapses plant
function collapsePlant( plant ) {
  var p = plant; 
  for (var i=0; i<plant.segments.length; i++) {
    var s = plant.segments[i];
    if (!s.isBaseSegment) {
      removeSpan(s.spCdP.id);  // downward (l to r) cross span to parent
      removeSpan(s.spCuP.id);  // upward (l to r) cross span to parent
    }
    removeSpan(s.spCd.id);  // downward (l to r) cross span
    removeSpan(s.spCu.id);  // upward (l to r) cross span
    s.ptE1.mass = s.ptE2.mass = 5;
  }
  if ( p.hasFlowers ) {
    for (var j=0; j<p.flowers.length; j++ ) {
      var f = p.flowers[j];
      removeSpan(f.spCuP.id);
      removeSpan(f.spCdP.id);
      removeSpan(f.spCu.id);
      removeSpan(f.spCd.id);
      removeSpan(f.spHcDB.id);
      removeSpan(f.spHcUB.id);
      removeSpan(f.spHcH.id);
    }
  }
  p.hasCollapsed = true; 
}

///decomposes plant after collapse
function decomposePlant( plant ) {
  if ( plant.leafArcHeight > 0.05 ) {  
    plant.leafArcHeight -= 0.0001;
  } else {
    plant.leafArcHeight = 0.05;
    plant.hasDecomposed = true;
  }
}

///removes plant and all of its associated points, spans, and skins
function fadePlantOutAndRemove( plant ) {
  var p = plant;
  if ( p.opacity > 0 ) {
    p.opacity -= 0.001;
  } else {
    removePoint( p.ptB1.id );  // plant base point 1
    removePoint( p.ptB2.id );  // plant base point 2
    removeSpan( p.spB.id );  // plant base span
    for ( var i=0; i<p.segments.length; i++ ) {
      sg = p.segments[i];
      removePoint( sg.ptE1.id );  // segment extension point 1
      removePoint( sg.ptE2.id );  // segment extension point 1
      removeSpan( sg.spL.id );  // segment left span
      removeSpan( sg.spR.id );  // segment right span
      removeSpan( sg.spF.id );  // segment forward span
      removeSpan( sg.spCd.id );  // segment downward (l to r) cross span
      removeSpan( sg.spCu.id );  // segment upward (l to r) cross span
      for (var j=0; j<sg.skins.length; j++) {
        removeSkin( sg.skins[j].id );
      } 
      if (!sg.isBaseSegment) {
        removeSpan( sg.spCdP.id );  // segment downward (l to r) cross span to parent
        removeSpan( sg.spCuP.id );  // segment upward (l to r) cross span to parent
      }
      if ( sg.hasLeaves ) {
        removePoint( sg.ptLf1.id );  // segment leaf point 1 (leaf tip)
        removePoint( sg.ptLf2.id );  // segment leaf point 2 (leaf tip)  
        removeSpan( sg.spLf1.id );  // segment leaf 1 Span
        removeSpan( sg.spLf2.id );  // segment leaf 2 Span
      }
    }
    removeAllflowerPointsAndSpans( p );
    removePlant( p.id );
    plant.hasBeenRemoved = true;
  }
}




//// Renderers ////

///renders seeds
function renderSeed( resultingPlant ) {
  var seed = resultingPlant.sourceSeed;
  //point instances (centers of the two component circles)
  var p1 = seed.p1;
  var p2 = seed.p2;
  var sp = seed.sp;
  var p1x = p1.cx; 
  var p1y = p1.cy;
  var p2x = p2.cx; 
  var p2y = p2.cy;
  //references points (polar points)
  var r1x = p1.cx - ( p2.cx - p1.cx ) * (p1.width*0.5 / sp.l );
  var r1y = p1.cy - ( p2.cy - p1.cy ) * (p1.width*0.5 / sp.l );
  var r2x = p2.cx + ( p2.cx - p1.cx ) * (p2.width*0.5 / sp.l );
  var r2y = p2.cy + ( p2.cy - p1.cy ) * (p2.width*0.5 / sp.l );
  //bezier handle lengths
  var h1l = seed.sw*0.85;
  var h2l = seed.sw*0.35;
  //top bezier handles points
  var h1x = r1x + h1l * ( p1y - r1y ) / (p1.width*0.5);
  var h1y = r1y - h1l * ( p1x - r1x ) / (p1.width*0.5);
  var h2x = r2x - h2l * ( p2y - r2y ) / (p2.width*0.5);
  var h2y = r2y - h2l * ( r2x - p2x ) / (p2.width*0.5);
  //bottom bezier handles points
  var h3x = r2x + h2l * ( p2y - r2y ) / (p2.width*0.5);
  var h3y = r2y + h2l * ( r2x - p2x ) / (p2.width*0.5);
  var h4x = r1x - h1l * ( p1y - r1y ) / (p1.width*0.5);
  var h4y = r1y + h1l * ( p1x - r1x ) / (p1.width*0.5);
  //rendering
  ctx.strokeStyle = "rgba( 0, 0, 0, "+seed.opacity+" )";
  ctx.fillStyle = "rgba( 73, 5, 0, "+seed.opacity+" )";
  ctx.lineWidth = "1";
  ctx.beginPath();
  ctx.moveTo( r1x, r1y );
  ctx.bezierCurveTo( h1x, h1y, h2x, h2y, r2x, r2y );
  ctx.bezierCurveTo( h3x, h3y, h4x, h4y, r1x, r1y );
  ctx.stroke();
  ctx.fill();
}

///renders leaf
function renderLeaf( plant, segment, leafSpan ) {
  var p = plant, s = segment, lSp = leafSpan;
  var p1x = lSp.p1.cx;
  var p1y = lSp.p1.cy;
  var p2x = lSp.p2.cx;
  var p2y = lSp.p2.cy;
  var mpx = ( p1x + p2x ) / 2;  // mid point x
  var mpy = ( p1y + p2y ) / 2;  // mid point y
  ctx.lineWidth = 2;
  ctx.strokeStyle = "rgba("+s.clO.r+","+s.clO.g+","+s.clO.b+","+p.opacity+")";
  ctx.fillStyle = "rgba("+s.clL.r+","+s.clL.g+","+s.clL.b+","+p.opacity+")";
  //leaf top
  var ccpx = mpx + ( p2y - p1y ) * p.leafArcHeight;  // curve control point x
  var ccpy = mpy + ( p1x - p2x ) * p.leafArcHeight;  // curve control point y
  ctx.beginPath();
  ctx.moveTo(p1x,p1y);
  ctx.quadraticCurveTo(ccpx,ccpy,p2x,p2y);
  ctx.stroke();
  ctx.fill();
  //leaf bottom
  ccpx = mpx + ( p1y - p2y ) * p.leafArcHeight;  // curve control point x
  ccpy = mpy + ( p2x - p1x ) * p.leafArcHeight;  // curve control point y
  ctx.beginPath();
  ctx.moveTo(p1x,p1y);
  ctx.quadraticCurveTo(ccpx,ccpy,p2x,p2y);
  ctx.stroke();
  ctx.fill();
}

///renders leaves
function renderLeaves( plant, segment ) {
  if ( segment.hasLeaves ) {
    renderLeaf( plant, segment, segment.spLf1 );
    renderLeaf( plant, segment, segment.spLf2 );
    if ( viewShadows && plant.isAlive ) { addLeafShadows( segment ); }
  }
}

///renders stalks
function renderStalks( plant, segment ) {
  var p = plant;
  var sg = segment;
  for (var i=0; i<sg.skins.length; i++) {
    var sk = segment.skins[i];
    //fills
    ctx.beginPath();
    ctx.fillStyle = "rgba("+sg.clS.r+","+sg.clS.g+","+sg.clS.b+","+p.opacity+")";
    ctx.lineWidth = 1;
    ctx.strokeStyle = ctx.fillStyle;
    ctx.moveTo(sk.points[0].cx, sk.points[0].cy);
    for(var j=1; j<sk.points.length; j++) { ctx.lineTo(sk.points[j].cx, sk.points[j].cy); }
    ctx.lineTo(sk.points[0].cx, sk.points[0].cy);
    ctx.stroke();
    ctx.fill(); 
    //outlines
    ctx.beginPath();
    ctx.lineWidth = 1;
    ctx.strokeStyle = "rgba("+sg.clO.r+","+sg.clO.g+","+sg.clO.b+","+p.opacity+")";
    ctx.moveTo(sk.points[3].cx, sk.points[3].cy);
    ctx.lineTo(sk.points[0].cx, sk.points[0].cy);
    ctx.moveTo(sk.points[2].cx, sk.points[2].cy);
    ctx.lineTo(sk.points[1].cx, sk.points[1].cy);
    ctx.stroke();
    if ( !segment.hasChild ) {
      ctx.beginPath();
      ctx.moveTo(sk.points[3].cx, sk.points[3].cy);
      ctx.lineTo(sk.points[2].cx, sk.points[2].cy);
      ctx.stroke();
    }
  }
}

///renders plants (sequentially)
function renderPlants() {
  for (var i=0; i<plants.length; i++) {
    var plant = plants[i];
    for (var j=0; j<plants[i].segments.length; j++) {
      var segment = plant.segments[j];
      if ( restrictGrowthByEnergy ) { applyHealthColoration( plant, segment ); }
      if ( viewStalks ) { renderStalks( plant, segment ); }
      if ( viewFlowers && segment.id === plants[i].segments.length ) { renderFlowers( plant ); }
      if ( viewLeaves ) { renderLeaves( plant, segment ); }
    }
    renderSeed( plant );
  }
  if ( viewShadows ) { renderLeafShadows(); }
}

function downloadScreenshot() {
  var image = canvas.toDataURL("image/png");
  var download = document.getElementById("save");
  download.href = image;
  var seasonTitleCase = currentSeason.charAt(0).toUpperCase()+currentSeason.slice(1);
  download.download = "Kiss the Sky - Year "+currentYear+", "+seasonTitleCase+".png";
}

///renders instructional demos at game opening (called as callback for season announcements in flower_handler.js)
function renderDemosInFirstYear() {
  if ( readyForEliminationDemo && !eliminationDemoHasBegun && currentSeason === "Spring" ) {
    eliminationDemoHasBegun = true;
    $("#demo_elimination_div")
      .css( "visibility", "visible" )
      .animate({ opacity: 1 }, 2000, "linear" )
      .delay(4000)
      .animate({ opacity: 0 }, 2000, "linear" );
  }
  if ( readyForChangeDemo && !changeDemoHasBegun && currentSeason === "Summer" ) {
    changeDemoHasBegun = true;
    $("#demo_change_div")
      .css( "visibility", "visible" )
      .animate({ opacity: 1 }, 2000, "linear" )
      .delay(4000)
      .animate({ opacity: 0 }, 2000, "linear", function() { 
        allDemosHaveRun = true;
      });
  }
}


//// End Game Sequences ////

///checks for game over (whether all plants have died) displays game over overlay and try again button
function checkForGameOver() {
  if ( yearTime === spL + suL + faL + wiL/2 ) {
    var allDead = true;
    for ( var i=0; i<plants.length; i++ ) {
      if ( plants[i].isAlive ) { allDead = false; }
    }
    if ( allDead ) {
      $("#season_announcement").finish();
      $("#game_over_div").css( "visibility", "visible" ).animate({ opacity: 1 }, 3000, "linear" );
      endOfGameAnnouncementDisplayed = true;
      pause();
    }
  }
}

///check for game win (whether a red flower reaches 100% screen height)  
function checkForGameWin() {
  if ( gameHasBegun && !endOfGameAnnouncementDisplayed && highestRedFlowerPct === 100) {
    pause();
    runGameWinFlowersAnimation();
  }
}

///game win animation
function runGameWinFlowersAnimation() {
  var totalFlowers = 600;
  $("#game_win_div").css({ visibility: "visible", opacity: "1"});
  $("#game_win_gen_number").text( currentYear.toString().replace(/0/g,"O") );  // (replace is for dotted Nunito zero)
  $("#game_win_mode").text( gameDifficulty.toUpperCase() );
  endOfGameAnnouncementDisplayed = true;
  $("#hundred_pct_large_height_announcement")  // initial large 100% text burst
    .animate({ 
      fontSize: "+=10pt",
      opacity: 1,
    }, 500, "linear" )
    .animate({ 
      fontSize: "+=10pt",
      letterSpacing: "+=3pt",
      opacity: 0,    
    }, 700, "linear", function() {
      (function flowerLoop( i ) {  // flower splatter (uses self-invoking function (for looping with timeouts)
        $(".announcement").finish();
        var tint = Tl.rib(1,2) === 1 ? "light" : "dark"; 
        var top = Tl.rib( 0, 100 );
        var left = Tl.rib( 0, 100 );
        var width = Tl.rfb( 12, 22 );
        var rotation = Tl.rfb( 0, 60 );
        var delay = i > 20 ? 400-Math.pow(totalFlowers-i,2)*3 : 400-i*20;  // starts slow, quickens, slows at end
        delay = delay < 20 ? 20 : delay;  // sets minimum delay
        setTimeout(function () {  // delays append and position of each new flower
          $("#game_win_div").prepend( "<img id='f"+i+"' class='flower' src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/flower_"+tint+".svg'>" );
          $("#f"+i).css({ 
            position: "absolute",
            top: top+"%", 
            left: left+"%", 
            width: width+"%", 
            transform: "translate(-50%,-50%) rotate("+rotation+"deg)",
          });
          switch( i ) {  // text and buttons sequencial heavy slam in
            case 500: $("#game_win_YOU").fadeIn(100); break;
            case 470: $("#game_win_KISSED").fadeIn(100); break;
            case 440: $("#game_win_THE").fadeIn(100); break;
            case 410: $("#game_win_SKY").fadeIn(100); break;
            case 380: $("#game_win_gen_count_text").fadeIn(1500); break;
            case 330: $("#game_win_mode_text").fadeIn(1500); break;
            case 270: $(".button_game_win_play_again").fadeIn(3000);
          }
          if ( --i ) flowerLoop( i );  //  decrements i and recursively calls loop function if i > 0 (i.e., true)
        }, delay);  // sets delay with current delay variable
      })( totalFlowers );  // sets the loop's total iteration count as the argument of the self-invoking function
    });
}


//// Logging ////

///logs all gene average value changes since first generation (includes inactive plants, but not removed)
function logAllGeneChanges() {
  for ( var geneName in Genome ) {
    var currentAlleleAvg = 0;
    for ( i=0; i<plants.length; i++ ) {
      var p = plants[i];
      currentAlleleAvg += p.genotype[geneName].allele1.value;
      currentAlleleAvg += p.genotype[geneName].allele2.value;      
    }
    currentAlleleAvg = currentAlleleAvg/(plants.length*2);
    var change = currentAlleleAvg - initialGeneValueAverages[geneName];
    change = change > 0 ? "+"+change : change;
    console.log( geneName+" change: "+change.toString().slice(0,6)+"  ("+initialGeneValueAverages[geneName].toString().slice(0,5)+"->"+currentAlleleAvg.toString().slice(0,5)+")" );
  }
}

///logs a gene's average value change since first generation (includes inactive plants, but not removed)
function logGeneChange( geneName ) {  // (enter name as string)
  var currentAlleleAvg = 0;
  for ( i=0; i<plants.length; i++ ) {
    var p = plants[i];
    currentAlleleAvg += p.genotype[geneName].allele1.value;
    currentAlleleAvg += p.genotype[geneName].allele2.value;      
  }
  currentAlleleAvg = currentAlleleAvg/(plants.length*2);
  var change = currentAlleleAvg - initialGeneValueAverages[geneName];
  change = change >= 0 ? "+"+change : change;
  console.log( geneName+" change: "+change.toString().slice(0,6)+"  ("+initialGeneValueAverages[geneName].toString().slice(0,5)+"->"+currentAlleleAvg.toString().slice(0,5)+")" );
}

///logs a gene's presence across the current population by value, ordered by dominance index
function logCurrentGenePresence( geneName ) {  // (enter name as string)
  var genArr = [];
  for (i=0;i<plants.length;i++) {
    var g = plants[i].genotype[geneName];  // gene
    genArr.push( g.allele1.dominanceIndex.toString().slice(0,4)+" ["+g.allele1.value.toString().slice(0,4)+"]" );
    genArr.push( g.allele2.dominanceIndex.toString().slice(0,4)+" ["+g.allele2.value.toString().slice(0,4)+"]" );
  }
  genArr2 = [];
  for (j=0;j<genArr.length;j++) {
    if ( !genArr2.includes(genArr[j]) ) { genArr2.push(genArr[j]); } 
  }
  console.log( "\ncurrent '"+geneName+"' gene presence, by value, in order of dominance index: " );
  console.log( genArr2.sort() );
}

///runs logs (frequency in screen repaints)
function runLogs( frequency ) {
  if ( worldTime % frequency === 0 ) { 

    // console.log("\n");

    // logAllGeneChanges();
    // logGeneChange( "maxTotalSegments" );
    // logGeneChange( "maxSegmentWidth" );
    // logGeneChange( "stalkStrength" );
    // logGeneChange( "firstLeafSegment" );
    // logGeneChange( "leafFrequency" );
    // logGeneChange( "maxLeafLength" );
    // logGeneChange( "flowerHue" );
    // logGeneChange( "flowerLightness" );

    // logCurrentGenePresence( "maxTotalSegments" );
    // logCurrentGenePresence( "maxSegmentWidth" );
    // logCurrentGenePresence( "stalkStrength" );
    // logCurrentGenePresence( "firstLeafSegment" );
    // logCurrentGenePresence( "leafFrequency" );
    // logCurrentGenePresence( "maxLeafLength" );
    // logCurrentGenePresence( "flowerHue" );
    // logCurrentGenePresence( "flowerLightness" );

    //console.log( );

  }
}




/////---TESTING---/////


///scenarios
//for ( var i=0; i<20; i++ ) { createSeed( null, generateRandomNewPlantGenotype() ); }
//for ( var i=0; i<5; i++ ) { createSeed( null, generateRandomRedFlowerPlantGenotype() ); }
//for ( var i=0; i<1; i++ ) { createSeed( null, generateTinyWhiteFlowerPlantGenotype() ); }
//for ( var i=0; i<5; i++ ) { createSeed( null, generateSmallPlantGenotype() ); }  
//for ( var i=0; i<5; i++ ) { createSeed( null, generateMediumPlantGenotype() ); }
//for ( var i=0; i<10; i++ ) { createSeed( null, generateLargePlantGenotype() ); }
//for ( var i=0; i<10; i++ ) { createSeed( null, generateTallPlantGenotype( 1 ) ); }
//for ( var i=0; i<5; i++ ) { createSeed( null, generateHugePlantGenotype() ); }
//for ( var i=0; i<5; i++ ) { createSeed( null, generateHugeRedPlantGenotype() ); }




/////---DISPLAY---/////


function display() {
  runVerlet();
  if ( gameHasBegun ) {
    if ( currentYear === 1 && currentSeason === "Spring" ) {  // starts with high frame rate for smooth seed scatter
      renderBackground(); renderPlants(); displayEliminatePlantIconWithCursor();
    } else if ( worldTime % renderFactor === 0 ) {  // improves performance to render less often than verlet runs
      renderBackground(); renderPlants(); displayEliminatePlantIconWithCursor();
    }
    trackSeasons();
    shedSunlight();
    growPlants();  
    if ( runPollinationAnimations ) { renderPollinationAnimations(); }
  }
  updateUI();
  if ( !ambientMode ) { 
    renderDemosInFirstYear();
    renderMilestones();
    renderHeightMarker(); 
    checkForGameOver(); 
    checkForGameWin(); 
  }
  //runLogs( 600 );
  if ( !gamePaused ) { window.requestAnimationFrame( display ); }
}

recordInitialGeneValueAverages();
createSunRays();
if ( useSunShades ) { placeSunShades(3,3); }
display();










External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.