diff --git a/exec/load/dd_lightbar_menu.js b/exec/load/dd_lightbar_menu.js
index b7d3c6aec3c15495f53127c585a743a6a47fb5e3..bd5f817807fab7cdcd93f69fdb3d849a81f3636f 100644
--- a/exec/load/dd_lightbar_menu.js
+++ b/exec/load/dd_lightbar_menu.js
@@ -4,7 +4,6 @@
  * Author: Eric Oulashin (AKA Nightfox)
  * BBS: Digital Distortion
  * Addresses: digitaldistortionbbs.com
- *            digdist.bbsindex.com
  *            digdist.synchro.net
 
 This is a lightbar menu library.  This allows creating a scrollable menu of
@@ -46,6 +45,23 @@ By default, the menu selection will wrap around to the beginning/end when using
 the down/up arrows.  That behavior can be disabled by setting the wrapNavigation
 property to false.
 
+You can enable the display of a scrollbar by setting the scrollbarEnabled property
+to true.  By default, it is false.  For instance (assuming the menu object is lbMenu):
+lbMenu.scrollbarEnabled = true;
+The scrollbar can help to visually show how far the user is through the menu.  When
+enabled, the scrollbar will appear on the right side of the menu.  If borders are enabled,
+the scrollbar will appear just inside the right border. Also, if the scrollbar is
+enabled but all the items would fit in a single "page" in the menu, then the scrollbar
+won't be displayed.
+The scrollbar uses block characters to draw the scrollbar: ASCII character 176 for
+the background and ASCII 177 for the block that moves on the scrollbar.  If you want
+to change those characters, you can change the scrollbarInfo.BGChar and
+scrollbarInfo.blockChar properties in the menu object.
+By default, the scrollbar colors are high (bright) black for the background and high
+(bright) white for the moving block character.  If desired, those can be changed
+with the colors.scrollbarBGColor and colors.scrollbarScrollBlockColor properties in
+the menu object.
+
 This menu object supports adding multiple hotkeys to each menu item.  A hotkey
 can be specified in the Add() method a couple of different ways - By specifying
 a hotkey as the 3rd parameter and/or by putting a & in the menu item text just
@@ -157,7 +173,11 @@ lbMenu.topBorderText = "Options";
 lbMenu.bottomBorderText = "Enter = Select";
 */
 
-load("sbbsdefs.js");
+if (typeof(require) === "function")
+	require("sbbsdefs.js", "K_UPPER");
+else
+	load("sbbsdefs.js");
+
 
 // Keyboard keys
 var KEY_ESC = ascii(27);
@@ -207,6 +227,10 @@ var LOWER_LEFT_VSINGLE_HDOUBLE = "\xD4";
 var LOWER_RIGHT_VSINGLE_HDOUBLE = "\xBE";
 // Other characters
 var CHECK_CHAR = "\xFB";
+var BLOCK1 = "\xB0"; // Dimmest block
+var BLOCK2 = "\xB1";
+var BLOCK3 = "\xB2";
+var BLOCK4 = "\xDB"; // Brightest block
 
 // Border types for a menu
 var BORDER_NONE = 0;
@@ -232,13 +256,16 @@ function DDLightbarMenu(pX, pY, pWidth, pHeight)
 		width: 45,
 		height: 10
 	};
-	this.showScrollbar = false;
+	this.scrollbarEnabled = false;
 	this.borderEnabled = false;
+	this.drawnAlready = false;
 	this.colors = {
 		itemColor: "\1n\1w\1" + "4",
 		selectedItemColor: "\1n\1b\1" + "7",
 		itemTextCharHighlightColor: "\1y\1h",
-		borderColor: "\1n\1b"
+		borderColor: "\1n\1b",
+		scrollbarScrollBlockColor: "\1h\1w",
+		scrollbarBGColor: "\1h\1k"
 	};
 	// Characters to use to draw the border
 	this.borderChars = {
@@ -251,6 +278,15 @@ function DDLightbarMenu(pX, pY, pWidth, pHeight)
 		left: VERTICAL_DOUBLE,
 		right: VERTICAL_DOUBLE
 	};
