<h1>Happy Holidays</h1>
<div class="tree">
  <span class="star">🌟</span>
  <div class="top"></div>
  <div class="trunk"></div>
<!--  <div class="gifts">🎁 &nbsp; &nbsp; 🎁 🎁</div>-->
</div>
 
<form>
  <label>
    Tree height:
    <input type="range" id="treeheight" min="100" max="600" value="350" step="10">
    <span class="rangeval"></span>px
  </label>
  <label>
    Tree radius:
    <input type="range" id="treeradius" min="120" max="300" value="150" step="10">
    <span class="rangeval"></span>px
  </label>
  <label>
    Number of sides:
    <input type="range" id="count" min="4" max="20" value="12" step="1">
    <span class="rangeval"></span>
  </label>
</form>
html,body {
  height: 100%;
}
body {
  width: 100%;
  overflow: hidden;
  display: flex;
  flex-flow: column;
  align-items: center;
  justify-content: center;
}

h1 {
  text-align: center;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 10000;
  transform: translateZ(1000px);
  color: #cc0000;  
  font-style: italic;
  font-size: 10vh;
  font-family: 'Lucida Handwriting', cursive;
}

form {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  padding-bottom: 1em;
  transform: translateZ(200px); // overlap tree
  
  label {
    display: block;
    margin: 0 auto;
    text-align: center;
    padding-top: 0.7em;
  }
  input {
    max-width: 45%;
  }
}
.tree {
  font-size: 36px;
  position: relative;
  transform-style: preserve-3d;
  margin-top: 15vh;
  
  .star {
    position: absolute;
    top: 0;
    width: 100%;
    text-align: center;
    color: gold;
    text-shadow: 0px 0px 8px rgba(255,255,0,0.9);
    transform: translateY(-0.8em);
  }

  .top {
    width: 100%;
    height: 100%;
    transform-style: preserve-3d;
    
    .branch {
      perspective: 800px;
      position: absolute;
      border-bottom: 0 solid green;
      border-left: 0 solid transparent;
      border-right: 0 solid transparent;
      transform-origin: 50% 0%;
      transform-style: preserve-3d;
    }
    
  } // .top
  
  .trunk {
    background: rgb(133,83,22);
    background: linear-gradient(to right, rgb(133,83,22) 0%, rgb(74,46,12) 100%);
    position: absolute;
    bottom: 0;
    transform: translateZ(-200px); // behind the tree
    transform-style: preserve-3d; /* Firefox */
  }
  
} // .tree

@media screen and (max-width:500px) {
  form label {
    padding: 1em 0;
  }
  h1 {
    font-size: 6vh;
  }
}
/* https://github.com/Keyframes/jQuery.Keyframes/blob/master/jquery.keyframes.js */
(function() {
    var animationSupport = false,
        animationString = 'animation',
        vendorPrefix = prefix = '',
        domPrefixes = ['Webkit', 'Moz', 'O', 'ms', 'Khtml'];

    $(document).ready(function(){
        var style = document.body.style;
        if( style.animationName !== undefined ) { animationSupport = true; }

        if( animationSupport === false ) {
            for( var i = 0; i < domPrefixes.length; i++ ) {
                if( style[ domPrefixes[i] + 'AnimationName' ] !== undefined ) {
                    prefix = domPrefixes[ i ];
                    animationString = prefix + 'Animation';
                    vendorPrefix = '-' + prefix.toLowerCase() + '-';
                    animationSupport = true;
                    break;
                }
            }
        }
    });


    var $createKeyframeStyleTag = function(id, css) {
        if($.keyframe.debug){ console.log(id + " " + css); }
        return $("<style>" + css + "</style>").attr({
            "class": "keyframe-style",
            id: id,
            type: "text/css"
        }).appendTo("head");
    };

    $.keyframe = {
        debug: false,
        getVendorPrefix: function() {
            return vendorPrefix;
        },
        isSupported: function() {
            return animationSupport;
        },
        generate: function(frameData) {
            var frameName = frameData.name || "";
            var css = "@" + vendorPrefix + "keyframes " + frameName + " {";

            for (var key in frameData) {
                if (key !== "name" && key !== "media" && key !== "complete") {
                    css += key + " {";

                    for (var property in frameData[key]) {
                        css += property + ":" + frameData[key][property] + ";";
                    }

                    css += "}";
                }
            }
            if(window.PrefixFree)
                css = PrefixFree.prefixCSS(css + "}");
            else 
                css += "}";
            if(frameData.media){
                css = "@media " + frameData.media + "{" + css + "}";
            }

            var $frameStyle = $("style#" + frameData.name);

            if ($frameStyle.length > 0) {
                $frameStyle.html(css);

                var $elems = $("*").filter(function() {
                    return this.style[animationString + "Name"] === frameName;
                });

                $elems.each(function() {
                    var $el = $(this);
                    var options = $el.data("keyframeOptions");
                    $el.resetKeyframe(function() {
                        $el.playKeyframe(options);
                    });
                });
            } else {
                $createKeyframeStyleTag(frameName, css);
            }
        },
        define: function(frameData) {
            if (frameData.length) {
                for (var i = 0; i < frameData.length; i++) {
                    var frame = frameData[i];
                    this.generate(frame);
                }
            } else {
                this.generate(frameData);
            }
        }
    };

    var animationPlayState = "animation-play-state";
    var playStateRunning = "running";

    $.fn.resetKeyframe = function(callback) {
        var $el = $(this).css(vendorPrefix + animationPlayState, playStateRunning).css(vendorPrefix + "animation", "none");

        if (callback) {
            setTimeout(callback, 1);
        }
    };

    $.fn.pauseKeyframe = function() {
        $(this).css(vendorPrefix + animationPlayState, "paused");
    };

    $.fn.resumeKeyframe = function() {
        $(this).css(vendorPrefix + animationPlayState, playStateRunning);
    };

    $.fn.playKeyframe = function(frameOptions, callback) {
        
        var animObjToStr = function(obj){
            obj = $.extend({
                duration: '0s',
                timingFunction: "ease",
                delay: '0s',
                iterationCount: 1,
                direction: "normal",
                fillMode: "forwards"
            }, obj);
            return [obj.name, obj.duration, obj.timingFunction, obj.delay, obj.iterationCount, obj.direction, obj.fillMode].join(" ");
        };

        var animationcss = "";

        if($.isArray(frameOptions)){
            var frameOptionsStrings = [];
            for(var i = 0; i < frameOptions.length; i++){
                if (typeof frameOptions[i] === 'string') {
                    frameOptionsStrings.push(frameOptions[i]);
                }else{
                    frameOptionsStrings.push(animObjToStr(frameOptions[i]));
                }
            }
            animationcss = frameOptionsStrings.join(", ");
        }else if (typeof frameOptions === 'string') {
            animationcss = frameOptions;
        }else{
            animationcss = animObjToStr(frameOptions);
        }

        var animationkey = vendorPrefix + "animation";
        var pfx = ["webkit", "moz", "MS", "o", ""];

        if(!callback && frameOptions.complete){
            callback = frameOptions.complete;
        }

        var _prefixEvent = function(element, type, callback) {
            for(var i = 0; i < pfx.length; i++){
                if (!pfx[i]) {
                    type = type.toLowerCase();
                }
                var evt = pfx[i] + type;
                element.off(evt).on(evt, callback);
            }
        };

        this.each(function() {
            var $el = $(this).addClass("boostKeyframe").css(vendorPrefix + animationPlayState, playStateRunning).css(animationkey, animationcss).data("keyframeOptions", frameOptions);
            if($.keyframe.debug){
                console.group();
                if(vendorPrefix){ console.log("Vendor Prefix: " + vendorPrefix); }
                console.log("Style Applied: " + animationcss);
                var testCss = $el.css(animationkey);
                console.log("Rendered Style: " + (testCss ? testCss : $el[0].style.animation));
                console.groupEnd();
            }
            if (callback) {
                _prefixEvent($el, 'AnimationIteration', callback);
                _prefixEvent($el, 'AnimationEnd', callback);
            }
        });
        return this;
    };

    $createKeyframeStyleTag("boost-keyframe", " .boostKeyframe{" + vendorPrefix + "transform:scale3d(1,1,1);}");

}).call(this);

