function ringMenu(radius,separation,tilt,sense,itemHeight,itemWidth,updateInterval,ringSpanColor,shrink) {
	this.r = ((radius > 0) ? radius : 1);
	this.ih = itemHeight;	// validate
	this.iw = itemWidth;	// validate
	this.sep = ((separation > 0) ? separation : 1);
	// validate tilt = should be 0 < tilt <= radius
	var t = ((tilt > 0) ? tilt : 0.1);
	this.tiltFactor = (t * t) / (this.r * this.r);	// will range from close to 0, to 1
	this.tiltSense = sense;
	this.nitems = 0;
	this.nsteps = 0;
	this.trajectory = new Array(3);	// x,y pairs, plus scaled height
	this.locoIndex = 0;
	// define the centroid of the ring (ellipse) - modify in calcSizes
	this.cdx = this.r;
	this.cdy = this.r;
	this.dir = 1;
	this.intv = 0;
	this.upd = updateInterval;
	this.rsbgc = ringSpanColor;
	this.rsw = 0;
	this.rsh = 0;
	this.bsw = 0;
	this.bsh = 0;
	this.minheight = 16;	// used to prevent distant items from totally disappearing
	this.shortRadius = (this.r * this.tiltFactor);
	this.shortAxis = (2 * this.shortRadius);
	this.shrink = shrink;

	// add an item
	this.addItem = function(msg,linkstr,imgsrc,alttxt,ltarget) {

		// alert(this.nitems);	// for debug

		var str = "<div ";
		str += "id='itemid" + this.nitems + "' ";
		str += "style='";
		str += "cursor:pointer;"; // adjust the div cursor to a hand pointing
		str += "position:absolute;";
		str += "top:0px;";
		str += "left:0px;";
		str += "height:" + this.ih + "px;";
		str += "width:" + this.iw + "px;";
		str += "overflow:hidden;";
		str += "text-align:center;";
		str += "background-color:transparent;";
//		str += "background-color:\"#ff0000\";";	// for debug, to see the item div's extent
		str += "' ";
		// set the onclick property of the div to go to the link
		str += "onclick='window.open(\""+linkstr+"\""+(ltarget?(",\""+ltarget+"\""):",\"_self\"")+")'";
		str += " >";
		if(imgsrc) {
// put an image within the div tag and set its height to 100% so it scales to the div
// omit its width, so it maintains its own aspect ratio
// because the div overflow is set to hidden, it will clip if necessary
			str += "<img src='" + imgsrc + "' alt='" + alttxt + "' border='1' ";
			str += "height='100%' ";
			str += " />";
		}
//		str += msg;
		str += "</div>";

		document.write(str);

		this.nitems++;	// last thing
	}

	this.start = function() {

		// write out the ring span closing tag
		document.write("</span>");
		// write out the controls
		this.writeControls();
		// write out the bounding span closing tag
		document.write("</span>");

		// pre-compute trajectory
		this.nsteps = this.nitems * this.sep;
		this.trajectory[0] = new Array(this.nsteps);
		this.trajectory[1] = new Array(this.nsteps);
		this.trajectory[2] = new Array(this.nsteps);

		var thetaIncr = 2 * Math.PI / this.nsteps;
		// alert("thetaIncr="+thetaIncr);
		var dx, dy, coord, scaleFactor, hscale;

		// init theta so 1st item is properly positioned front and center
		var theta = Math.PI / 2;

		for( var i=0; i < this.nsteps; i++, theta += thetaIncr ) {
			// alert("i="+i+",theta="+theta);
			// calc as tho it's a circle, then scale to an ellipse according to sense
			dx = this.r + (Math.cos(theta) * this.r);
			dy = this.r + (Math.sin(theta) * this.r);
			if(this.tiltSense) {	// true means yaw
				dx *= this.tiltFactor;
				coord = dx;
			} else {	// false means pitch
				dy *= this.tiltFactor;
				coord = dy;
			}
			// alert(dx + "," + dy);
			this.trajectory[0][i] = Math.round(dx);
			this.trajectory[1][i] = Math.round(dy);
			if(this.shrink) {
				// also calculate the scaled HEIGHT, not width
				// we don't want distant items to totally disappear,
				// but we also don't want foremost items
				// to ever exceed full size (ih)...
				scaleFactor = coord / this.shortAxis;
				hscale = (this.ih - this.minheight) * scaleFactor + this.minheight;
				hscale = Math.round(hscale);
				// alert("sf="+scaleFactor+", hs="+hscale);
				this.trajectory[2][i] = hscale;
			} else {
				this.trajectory[2][i] = this.ih;
			}
		}

		// alert("tiltFactor="+this.tiltFactor);
		// this.dumpTrajectory();	// for debug only

		// display the items in their proper places for the first time
		this.updateItems();
	}

	this.updateItems = function() {
		var iobj, istyle, istr, slot, coord;
		for( var i=0; i < this.nitems; i++ ) {
			slot = (this.locoIndex + i * this.sep) % this.nsteps;
			istr = "itemid" + i + "";
			iobj = document.getElementById(istr);
			istyle = iobj.style;
			istyle.top = this.trajectory[1][slot] + "px";
			istyle.left = this.trajectory[0][slot] + "px";
			// set the z-index - if horizontal, use the y coord
			if(this.tiltSense) {	// true means yaw/vertical
				coord = this.trajectory[0][slot];
			} else {	// false means pitch
				coord = this.trajectory[1][slot];
			}
			istyle.zIndex = coord;
			if(this.shrink) istyle.height = this.trajectory[2][slot] + "px";
		}
	}

	this.dumpTrajectory = function() {
		var str = "nsteps="+ this.nsteps +"\n";
		for( var i=0; i < this.nsteps; i++ ) {
			str += " " + i + ". (" + this.trajectory[0][i] + "," + this.trajectory[1][i] + ")\n";
		}
		alert(str);
	}

	// advance, given direction
	this.advance = function() {
		if( this.dir > 0 ) {
			this.locoIndex++;
			if( this.locoIndex >= this.nsteps ) this.locoIndex = 0;
		} else {
			this.locoIndex--;
			if( this.locoIndex < 0 ) this.locoIndex = this.nsteps - 1;
		}
		this.updateItems();
	}

	// onmouseover function for cw control
	this.spinCW = function(damper) {
		this.dir = 1;
		var updfreq = this.upd;
		updfreq *= (damper+1);
		updfreq = Math.round(updfreq);
		var myself = this;
		this.intv = window.setInterval(function(){myself.advance()},updfreq);
	}

	// onmouseout function for both controls
	this.stopSpin = function() {
		window.clearInterval(this.intv);
	}

	// onmouseover function for ccw control
	this.spinCCW = function(damper) {
		this.dir = -1;
		var updfreq = this.upd;
		updfreq *= (damper+1);
		updfreq = Math.round(updfreq);
		var myself = this;
		this.intv = window.setInterval(function(){myself.advance()},updfreq);
	}

	// figure out the width and height for the bounding span and the ring span
	// since the bounding span includes the ring span, do the ring span first
	// update the centroid coords based on tiltFactor and sense
	this.calcSizes = function() {
		// figure out the width and height of the ring span
		this.rsw = this.rsh = 2 * this.r;
		if(this.tiltSense) {
			this.rsw *= this.tiltFactor;
			this.cdx *= this.tiltfactor;
		} else {
			this.rsh *= this.tiltFactor;
			this.cdy *= this.tiltfactor;
		}
		this.rsw += this.iw;
		this.rsw = Math.round(this.rsw);
		this.rsh += this.ih;
		this.rsh = Math.round(this.rsh);

		// figure out the width and height of the bounding span
		// it must include the ring and the controls

		// for now assume controls are always lined up along bottom
		// bounding span width = ring span width
		this.bsw = this.rsw;

		// bounding span height?
		// it must include the ring span height plus the control button height
		// but they don't exist yet!
		// oh well, since overflow is not set to hidden, it will grow as required!
		this.bsh = this.rsh;
	}

	this.openRing = function() {
		// write the ring span opening tag
		var str = "<span id='ringspan' ";
		str += "style='";
		str += "position:relative;";
		str += "width:" + this.rsw + "px;";
		str += "height:" + this.rsh + "px;";
		str += "background-color: " + this.rsbgc + ";";
		str += "' ";
		str += ">";
		document.write(str);
	}

	this.openBound = function() {
		// write the bounding span opening tag
		var str = "<span id='bspan' ";
		str += "style='";
		str += "position:relative;";
		str += "width:" + this.bsw + "px;";
		str += "height:" + this.bsh + "px;";
		str += "background-color: " + this.rsbgc + ";";
		str += "' ";
		str += ">";
		document.write(str);
	}

	this.create = function() {
		this.calcSizes();
		this.openBound();
		this.openRing();
	}

	this.writeControls = function() {
		// figure out apropos width for the button - assume equally spread across bounding width bsw
		var butwidth = this.bsw / 7;
		butwidth = Math.floor(butwidth);

		var butbase = "<button style='border:0;width:";
		var omoverccw = "px;' onmouseover='myRM.spinCCW(";
		var omovercw = "px;' onmouseover='myRM.spinCW(";
		var mout = ");' onmouseout='myRM.stopSpin();' >";
		var butend = "</button>";

		var str = "<br style=\"clear:'both';\" />";
		str += butbase + butwidth + omoverccw + 0 + mout + "&lt;&lt;&lt;" + butend;
		str += butbase + butwidth + omoverccw + 4 + mout + "&lt;&lt;" + butend;
		str += butbase + butwidth + omoverccw + 10 + mout + "&lt;" + butend;
		str += butbase + butwidth + "px;' >0</button>";
		str += butbase + butwidth + omovercw + 10 + mout + "&gt;" + butend;
		str += butbase + butwidth + omovercw + 4 + mout + "&gt;&gt;" + butend;
		str += butbase + butwidth + omovercw + 0 + mout + "&gt;&gt;&gt;" + butend;

		document.write(str);
	}
}

var myRM = new ringMenu(309,4,70,false,60,132,10, "",true);	// cccc66