+	// Scrollbar information (characters, etc.)
+	this.scrollbarInfo = {
+		blockChar: BLOCK2,
+		BGChar: BLOCK1,
+		numSolidScrollBlocks: 0,
+		numNonSolidScrollBlocks: 0,
+		solidBlockLastStartRow: 0
+	};
+
 	this.selectedItemIdx = 0;
 	this.topItemIdx = 0;
 	this.wrapNavigation = true;
@@ -295,6 +331,12 @@ function DDLightbarMenu(pX, pY, pWidth, pHeight)
 	this.AddAdditionalSelectItemKeys = DDLightbarMenu_AddAdditionalSelectItemKeys;
 	this.SelectItemKeysIncludes = DDLightbarMenu_SelectItemKeysIncludes;
 	this.ClearAdditionalSelectItemKeys = DDLightbarMenu_ClearAdditionalSelectItemKeys;
+	this.DisplayInitialScrollbar = DDLightbarMenu_DisplayInitialScrollbar;
+	this.UpdateScrollbar = DDLightbarMenu_UpdateScrollbar;
+	this.CalcScrollbarBlocks = DDLightbarMenu_CalcScrollbarBlocks;
+	this.CalcScrollbarSolidBlockStartRow = DDLightbarMenu_CalcScrollbarSolidBlockStartRow;
+	this.UpdateScrollbarWithHighlightedItem = DDLightbarMenu_UpdateScrollbarWithHighlightedItem;
+	this.CanShowAllItemsInWindow = DDLightbarMenu_CanShowAllItemsInWindow;
 
 	// Set some things based on the parameters passed in
 	if ((typeof(pX) == "number") && (typeof(pY) == "number"))
@@ -449,15 +491,19 @@ function DDLightbarMenu_SetHeight(pHeight)
 //  pSelectedItemIndexes: An object that can contain multiple indexes of selected
 //                        items.  Only for multi-select mode.  These are used
 //                        for drawing a marking character in the item text.
-//  pDrawBorders: Boolean - Whether or not to draw the borders, if borders are enabled.
-//                Defaults to true.
-function DDLightbarMenu_Draw(pSelectedItemIndexes, pDrawBorders)
+//  pDrawBorders: Optional boolean - Whether or not to draw the borders, if borders
+//                are enabled.  Defaults to true.
+//  pDrawScrollbar: Optional boolean - Whether or not to draw the scrollbar, if
+//                  the scrollbar is enabled.  Defaults to this.scrollbarEnabled, and the scrollbar
+//                  will only be drawn if not all items can be shown in a single page.
+function DDLightbarMenu_Draw(pSelectedItemIndexes, pDrawBorders, pDrawScrollbar)
 {
 	var drawBorders = (typeof(pDrawBorders) == "boolean" ? pDrawBorders : true);
+	var drawScrollbar = (typeof(pDrawScrollbar) == "boolean" ? pDrawScrollbar : true);
 
-	var itemLen = (this.showScrollbar ? this.size.width - 1 : this.size.width);
 	var curPos = { x: this.pos.x, y: this.pos.y }; // For writing the menu items
-	// If there is a border, then adjust the item length, starting x, and starting
+	var itemLen = this.size.width;
+	// If borders are enabled, then adjust the item length, starting x, and starting
 	// y accordingly, and draw the border.
 	if (this.borderEnabled)
 	{
@@ -467,6 +513,15 @@ function DDLightbarMenu_Draw(pSelectedItemIndexes, pDrawBorders)
 		if (drawBorders)
 			this.DrawBorder();
 	}
+	if (this.scrollbarEnabled && drawScrollbar && !this.CanShowAllItemsInWindow())
+	{
+		--itemLen; // Leave room for the scrollbar in the item lengths
+		this.CalcScrollbarBlocks();
+		if (!this.drawnAlready)
+			this.DisplayInitialScrollbar(this.pos.y);
+		else
+			this.UpdateScrollbarWithHighlightedItem();
+	}
 	// For numbered mode, we'll need to know the length of the longest item number
 	// so that we can use that space to display the item numbers.
 	if (this.numberedMode)
@@ -499,6 +554,8 @@ function DDLightbarMenu_Draw(pSelectedItemIndexes, pDrawBorders)
 			printf(this.colors.itemColor + "%-" + itemLen + "s", "");
 		}
 	}
+
+	this.drawnAlready = true;
 }
 
 // Draws the border around the menu items