/* ************************************************************************ */

$('#treeheight').prop('max',$(window).height()-100);
$('#treeradius').prop('max',$(window).width()/2);

$('input[type="range"]').on('input change',function() {
  $(this).next('.rangeval').text($(this).val());
  
  var td = { // tree data
    cycle: 8, // seconds
    treetilt: '15deg', // degrees
    count: 1*$('#count').val(), 
    treeheight: 1*$('#treeheight').val(), // pixels
    treeradius: 1*$('#treeradius').val(), // pixels
    triheight: function() {
      return Math.sqrt(Math.pow(this.treeheight,2)+Math.pow(this.treeradius,2));
    },
    countangle: function() {
      return Math.PI / this.count; // Math.tan() only accepts radians
    },
    trihalfbase: function() {
      return Math.tan(this.countangle()) * this.treeradius * 1.01; // helps close the gaps
    },
    tilt: function() {
      return Math.asin(this.treeradius / this.triheight());
    },
    trunksize: function() {
      return this.treeradius * 0.2;
    }
  }; // td
  
  $('.tree').css({
    width: td.treeradius * 2,
    height: td.treeheight + 1.5 * td.trihalfbase(),
    'max-height': $(window).height()
  });
  $('.tree .trunk').css({
    left: 'calc(50% - '+(td.trunksize()*0.5)+'px)',
    width: td.trunksize(),
    top: td.treeheight,
    height: 1.5 * td.trunksize(),
  });

  $('.tree .top').empty();
  for (var i=1; i<=td.count; i++) {
    $('.tree .top').append('<div class="branch">');
    var angle = i*360 / td.count,
        spinobj = { name: 'spin'+i };
    for (var j=0; j<=td.count; j++) {
      var pct = 100 * j / td.count,
          apos = angle + (360 * j / td.count),
          jpct = ((270+apos) * 100 / 360) % 100,
          brightness = 20 + (jpct<50?jpct:(100-jpct)) * 80/100,
          icolor = 'hsla(100,80%,'+brightness+'%,1.0)';
      spinobj[pct+'%'] = {
        transform: 'rotateX(-'+td.treetilt+')' 
                    + ' rotateY('+apos+'deg)'
                    + ' rotateX('+td.tilt()+'rad)',
        'border-bottom-color': icolor
      };
    }; // for j
//    console.log(spinobj);
/*    $('.branch').eq(i-1).css({
      animation: 'spin'+i+' '+td.cycle+'s linear infinite'
    });*/
    $.keyframe.define([spinobj]);
    $('.branch:nth-child('+i+')').playKeyframe({
      name: 'spin'+i,
      duration: td.cycle+'s',
      timingFunction: 'linear', 
      iterationCount: 'infinite'
    });
  }; // for i

  $('.tree .top .branch').css({
    left: 'calc(50% - '+td.trihalfbase()+'px)',
    'border-bottom-width': td.triheight(),
    'border-left-width': td.trihalfbase(),
    'border-right-width': td.trihalfbase()
  });

}).trigger('change');


External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js