@@ -600,7 +657,13 @@ function DDLightbarMenu_WriteItem(pIdx, pItemLen, pHighlight, pSelected)
 			itemLen = pItemLen;
 		else
 		{
-			itemLen = (this.showScrollbar ? this.size.width - 1 : this.size.width);
+			itemLen = this.size.width;
+			// If the scrollbar is enabled & we can't show all items in the window,
+			// then subtract 1 from itemLen to make room for the srollbar.
+			if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow())
+				--itemLen;
+			// If borders are enabled, then subtract another 2 from itemLen to make
+			// room for the left & right borders
 			if (this.borderEnabled)
 				itemLen -= 2;
 			// For numbered mode, we'll need to know the length of the longest item number
@@ -612,6 +675,7 @@ function DDLightbarMenu_WriteItem(pIdx, pItemLen, pHighlight, pSelected)
 				--itemLen; // Have a space for separation between the numbers and items
 			}
 		}
+
 		var itemColor = "";
 		if (typeof(pHighlight) == "boolean")
 			itemColor = (pHighlight ? this.colors.selectedItemColor : this.colors.itemColor);
@@ -774,6 +838,9 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes)
 	var continueOn = true;
 	while (continueOn)
 	{
+		if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow())
+			this.UpdateScrollbarWithHighlightedItem();
+
 		this.lastUserInput = getKeyWithESCChars(K_NOECHO|K_NOSPIN|K_NOCRLF);
 		if ((this.lastUserInput == KEY_UP) || (this.lastUserInput == KEY_LEFT))
 		{
@@ -1056,7 +1123,7 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes)
 					--XPos;
 					++YPos;
 				}
-				if (this.showScrollbar)
+				if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow())
 					--XPos;
 				console.gotoxy(XPos, YPos);
 				if (added)
@@ -1306,6 +1373,236 @@ function DDLightbarMenu_ClearAdditionalSelectItemKeys()
 	this.additionalSelectItemKeys = "";
 }
 
+// Displays an initial scrollbar
+//
+// Parameters:
+//  pSolidBlockStartRow: The starting row for the solid/bright blocks
+//  pNumSolidBlocks: The number of solid/bright blocks to write.  If this
+//                   is omitted, this.scrollbarInfo.numSolidScrollBlocks
+//                   will be used.
+function DDLightbarMenu_DisplayInitialScrollbar(pSolidBlockStartRow, pNumSolidBlocks)
+{
+	var numSolidBlocks = (typeof(pNumSolidBlocks) == "number" ? pNumSolidBlocks : this.scrollbarInfo.numSolidScrollBlocks);
+
+	var numSolidBlocksWritten = 0;
+	var wroteBrightBlockColor = false;
+	var wroteDimBlockColor = false;
+	var startY = this.pos.y;
+	var screenBottomRow = this.pos.y + this.size.height - 1;
+	var scrollbarCol = this.pos.x + this.size.width - 1;
+	if (this.borderEnabled)
+	{
+		++startY;
+		--screenBottomRow;
+		--scrollbarCol;
+	}
+	this.scrollbarInfo.solidBlockLastStartRow = startY;
+	for (var screenY = startY; screenY <= screenBottomRow; ++screenY)
+	{
+		console.gotoxy(scrollbarCol, screenY);
+		if ((screenY >= pSolidBlockStartRow) && (numSolidBlocksWritten < numSolidBlocks))
+		{
+			if (!wroteBrightBlockColor)
+			{
+				console.print("\1n" + this.colors.scrollbarScrollBlockColor);
+				wroteBrightBlockColor = true;
+				wroteDimBlockColor = false;
+			}
+			console.print(this.scrollbarInfo.blockChar);
+			++numSolidBlocksWritten;
+		}
+		else
+		{
+			if (!wroteDimBlockColor)
+			{
+				console.print("\1n" + this.colors.scrollbarBGColor);
+				wroteDimBlockColor = true;
+			}
+			console.print(this.scrollbarInfo.BGChar);
+		}
+	}
+}
+
+// Calculates the starting row for the solid blocks on the scrollbar
+//
+// Return value: The starting row for the solid blocks on the scrollbar
+function DDLightbarMenu_CalcScrollbarSolidBlockStartRow()
+{
+	var scrollbarStartY = this.pos.y;
+	var scrollbarHeight = this.size.height;
+	if (this.borderEnabled)
+	{
+		++scrollbarStartY;
+		scrollbarHeight -= 2;
+	}
+	var scrollbarBottomY = scrollbarStartY + scrollbarHeight - 1;
+	var solidBlockStartRow = scrollbarStartY;
+	if (this.items.length > 0)
+	{
+		var scrollbarFraction = this.selectedItemIdx / this.items.length;
+		var scrollbarStartRow = scrollbarStartY + Math.floor(scrollbarHeight * scrollbarFraction);
+		solidBlockStartRow = scrollbarStartRow - Math.floor(this.scrollbarInfo.numSolidScrollBlocks / 2);
+		// Don't let the solid blocks go above the starting screen row or below the ending
+		// screen row of the scrollbar
+		if (solidBlockStartRow < scrollbarStartY)
+			solidBlockStartRow = scrollbarStartY;
+		else if (solidBlockStartRow + this.scrollbarInfo.numSolidScrollBlocks > scrollbarBottomY)
+			solidBlockStartRow = scrollbarBottomY - this.scrollbarInfo.numSolidScrollBlocks + 1;
+	}
+	return solidBlockStartRow;
+}
+
+// Updates the scrollbar position based on the currently-selected
+// item index, this.selectedItemIdx.
+function DDLightbarMenu_UpdateScrollbarWithHighlightedItem()
+{
+	var solidBlockStartRow = this.CalcScrollbarSolidBlockStartRow();
+	if (solidBlockStartRow != this.scrollbarInfo.solidBlockLastStartRow)
+		this.UpdateScrollbar(solidBlockStartRow, this.scrollbarInfo.solidBlockLastStartRow, this.scrollbarInfo.numSolidScrollBlocks);
+	this.scrollbarInfo.solidBlockLastStartRow = solidBlockStartRow;
+}
+
+function DDLightbarMenu_CanShowAllItemsInWindow()
+{
+	var pageHeight = (this.borderEnabled ? this.size.height - 2 : this.size.height);
+	return (this.items.length <= pageHeight);
+}
+
+// For the DigDistMsgReader class: Updates the scrollbar for a message, for use
+// in enhanced reader mode.  This does only the necessary character updates to
+// minimize the number of characters that need to be updated on the screen.
+//
+// Parameters:
+//  pNewStartRow: The new (current) start row for solid/bright blocks
+//  pOldStartRow: The old start row for solid/bright blocks
+//  pNumSolidBlocks: The number of solid/bright blocks.  If this is omitted,
+//                   this.scrollbarInfo.numSolidScrollBlocks will be used.
+function DDLightbarMenu_UpdateScrollbar(pNewStartRow, pOldStartRow, pNumSolidBlocks)
+{
+	var numSolidBlocks = (typeof(pNumSolidBlocks) == "number" ? pNumSolidBlocks : this.scrollbarInfo.numSolidScrollBlocks);
+
+	var startY = this.pos.y;
+	var screenBottomRow = this.pos.y + this.size.height - 1;
+	var scrollbarCol = this.pos.x + this.size.width - 1;
+	if (this.borderEnabled)
+	{
+		++startY;
+		--screenBottomRow;
+		--scrollbarCol;
+	}
+
+	// Calculate the difference in the start row.  If the difference is positive,
+	// then the solid block section has moved down; if the diff is negative, the
+	// solid block section has moved up.
+	var solidBlockStartRowDiff = pNewStartRow - pOldStartRow;
+	var oldLastRow = pOldStartRow + numSolidBlocks - 1;
+	var newLastRow = pNewStartRow + numSolidBlocks - 1;
+	if (solidBlockStartRowDiff > 0)
+	{
+		// The solid block section has moved down
+		if (pNewStartRow > oldLastRow)
+		{
+			// No overlap
+			// Write dim blocks over the old solid block section
+			console.print("\1n" + this.colors.scrollbarBGColor);
+			for (var screenY = pOldStartRow; screenY <= oldLastRow; ++screenY)
+			{
+				console.gotoxy(scrollbarCol, screenY);
+				console.print(this.scrollbarInfo.BGChar);
+			}
+			// Write solid blocks in the new locations
+			console.print("\1n" + this.colors.scrollbarScrollBlockColor);
+			for (var screenY = pNewStartRow; screenY <= newLastRow; ++screenY)
+			{
+				console.gotoxy(scrollbarCol, screenY);
+				console.print(this.scrollbarInfo.blockChar);
+			}
+		}
+		else
+		{
+			// There is some overlap
+			// Write dim blocks on top
+			console.print("\1n" + this.colors.scrollbarBGColor);
+			for (var screenY = pOldStartRow; screenY < pNewStartRow; ++screenY)
+			{
+				console.gotoxy(scrollbarCol, screenY);
+				console.print(this.scrollbarInfo.BGChar);
+			}
+			// Write bright blocks on the bottom
+			console.print("\1n" + this.colors.scrollbarScrollBlockColor);
+			for (var screenY = oldLastRow+1; screenY <= newLastRow; ++screenY)
+			{
+				console.gotoxy(scrollbarCol, screenY);
+				console.print(this.scrollbarInfo.blockChar);
+			}
+		}
+	}
+	else if (solidBlockStartRowDiff < 0)
+	{
+		// The solid block section has moved up
+		if (pOldStartRow > newLastRow)
+		{
+			// No overlap
+			// Write dim blocks over the old solid block section
+			console.print("\1n" + this.colors.scrollbarBGColor);
+			for (var screenY = pOldStartRow; screenY <= oldLastRow; ++screenY)
+			{
+				console.gotoxy(scrollbarCol, screenY);
+				console.print(this.scrollbarInfo.BGChar);
+			}
+			// Write solid blocks in the new locations
+			console.print("\1n" + this.colors.scrollbarScrollBlockColor);
+			for (var screenY = pNewStartRow; screenY <= newLastRow; ++screenY)
+			{
+				console.gotoxy(scrollbarCol, screenY);
+				console.print(this.scrollbarInfo.blockChar);
+			}
+		}
+		else
+		{
+			// There is some overlap
+			// Write bright blocks on top
+			console.print("\1n" + this.colors.scrollbarScrollBlockColor);
+			var endRow = pOldStartRow;
+			for (var screenY = pNewStartRow; screenY < endRow; ++screenY)
+			{
+				console.gotoxy(scrollbarCol, screenY);
+				console.print(this.scrollbarInfo.blockChar);
+			}
+			// Write dim blocks on the bottom
+			console.print("\1n" + this.colors.scrollbarBGColor);
+			endRow = pOldStartRow + numSolidBlocks;
+			for (var screenY = pNewStartRow+numSolidBlocks; screenY < endRow; ++screenY)
+			{
+				console.gotoxy(scrollbarCol, screenY);
+				console.print(this.scrollbarInfo.BGChar);
+			}
+		}
+	}
+}
+
+// Calculates the number of solid scrollbar blocks & non-solid scrollbar blocks
+// to use.  Saves the information in this.scrollbarInfo.numSolidScrollBlocks and
+// this.scrollbarInfo.numNonSolidScrollBlocks.
+function DDLightbarMenu_CalcScrollbarBlocks()
+{
+	var menuDisplayHeight = this.size.height;
+	if (this.borderEnabled)
+		menuDisplayHeight -= 2;
+	var menuListFractionShown = menuDisplayHeight / this.items.length;
+	if (menuListFractionShown > 1)
+		menuListFractionShown = 1.0;
+	this.scrollbarInfo.numSolidScrollBlocks = Math.floor(menuDisplayHeight * menuListFractionShown);
+	if (this.scrollbarInfo.numSolidScrollBlocks <= 0)
+		this.scrollbarInfo.numSolidScrollBlocks = 1;
+	else if (this.scrollbarInfo.numSolidScrollBlocks > menuDisplayHeight)
+		this.scrollbarInfo.numSolidScrollBlocks = menuDisplayHeight;
+	this.scrollbarInfo.numNonSolidScrollBlocks = menuDisplayHeight - this.scrollbarInfo.numSolidScrollBlocks;
+}
+
+//////////////////////////////////////////////////////////
+// Helper functions, not part of the DDLightbarMenu class
+
 // Inputs a keypress from the user and handles some ESC-based
 // characters such as PageUp, PageDown, and ESC.  If PageUp
 // or PageDown are pressed, this function will return the