diff --git a/xtrn/mrc/mrc-client.example.ini b/xtrn/mrc/mrc-client.example.ini
index 589b8970018b46a6da2c2bfed9accacea9f6c9a3..8c0eda1dfe04d9434bb0445cd28317f45e1762ea 100644
--- a/xtrn/mrc/mrc-client.example.ini
+++ b/xtrn/mrc/mrc-client.example.ini
@@ -5,10 +5,15 @@ ping_interval = 60
 [startup]
 room = lobby
 motd = true
-banners = true
+commands = 
+splash = true
 
 [aliases]
 
-[client]
-;change this to true if you want show the nick list on connect.
-show_nicks=false
+[theme]
+
+[msg_color]
+
+[show_nicks]
+
+[twit_list]
diff --git a/xtrn/mrc/mrc-client.js b/xtrn/mrc/mrc-client.js
index 2e0c7ee95926dacff9c27888d98e186e29485d76..4108746bb6ef0225554664d9d616516f07fc07be 100644
--- a/xtrn/mrc/mrc-client.js
+++ b/xtrn/mrc/mrc-client.js
@@ -5,6 +5,36 @@
  * echicken -at- bbs.electronicchicken.com
  *
  * I started out with good intentions.  At least it works.
+ *
+ * -------------------------------------------------------------
+ * Codefenix's comments:
+ * codefenix -at- conchaos.synchro.net
+ *
+ * Sincere thanks and kudos to echicken for laying down the
+ * foundation with a great initial working build. I've used it
+ * for a long time to join weekly MRC meetups, and it has always
+ * worked great.
+ *
+ * In 2023 I took it upon myself to start studying this code,
+ * and attempted make some basic cosmetic changes I felt were
+ * needed, like timestamped messages and a togglable nick list.
+ *
+ * In 2024, nelgin implemented SSL (BIG thanks for that!). Later
+ * that same year I decided to get back into it add basic support
+ * for mentions, indented line-wrapping, and basic customizable 
+ * themes.
+ *
+ * 2025 brings the most changes yet: twit filter, browseable
+ * mentions, and numerous fixes previously missed.
+ *
+ * I'm a big believer in MRC, and I've always tried to be 
+ * respectful of the original design of this code and not make 
+ * too many jarring changes. Please excuse some of the coding 
+ * decisions I made that may appear less than elegant. I do hope 
+ * to optimize and improve the features implemented. Thanks to 
+ * everyone who has provided feedback and words of encouragement.
+ *
+ *
  */
 
 load('sbbsdefs.js');
@@ -21,8 +51,11 @@ var input_state = 'chat';
 var show_nicks = false;
 var stat_mode = 0; // 0 = Local Session stats (Chatters, Latency, & Mentions); 1 = Global MRC Stats
 
+var mentions = [];
+var mention_index = 0;
 const MENTION_MARKER = ascii(15);
 const INDENT_MARKER = ascii(28);
+const NOTIF_FILE = system.data_dir + "msgs/" + format("%04d.msg", user.number);
 
 // default theme
 var theme_bg_color = BG_BLUE;
@@ -38,13 +71,14 @@ const settings = {
     aliases: f.iniGetObject('aliases') || {},
     client: f.iniGetObject('client') || {},
     show_nicks: f.iniGetObject('show_nicks') || {},
-    theme: f.iniGetObject('theme') || {}
+    theme: f.iniGetObject('theme') || {},
+    msg_color: f.iniGetObject('msg_color') || {},
+    twit_list: f.iniGetObject('twit_list') || {}
 };
 
 f.close();
 f = undefined;
 
-
 const NICK_COLOURS = [
     '\x01h\x01r',
     '\x01h\x01g',
@@ -71,34 +105,6 @@ const ACT_LVL = [
     /* 3: HI  */ "\x01H\x01RHI"
 ];
 
-function pipe_to_ctrl_a(str) { // could use the pipeToCtrlA function in funclib.js instead
-	str = str.replace(/\|00/g, "\x01N\x01K");
-	str = str.replace(/\|01/g, "\x01N\x01B");
-	str = str.replace(/\|02/g, "\x01N\x01G");
-	str = str.replace(/\|03/g, "\x01N\x01C");
-	str = str.replace(/\|04/g, "\x01N\x01R");
-	str = str.replace(/\|05/g, "\x01N\x01M");
-	str = str.replace(/\|06/g, "\x01N\x01Y");
-	str = str.replace(/\|07/g, "\x01N\x01W");
-	str = str.replace(/\|08/g, "\x01H\x01K");
-	str = str.replace(/\|09/g, "\x01H\x01B");
-	str = str.replace(/\|10/g, "\x01H\x01G");
-	str = str.replace(/\|11/g, "\x01H\x01C");
-	str = str.replace(/\|12/g, "\x01H\x01R");
-	str = str.replace(/\|13/g, "\x01H\x01M");
-	str = str.replace(/\|14/g, "\x01H\x01Y");
-	str = str.replace(/\|15/g, "\x01H\x01W");
-	str = str.replace(/\|16/g, "\x01" + 0);
-	str = str.replace(/\|17/g, "\x01" + 4);
-	str = str.replace(/\|18/g, "\x01" + 2);
-	str = str.replace(/\|19/g, "\x01" + 6);
-	str = str.replace(/\|20/g, "\x01" + 1);
-	str = str.replace(/\|21/g, "\x01" + 5);
-	str = str.replace(/\|22/g, "\x01" + 3);
-	str = str.replace(/\|23/g, "\x01" + 7);
-	return str;
-}
-
 function resize_nicklist(frames, nicks) {
     const maxlen = show_nicks ? Math.max(1, nicks.reduce(function (a, c) {
         return c.length > a ? c.length : a;
@@ -121,7 +127,7 @@ function redraw_nicklist(frames, nicks, colours) {
     }
 }
 
-function init_display() {
+function init_display(msg_color) {
     const w = console.screen_columns;
     const h = console.screen_rows;
     const f = { top: new Frame(1, 1, w, h, BG_BLACK|LIGHTGRAY) };
@@ -130,10 +136,12 @@ function init_display() {
     f.divider = new Frame(1, h - 1, w, 1, theme_bg_color|LIGHTGRAY, f.top);
     f.nicklist = new Frame(w - 2, 2, 2, h - 3, BG_BLACK|LIGHTGRAY, f.top);
     f.nicks = new Frame(w - 1, 2, 1, h - 3, BG_BLACK|LIGHTGRAY, f.nicklist);
-    f.input = new Frame(1, h, w, 1, BG_BLACK|WHITE, f.top);
+    f.input_color = new Frame(1, h, 1, 1, BG_BLACK|LIGHTGRAY, f.top);
+    f.input = new Frame(2, h, w-2, 1, BG_BLACK|WHITE, f.top); // TODO: Test
     f.output_scroll = new ScrollBar(f.output, { autohide: true });
     f.nick_scroll = new ScrollBar(f.nicks, { autohide: true });
     f.output.word_wrap = true;
+    f.input_color.putmsg( pipeToCtrlA( format( "|%02d>", msg_color) ) );
     f.divider.gotoxy(f.divider.width - 5, 1);
     f.divider.putmsg(theme_fg_color + '/help');
     f.top.open();
@@ -141,19 +149,19 @@ function init_display() {
 }
 
 function refresh_stats(frames, session) {
-    if (input_state == 'chat' ) {            
+    if (input_state == 'chat' ) {
         frames.divider.clear();
         if (stat_mode == 0) {
-            frames.divider.putmsg(format(theme_fg_color + "CLIENT STATS" + theme_2nd_color + "> " + 
+            frames.divider.putmsg(format(theme_fg_color + "CLIENT STATS" + theme_2nd_color + "> " +
                 theme_fg_color + "Latency" +  theme_2nd_color + " [" + theme_fg_color + "%d" + theme_2nd_color + "] " +
                 theme_fg_color + "Chatters" + theme_2nd_color + " [" + theme_fg_color + "%d" + theme_2nd_color + "] " +
                 theme_fg_color + "Mentions" + theme_2nd_color + " [" + theme_fg_color + "%d" + theme_2nd_color + "]",
                 session.latency,
                 session.nicks.length,
                 session.mention_count
-            ));        
+            ));
         } else {
-            frames.divider.putmsg(format(theme_fg_color + "GLOBAL STATS" + theme_2nd_color + "> " + 
+            frames.divider.putmsg(format(theme_fg_color + "GLOBAL STATS" + theme_2nd_color + "> " +
                 theme_fg_color + "BBSes" +    theme_2nd_color + " [" + theme_fg_color + "%d" + theme_2nd_color + "] " +
                 theme_fg_color + "Rooms" +    theme_2nd_color + " [" + theme_fg_color + "%d" + theme_2nd_color + "] " +
                 theme_fg_color + "Users" +    theme_2nd_color + " [" + theme_fg_color + "%d" + theme_2nd_color + "] " +
@@ -179,8 +187,8 @@ function append_message(frames, msg, mention) {
     }
 
     // programmatically insert indent(s), if the message length exceeds the frame width.
-    if (strlen(msg) >= frames.output.width - 8) {
-        var indent = "\r\n" + (new Array(7).join( " " )) + INDENT_MARKER + " ";
+    if (strlen(msg) >= frames.output.width - (console.screen_columns < 132 ? 8 : 11)) {
+        var indent = "\r\n" + (new Array( console.screen_columns < 132 ? 7 : 10 ).join( " " )) + INDENT_MARKER + " ";
         msg = lfexpand(word_wrap(msg, frames.output.width - 9)).replace(/\r\n$/g, "").replace(/\r\n/g, indent).trim();
     }
 
@@ -190,20 +198,29 @@ function append_message(frames, msg, mention) {
         "\x01n" + msg + '\r\n'                                               // message itself
     );
     frames.output.scroll(0, -1);
-    if (input_state == 'scroll') frames.output.scrollTo(0, top);
+
+    if (mention) {
+        mentions.push(top+1);
+    }
+
+    if (input_state == 'scroll' || input_state == 'mentions') frames.output.scrollTo(0, top);
 }
 
 function getShortTime(d) {
-    return format( "%02d:%02d", d.getHours(), d.getMinutes() );
+    if (console.screen_columns < 132 ) {
+        return format( "%02d:%02d", d.getHours(), d.getMinutes() );
+    } else {
+        return format( "%02d:%02d:%02d", d.getHours(), d.getMinutes(), d.getSeconds() );
+    }
 }
 
-function display_message(frames, msg, mention) { //, colour param was listed but not used..     
-    const body = pipe_to_ctrl_a(truncsp(msg.body) || '').split(' ');
+function display_message(frames, msg, mention) {
+    const body = pipeToCtrlA(truncsp(msg.body) || '').split(' ');
     append_message(frames, body[0] + '\x01n\x01w ' + body.slice(1).join(' ') + '\x01n\x01w', mention);
 }
 
 function display_server_message(frames, msg) {
-    append_message(frames, '\x01h\x01w' + pipe_to_ctrl_a(truncsp(msg) || ''));
+    append_message(frames, '\x01h\x01w' + pipeToCtrlA(truncsp(msg) || ''));
 }
 
 function display_title(frames, room, title) {
@@ -225,9 +242,10 @@ function new_alias() {
     ) + suffix;
     set_alias(alias);
     settings.aliases[user.alias] = alias;
+    return alias;
 }
 
-function list_themes () {
+function list_themes() {
     var theme_list = [];
     var theme_files = directory(backslash(js.startup_dir) + "mrc-theme-*.ini");
     for (var t = 0; t < theme_files.length; t++) {
@@ -236,7 +254,7 @@ function list_themes () {
     return theme_list.join(", ");
 }
 
-function set_theme (theme_name) {
+function set_theme(theme_name) {
     var f = new File(js.startup_dir + format("mrc-theme-%s.ini", theme_name));
     if ( f.open('r') ) {
         const theme = f.iniGetObject();
@@ -250,13 +268,99 @@ function set_theme (theme_name) {
     }
 }
 
-function save_setting (alias, setting_name, setting_value) {
+function save_setting(alias, setting_name, setting_value) {
     const f = new File(js.startup_dir + 'mrc-client.ini');
     f.open('r+');
     f.iniSetValue(setting_name, alias, setting_value);
     f.close();
 }
 
+function manage_twits(cmd, twit, twitlist, frames) {
+    switch (cmd) {
+        case "add":
+            if (twitlist.indexOf(twit) < 0) {
+                twitlist.push(twit);
+                if (twitlist.indexOf(twit) >= 0) {
+                    save_setting(user.alias, "twit_list", twitlist.join(ascii(126)));
+                    display_server_message(frames, "\x01n\x01c\x01h" + twit + "\x01w successfully added to Twit List\x01n.\x01n\x01w");
+                }
+            }
+            break;
+        case "del":
+            var indexRmv = twitlist.indexOf(twit);
+            if (indexRmv >= 0) {
+                twitlist.splice(indexRmv, 1);
+                if (twitlist.indexOf(twit.toLowerCase()) < 0) {
+                    save_setting(user.alias, "twit_list", twitlist.join(ascii(126)));
+                    display_server_message(frames, "\x01n\x01c\x01h" + twit + "\x01w successfully REMOVED from Twit List\x01n.\x01n\x01w");
+                }
+            }
+            break;
+        case "list":
+            if (twitlist.length > 0) {
+                display_server_message(frames, "\x01n\x01c\x01hTwit List (\x01w" + twitlist.length + "\x01c)\x01n:\x01n\x01w");
+                for (var twit in twitlist ) {
+                    display_server_message(frames, "\x01n\x01w - \x01h" + twitlist[twit] + "\x01n\x01w");
+                }
+            } else {
+                display_server_message(frames, "\x01n\x01c\x01hYour Twit List is \x01wempty\x01n.\x01n\x01w");
+            }
+            break;
+        case "clear":
+            twitlist.splice(0,twitlist.length);
+            save_setting(user.alias, "twit_list", "");
+            display_server_message(frames, "\x01n\x01c\x01hYour Twit List is cleared.\x01n\x01w");
+            break;
+        default:
+            display_server_message(frames, "\x01k\x01h*.:\x01n\x01w (*) Invalid twit command.\x01n\x01w");
+            display_server_message(frames, "\x01k\x01h*.:\x01n\x01w     See\x01H\x01K:\x01N \x01H/\x01Chelp\x01N \x01Htwit\x01n\x01w");
+            display_server_message(frames, "\x01k\x01h*.: __\x01n\x01w");
+    }
+}
+
+function display_system_messages(frames) { // display local system messages (e.g.: user new email alerts, etc.)
+    if (file_size(NOTIF_FILE) > 0) {
+        var fnotif = new File(NOTIF_FILE);
+        if ( fnotif.open('r') ) {
+            const notif = fnotif.readAll();
+            fnotif.close();
+            fnotif = undefined;
+            display_server_message(frames, "\x01k\x01h*.:\x01y\x01h :: System Notification ::\x01n\x01w");
+            for (var notif_line in notif) {
+                display_server_message(frames, "\x01k\x01h*.: " + notif[notif_line]);
+            }
+            display_server_message(frames, "\x01k\x01h*.:\x01k\x01h__\x01n\x01w");
+        }
+        console.beep();
+        file_removecase(NOTIF_FILE);
+    }
+}
+
+function browse_mentions(frames) {
+    if (mentions.length > 0) {
+        mention_index = mentions.length-1;
+        input_state = 'mentions';
+        frames.divider.clear();
+        frames.divider.putmsg(theme_fg_color + format('UP/DOWN to browse mentions (%d of %d), ENTER to return', mention_index+1, mentions.length ));
+        frames.output.scrollTo(1, mentions[mention_index]);
+        frames.output_scroll.cycle();
+    }
+}
+
+function display_external_text(frames, which) {
+    var text = [];
+    var fhelp = new File(js.startup_dir + "mrc-" + which + ".msg");
+    if ( fhelp.open('r') ) {
+        text = fhelp.readAll();
+        fhelp.close(); 
+        for (var line in text) {
+            display_server_message(frames, text[line]);
+        }  
+    } else {
+        display_server_message(frames, "\x01w\x01h (*) Topic not found: \x01c" + which + "\x01n" );
+    }    
+}
+
 function main() {
 
     var msg;
@@ -274,15 +378,21 @@ function main() {
     if (settings.show_nicks[user.alias]) {
         show_nicks = settings.show_nicks[user.alias];
     }
-
     if (settings.theme[user.alias]) {
         set_theme(settings.theme[user.alias]);
     }
+    if (settings.msg_color[user.alias]) {
+        session.msg_color = settings.msg_color[user.alias];
+    }
+    if (settings.twit_list[user.alias]) {
+        session.twit_list = settings.twit_list[user.alias].split(ascii(126));
+    }
 
-    const frames = init_display();
+    const frames = init_display(session.msg_color);
     const inputline = new InputLine(frames.input);
     inputline.show_cursor = true;
     inputline.max_buffer = 140;
+    inputline.cursor_attr = BG_BLACK|session.msg_color;
 
     resize_nicklist(frames, []);
     redraw_nicklist(frames, []);
@@ -297,42 +407,30 @@ function main() {
     session.on('error', function (err) {
         display_message(frames, { from_user: 'ERROR', body: err });
     });
-    session.on('help', function (cmd, help, ars) {
-        if (!ars || user.compare_ars(ars)) {
-            display_server_message(frames, format('\x01h\x01w/\x01h\x01c%s \x01h\x01w- \x01n\x01w%s', cmd, help));
+
+    session.on('local_help', function (help_topic) {
+        display_external_text(frames, "help-" + (help_topic ? help_topic : "main"));
+        if (help_topic == "theme") {
+            display_server_message(frames, "\x01n\x01w Available theme options: " + list_themes());
         }
-    });
-    session.on('local_help', function (msg) {
-        display_server_message(frames, '\x01h\x01w/\x01h\x01cscroll \x01h\x01w- \x01n\x01wScroll the output area');
-        display_server_message(frames, '\x01h\x01w/\x01h\x01cscroll_nicks \x01h\x01w- \x01n\x01wScroll the nicklist');
-        display_server_message(frames, '\x01h\x01w/\x01h\x01cnick_prefix \x01h\x01w- \x01n\x01wSet a single-character prefix for your handle, eg. /nick_prefix @');
-        display_server_message(
-            frames,
-            '\x01h\x01w/\x01h\x01cnick_color \x01h\x01w- \x01n\x01wSet your nick color to one of '
-            + PIPE_COLOURS.reduce(function (a, c) {
-                a += format('|%02d%s ', c, c);
-                return a;
-            }, '')
-            + ', eg. /nick_color 11'
-        );
-        display_server_message(frames, '\x01h\x01w/\x01h\x01cnick_suffix \x01h\x01w- \x01n\x01wSet an eight-character suffix for your handle, eg. /nick_suffix <poop>');
-        display_server_message(frames, '\x01h\x01w/\x01h\x01ctoggle_nicks \x01h\x01w- \x01n\x01wShow/hide the nicklist');
-        display_server_message(frames, '\x01h\x01w/\x01h\x01ctoggle_stats \x01h\x01w- \x01n\x01wSwitch between global MRC stats and client/session stats.');
-        display_server_message(frames, "\x01h\x01w/\x01h\x01ctheme \x01n\x01w- \x01nSelect a theme: " + list_themes() );
-        display_server_message(frames, '\x01h\x01w/\x01h\x01cquit \x01n\x01w- \x01h\x01wExit the program');
+        display_server_message(frames, "\x01k\x01h*.:\x01k\x01h__\x01n\x01w");        
     });
     session.on('message', function (msg) {
         if (msg.from_user == 'SERVER') {
             display_server_message(frames, msg.body);
         } else {
-            var mention = false;
-            if (msg.body.toUpperCase().indexOf(user.alias.toUpperCase()) >= 0 && msg.from_user !== user.alias) {
-                console.beep();
-                mention = true;
-                session.mention_count = session.mention_count + 1;
-                refresh_stats (frames, session);
-            }            
-            display_message(frames, msg, mention); //, nick_colours[msg.from_user] || '');
+            if (session.twit_list.indexOf(msg.from_user.toLowerCase()) < 0) {
+                var mention = false;
+                if (msg.body.toUpperCase().indexOf(user.alias.toUpperCase()) >= 0 && msg.from_user !== user.alias) {
+                    console.beep();
+                    mention = true;
+                    session.mention_count = session.mention_count + 1;
+                    refresh_stats (frames, session);
+                }
+                display_message(frames, msg, mention);
+            } /*else {
+                log ( "filtered message from " + msg.from_user + ".");
+            }*/
         }
     });
     session.on('nicks', function (nicks) {
@@ -341,7 +439,7 @@ function main() {
         });
         resize_nicklist(frames, nicks);
         redraw_nicklist(frames, nicks, nick_colours);
-        refresh_stats (frames, session);
+        refresh_stats(frames, session);
     });
     session.on('sent_privmsg', function (user, msg) {
         display_message(frames, { body: '--> ' + (nick_colours[user] || '') + user + ' ' + msg });
@@ -356,19 +454,64 @@ function main() {
         refresh_stats(frames, session);
     });
 
+    if (settings.startup.splash) display_external_text(frames, "splash");
     if (settings.startup.motd) session.motd();
-    if (settings.startup.banners) session.banners();
+    session.send_notme("|07- |11" + user.alias + " |03has arrived.");
+    mswait(20);
+    session.send_command('TERMSIZE:' + console.screen_columns + 'x' + console.screen_rows);
+    mswait(20);
+    session.send_command('BBSMETA: SecLevel(' + user.security.level + ') SysOp(' + system.operator + ')');
+    mswait(20);
+    session.send_command('USERIP:' + (bbs.atcode("IP") == "127.0.0.1" ? client.ip_address : bbs.atcode("IP")) );
     if (settings.startup.room) session.join(settings.startup.room);
 
+    if (settings.startup.commands) {
+        var startup_cmds = settings.startup.commands.split(',');
+        for (var startup_cmd in startup_cmds) {
+            session.send_command(startup_cmds[startup_cmd].toUpperCase());
+        }
+    }
+
     var cmd, line, user_input;
     while (!js.terminated && !break_loop) {
         session.cycle();
-        frames.divider.gotoxy(frames.divider.width - 16, 1);
-        frames.divider.putmsg(theme_2nd_color + "[" + theme_fg_color + ("000" + inputline.buffer.length).slice(-3) + theme_2nd_color + '/' + theme_fg_color + inputline.max_buffer + theme_2nd_color + "]");
+        if (input_state == 'chat') {
+            frames.divider.gotoxy(frames.divider.width - 16, 1);
+            frames.divider.putmsg(theme_2nd_color + "[" + theme_fg_color + ("000" + inputline.buffer.length).slice(-3) + theme_2nd_color + '/' + theme_fg_color + inputline.max_buffer + theme_2nd_color + "]");
+
+            if (inputline.buffer.search(/^\/(identify|register|roompass|update password|roomconfig password) ./i) == 0) {
+                inputline.frame.left();
+                inputline.frame.write("*"); // mask text after commands involving passwords
+            } 
+        }
         user_input = inputline.getkey();
         if (typeof user_input == 'string') {
             if (input_state == 'chat') {
-                if (user_input.substring(0, 1) == '/') { // It's a command
+                // Define some hotkeys
+                if (user_input == KEY_LEFT || user_input == KEY_RIGHT) { // Change user's text color                  
+                    session.msg_color = session.msg_color + (user_input == KEY_LEFT ? -1 : 1);
+                    if (session.msg_color < 1) {
+                        session.msg_color = 15;
+                    } else if (session.msg_color > 15) {
+                        session.msg_color = 1;
+                    }
+                    inputline.cursor_attr = BG_BLACK|session.msg_color;
+                    frames.input_color.clear();
+                    frames.input_color.putmsg( pipeToCtrlA( format( "|%02d>", session.msg_color) ) );
+                    save_setting(user.alias, "msg_color", session.msg_color);
+                } else if (user_input == KEY_PAGEUP) { // Shortcut for scroll
+                    input_state = 'scroll';
+                    if (frames.output.offset.y > 0) {
+                        frames.output.scroll(0, -1 * frames.output.height);
+                        frames.output_scroll.cycle();                    
+                    }
+                    frames.divider.clear();
+                    frames.divider.putmsg(theme_fg_color + 'UP/DOWN, PGUP/PGDN, HOME/END to scroll, ENTER to return');
+                } else if (user_input == KEY_UP) {
+                    browse_mentions(frames);
+                } else if (user_input == CTRL_D) {
+                    inputline.clear();
+                } else if (user_input.substring(0, 1) == '/') { // It's a command
                     cmd = user_input.split(' ');
                     cmd[0] = cmd[0].substr(1).toLowerCase();
                     switch (cmd[0]) {
@@ -435,20 +578,38 @@ function main() {
                             redraw_nicklist(frames, session.nicks, nick_colours);
                             save_setting(user.alias, "show_nicks", show_nicks);
                             break;
-                        case "toggle_stats":                            
+                        case "toggle_stats":
                             stat_mode = stat_mode === 0 ? 1 : 0;
                             refresh_stats(frames, session);
                             break;
                         case "theme":
-                            set_theme( cmd[1] );
-                            frames.title.attr = theme_bg_color|theme_fg_color;
-                            frames.divider.attr = theme_bg_color|theme_fg_color;
-                            display_title(frames, session.room, session.room_topic);
-                            refresh_stats(frames, session);
+                            if (cmd.length == 2) {
+                                set_theme( cmd[1] );
+                                frames.title.attr = theme_bg_color|theme_fg_color;
+                                frames.divider.attr = theme_bg_color|theme_fg_color;
+                                display_title(frames, session.room, session.room_topic);
+                                refresh_stats(frames, session);
+                            }
+                            break;
+                        case "mentions":
+                            browse_mentions(frames);
+                            break;
+                        case "twit":
+                            if (cmd.length >= 2) {
+                                manage_twits( cmd[1].toLowerCase(), cmd.slice(2).join(' ').toLowerCase(), session.twit_list, frames );
+                            }
                             break;
                         default:
                             if (typeof session[cmd[0]] == 'function') {
                                 session[cmd[0]](cmd.slice(1).join(' '));
+                                // This check doesn't let commands out unless they're
+                                // strictly defined in mrc-session.js, meaning we'd  have
+                                // to constantly chase down new server commands each time
+                                // they're added/removed.
+                            } else {
+                                // Just send the command to the server if it's not defined as
+                                // a local command. The server will tell the user if it's not valid.
+                                session.send_command(user_input.substr(1));
                             }
                             break;
                     }
@@ -475,10 +636,32 @@ function main() {
                 } else if ( // Regular message
                     typeof user_input == 'string' // Could be bool at this point
                     && user_input != ''
-                    && [KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT].indexOf(user_input) < 0
+                    && [KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, 
+                        KEY_HOME, KEY_END, KEY_INSERT, KEY_PAGEDN].indexOf(user_input) < 0
                 ) {
                     session.send_room_message(user_input);
                 }
+            } else if (input_state == 'mentions') {
+                if (user_input == KEY_UP || user_input == KEY_DOWN) {
+                    mention_index = mention_index + (user_input == KEY_UP ? -1 : 1);
+                    if (mention_index < 0) {
+                        mention_index = 0;
+                    } else if (mention_index > mentions.length-1) {
+                        mention_index = mentions.length-1;
+                    }
+                    if (frames.output.offset.y !== mentions[mention_index] ) {
+                        frames.output.scrollTo(1, mentions[mention_index]);
+                        frames.output_scroll.cycle();
+                        frames.divider.clear();
+                        frames.divider.putmsg(theme_fg_color + format('UP/DOWN to browse mentions (%d of %d), ENTER to return', mention_index+1, mentions.length ));
+                    }
+                } else if (user_input == '' || user_input == 'q') {
+                    frames.output.scrollTo(1, frames.output.data_height - frames.output.height-1);
+                    frames.output_scroll.cycle();
+                    input_state = 'chat';
+                    session.mention_count = 0;
+                    refresh_stats(frames, session);
+                }
             } else if (input_state == 'scroll' || input_state == 'scroll_nicks') {
                 var sframe = input_state == 'scroll' ? frames.output : frames.nicks;
                 if (user_input == KEY_UP && sframe.offset.y > 0) {
@@ -510,8 +693,12 @@ function main() {
             frames.nick_scroll.cycle();
             console.gotoxy(inputline.frame.__relations__.child[0].x, inputline.frame.y);
         }
+
+        display_system_messages(frames);
+
         yield();
     }
+    console.clear(autopause=false); // prevent an unintended auto-pause when quitting
 }
 
 main();
diff --git a/xtrn/mrc/mrc-connector.js b/xtrn/mrc/mrc-connector.js
index 3de85ea00ceeed085c7fe062533874ebcf6a84a0..380c1cbf54735b39dc1095ed18d330413a23dcec 100644
--- a/xtrn/mrc/mrc-connector.js
+++ b/xtrn/mrc/mrc-connector.js
@@ -28,8 +28,8 @@ f = undefined;
 if (!settings.ssl)
 	settings.ssl=false;
 
-const PROTOCOL_VERSION = '1.3.1';
-const MAX_LINE = 256;
+const PROTOCOL_VERSION = '1.3.2';
+const MAX_LINE = 512;
 const FROM_SITE = system.name.replace(/ /g, "_");
 const SYSTEM_NAME = system_info.system_name || system.name;
 
@@ -221,8 +221,10 @@ function mrc_receive(sock) {
         line = sock.recvline(MAX_LINE, settings.timeout);
         if (!line || line == '') break;
         latency_tracker.forEach(function(m) {
-            if (m.line===line.trim()) {
+            if (m.line===line.trim() || 
+                (m.line.indexOf("~STATS") >= 0 && line.indexOf("~STATS") >= 0 ) ) {
                 client_send({ from_user: "SERVER", to_user: 'CLIENT', body: 'LATENCY:' + (Date.now() - m.time) }); 
+                log(LOG_DEBUG, 'Latency: ' +  (Date.now() - m.time) );
                 latency_tracker = [];
             }
         });
@@ -239,14 +241,12 @@ function mrc_receive(sock) {
             fMa.write(message.body.substr(message.body.indexOf(':')+1).trim());
             fMa.close();
         }
-        if (['', 'ALL', FROM_SITE].indexOf(message.to_site) > -1) {
-            if (['', 'CLIENT', 'ALL', 'NOTME'].indexOf(message.to_user) > -1) {
-                // Forward to all clients
-                client_send(message);
-            } else {
-                // Send to this user
-                client_send(message, message.to_user);
-            }
+        if (['', 'CLIENT', 'ALL', 'NOTME'].indexOf(message.to_user) > -1) {
+            // Forward to all clients
+            client_send(message);
+        } else {
+            // Send to this user
+            client_send(message, message.to_user);
         }
         yield();
     }
@@ -274,6 +274,7 @@ function main() {
 
         Object.keys(clients).forEach(function (e, i) {
             if (!clients[e].socket.is_connected) {
+                mrc_send(mrc_sock, clients[e].username, "", "NOTME", "", "", "|07- |12" + clients[e].username + " |04has left chat|08.");                
                 mrc_send(mrc_sock, clients[e].username, '', 'SERVER', '', '', 'LOGOFF');
                 client_send({ from_user: clients[e].username, to_user: 'SERVER', body: 'LOGOFF' }); // Notify local clients
                 delete clients[e];
diff --git a/xtrn/mrc/mrc-help-main.msg b/xtrn/mrc/mrc-help-main.msg
new file mode 100644
index 0000000000000000000000000000000000000000..2b82190c7bb9f130bca968c50d0d3d41a6fc1faa
--- /dev/null
+++ b/xtrn/mrc/mrc-help-main.msg
@@ -0,0 +1,21 @@
+NB__[ HWList of available commandsN: B]_____________________________
+WH/CinfoN�HK:N View information about a BBSHK:N H/CinfoN H#
+WH/Ct Kor W/Cmsg�K:N Send a direct messageHK:N�H/CtN HnickN HBmessage
+WH/CrN�HK:N Reply to last direct messageHK:N H/CrB message
+WH/CjoinN�HK:N Move to a new roomHK:W�/CjoinN Hroom_name
+WH/CtopicN�HK:N Change room topic:�H/CtopicW topic
+WH/CroomsN�HK:N List available rooms
+WH/CusersN�HK:N List users
+WH/CwhoonN�HK:N List users and BBSes
+WH/CmotdN�HK:N Display the Message of the Day
+WH/Cscroll Kor CPGUPK:N Scroll the chat window
+WH/Cmentions Kor CUPK:N Reset mention counter and review mentions
+WH/Cscroll_nicksN  HK:N Scroll the nicklist
+WH/Ctoggle_nicksN  HK:N Show/hide the nicklist
+WH/Ctoggle_statsN  HK:N Toggle MRC stats view
+WH/CthemeN�HK:N Select a theme HK..........N seeHK:N H/ChelpN Htheme
+WH/CtwitN�HK:N Twit List management HK....N seeHK:N H/ChelpN Htwit
+WH/CquitN�HK:N Exit the program
+WHCLEFTK/CRIGHTW�K:N Change your text color HK..N seeHK:N H/ChelpN Hnick
+WHCCTRLK+CDN�HK:N Clear the input line
+NFor a list of additional HSERVERN commands, see H/CquoteN Hhelp
\ No newline at end of file
diff --git a/xtrn/mrc/mrc-help-nick.msg b/xtrn/mrc/mrc-help-nick.msg
new file mode 100644
index 0000000000000000000000000000000000000000..b0941fcadb05fce51c858bc33542e70874d8e36e
--- /dev/null
+++ b/xtrn/mrc/mrc-help-nick.msg
@@ -0,0 +1,12 @@
+NB__[ HWNick customization optionsN: B]_____________________________
+HW/Cnick_prefix K:W NSet a single-character prefix for your handle
+N�eg.H /Cnick_prefixN H@
+WH/Cnick_colorN  HK:W NSet your nick color
+H�NG02HW NC03HW NR04HW NM05HW NY06HW B09W G10W C11W R12M 13W Y14W 15
+WH/Cnick_suffix K:W NSet an eight-character suffix for your handle
+N�eg.H N/HCnick_suffix W<afilz>
+
+N You can include pipe color codes in your nick_prefix and
+N nick_suffix. Get as creative as you want!
+
+HB NExample: HB@CUserNameB<GaFiLZB>
diff --git a/xtrn/mrc/mrc-help-theme.msg b/xtrn/mrc/mrc-help-theme.msg
new file mode 100644
index 0000000000000000000000000000000000000000..77bbc7042b3bb13e9544737738092789794387e3
--- /dev/null
+++ b/xtrn/mrc/mrc-help-theme.msg
@@ -0,0 +1,5 @@
+NB__[ HWHow to set a custom themeN: B]______________________________
+
+N Usage:
+   WH/Ctheme NHnameoftheme
+N 
\ No newline at end of file
diff --git a/xtrn/mrc/mrc-help-twit.msg b/xtrn/mrc/mrc-help-twit.msg
new file mode 100644
index 0000000000000000000000000000000000000000..b2ba22b3731e7a7c5b1c97bc6bd7e3d262812aea
--- /dev/null
+++ b/xtrn/mrc/mrc-help-twit.msg
@@ -0,0 +1,10 @@
+NB__[ HWHow to use the twit listN: B]_______________________________
+
+N Basic twit list implemented! Hides messages from problematic 
+N chatters.
+
+N Usage:
+   WH/Ctwit NHadd HBnameoftwit K:W NFilters out nameoftwit's messages
+   WH/Ctwit NHdel HBnameoftwit K:W Nnameoftwit's messages are re-allowed
+   WH/Ctwit NHlist           K:W NLists twits the user has added
+   WH/Ctwit NHclear          K:W NEmpty's the user's twit list
diff --git a/xtrn/mrc/mrc-session.js b/xtrn/mrc/mrc-session.js
index 6699434858d261824231a6f654dfc6d1dd40f6f0..ca6e5e351681fd014a2bcfd317885d7ca26ff07d 100644
--- a/xtrn/mrc/mrc-session.js
+++ b/xtrn/mrc/mrc-session.js
@@ -16,7 +16,10 @@ function MRC_Session(host, port, user, pass, alias) {
         alias: alias || user,
         stats: ['-','-','-','0'],
         mention_count: 0,
-        latency: '-'
+        latency: '-',
+        msg_color: 7,
+        twit_list: [],
+        last_private_msg_from: ""
     };
 
     const callbacks = {
@@ -41,10 +44,10 @@ function MRC_Session(host, port, user, pass, alias) {
     function send_message(to_user, to_room, body) {
         if (body.length + state.alias.length + 1 > 250) {
             word_wrap(body, 250 - 1 - state.alias.length).split(/\n/).forEach(function (e) {
-                send(to_user, '', to_room, state.alias + ' ' + e);
+                send(to_user, '', to_room, state.alias + ' ' + format("|%02d", state.msg_color) + e);
             });
         } else {
-            send(to_user, '', to_room, state.alias + ' ' + body);
+            send(to_user, '', to_room, state.alias + ' ' + format("|%02d", state.msg_color) + body);
         }
     }
 
@@ -80,7 +83,7 @@ function MRC_Session(host, port, user, pass, alias) {
                     break;
                 case 'STATS':
                     state.stats = params.split(' ');
-                    emit('stats'); //, state.stats);
+                    emit('stats'); 
                     break;
                 case 'LATENCY':
                     state.latency = params;
@@ -90,6 +93,10 @@ function MRC_Session(host, port, user, pass, alias) {
                     emit('message', msg);
                     break;
             }
+            if (msg.body.search(/just joined room/) > -1 ||
+                msg.body.search(/session has timed-out/) > -1) {
+                send_command('USERLIST', 'ALL');
+            }
         } else if (msg.to_user == 'SERVER') {
             if (msg.body == 'LOGOFF') {
                 uidx = state.nicks.indexOf(msg.from_user);
@@ -97,8 +104,13 @@ function MRC_Session(host, port, user, pass, alias) {
                     state.nicks.splice(uidx, 1);
                     emit('nicks', state.nicks);
                 }
-            }
+            } 
         } else if (msg.to_user == '' || user.toLowerCase() == msg.to_user.toLowerCase()) {
+            
+            if (user.toLowerCase() == msg.to_user.toLowerCase()) {
+                state.last_private_msg_from = msg.from_user;
+            }
+            
             if (msg.to_room == '' || msg.to_room == state.room) {
                 emit('message', msg);
             }
@@ -112,9 +124,9 @@ function MRC_Session(host, port, user, pass, alias) {
                     state.nicks.splice(uidx, 1);
                     emit('nicks', state.nicks);
                 }
-            } else if (msg.body.search(/just joined room/) > -1) {
-                send_command('USERLIST', 'ALL');
-            }
+            } /*else if (msg.body.search(/just joined room/) > -1) {
+                send_command('USERLIST', 'ALL'); // moved above to msg.from_user == 'SERVER'
+            }*/
             emit('message', msg);
         }
     }
@@ -122,6 +134,10 @@ function MRC_Session(host, port, user, pass, alias) {
     this.send_room_message = function (msg) {
         send_message('', state.room, msg);
     }
+    
+    this.send_notme = function (msg) {
+        send("NOTME", "", "", msg);
+    }
 
     this.send_private_messsage = function (user, msg) {
         msg = '|11(|03Private|11)|07 ' + msg;
@@ -140,7 +156,11 @@ function MRC_Session(host, port, user, pass, alias) {
             password: pass,
             alias: state.alias
         }) + '\r\n');
+
+        this.send_command('IAMHERE');
+        mswait(20);
         this.send_command('USERLIST', 'ALL');
+        mswait(20);
         this.send_command('STATS', 'ALL');
     }
 
@@ -181,50 +201,53 @@ function MRC_Session(host, port, user, pass, alias) {
     }
 
     const commands = {
-        banners: {
-            help: 'List of banners from server' // Doesn't do anything?
-        },
-        chatters: {
-            help: 'List current users'
-        },
-        bbses: {
-            help: 'List connected BBSs',
-            command: 'CONNECTED'
-        },
+        
+        // This section has been cleaned up significantly.
+        //         
+        // Server commands no longer need to be strictly defined 
+        // here.
+        //
+        // Some commands are still defined if they have specific 
+        // function calls attached to them in mrc-client.js 
+        // (e.g. /motd) and or are local command shortcuts 
+        // (e.g. /t and /r).
+        //
+        // Any command not defined here (or in mrc-client.js) will
+        // be assumed to be a server command. The server will 
+        // inform the user if a command is invalid.
+        //
+        // The "help" properties are no longer needed, since 
+        // help is contained in external .msg file. They have been
+        // left commended out to provide reference.
+        // 
+        
         help: {
-            help: 'Display this help message',
+            //help: 'Display this help message',
             callback: function (str) {
-                emit('help', 'List of available commands:', '');
-                for (var c in commands) {
-                    emit('help', c, commands[c].help, commands[c].ars);
-                }
-                emit('local_help');
+                emit('local_help', str);
             }
         },
         info: {
-            help: 'View information about a BBS (/info #)',
+            //help: 'View information about a BBS (/info #)',
             callback: function (str) {
                 this.send_command('INFO ' + str);
             }
         },
-        join: {
-            help: 'Move to a new room: /join room_name',
+        join: {            
+            //help: 'Move to a new room: /join room_name',
             callback: function (str) { // validate valid room name?
-                str = str.replace(/^#/, '');
+                str = str.replace(/^#/, '');                
                 this.send_command(format('NEWROOM:%s:%s', state.room, str));
                 state.room = str;
                 state.nicks = [];
                 this.send_command('USERLIST', 'ALL');
             }
         },
-        meetups: {
-            help: 'Display information about upcoming meetups'
-        },
         motd: {
-            help: 'Display the Message of the Day'
+            //help: 'Display the Message of the Day'
         },
-        msg: {
-            help: 'Send a private message: /msg nick message goes here',
+        msg: { // This is largely overtaken by /t, but we'll still handle it.
+            //help: 'Send a private message: /msg nick message goes here',
             callback: function (str) {
                 const cmd = str.split(' ');
                 if (cmd.length > 1 && state.nicks.indexOf(cmd[0]) > -1) {
@@ -232,8 +255,8 @@ function MRC_Session(host, port, user, pass, alias) {
                 }
             }
         },
-        t: {                                                                       // added as shorthand for /msg
-            help: 'Shorthand for /msg: /t nick message goes here',
+        t: {
+            //help: 'Send a private message: /t nick message goes here',
             callback: function (str) {
                 const cmd = str.split(' ');
                 if (cmd.length > 1 && state.nicks.indexOf(cmd[0]) > -1) {
@@ -241,70 +264,41 @@ function MRC_Session(host, port, user, pass, alias) {
                 }
             }
         },
+        r: {
+            //help: 'Reply to last private message: /r message goes here',
+            callback: function (str) {
+                if (state.last_private_msg_from) {
+                    this.send_private_messsage(state.last_private_msg_from, str);
+                }
+            }
+        },        
         quote: {
-            help: 'Send a raw command to the server',
+            //help: 'Send a raw command to the server',
             callback: function (str) {
                 this.send_command(str);
             }
         },
         quit: {
-            help: 'Quit the program',
+            //help: 'Quit the program',
             callback: function () {
-                handle.close();
                 emit('disconnect');
+                handle.close();
             }
         },
         rooms: {
-            help: 'List available rooms',
+            //help: 'List available rooms',
             command: 'LIST'
         },
         stats: {
-            help: 'Return anonymous server stats',
+            //help: 'Return anonymous server stats',
             command: 'statistics'
         },
         topic: {
-            help: 'Change the topic of the current room',
+            //help: 'Change the topic of the current room',
             callback: function (str) {
                 this.send_command(format('NEWTOPIC:%s:%s', state.room, str));
             }
-        },
-        users: {
-            help: 'Display list of users'
-        },
-        whoon: {
-            help: 'Display list of users and BBSs'
-        },
-        afk: {
-            help: "Set yourself AFK (Shortcut for STATUS AFK)",
-            callback: function (str) {
-                this.send_command('AFK ' + str);
-            }
-        },
-        register: {
-            help: "Register handle on server (MRC Trust)",
-            callback: function (str) {
-                this.send_command('REGISTER ' + str);
-            }
-        },
-        identify: {
-            help: "Identify as a registered user (MRC Trust)",
-            callback: function (str) {
-                this.send_command('IDENTIFY ' + str);
-            }
-        },
-        update: {
-            help: "Update user registration (MRC Trust)",
-            callback: function (str) {
-                this.send_command('UPDATE ' + str);
-            }
-        },
-        trust: {
-            help: "MRC Trust Info (MRC Trust)",
-            callback: function (str) {
-                this.send_command('TRUST ' + str);
-            }
-        }
-        
+        }          
     };
 
     Object.keys(commands).forEach(function (e) {
@@ -312,11 +306,11 @@ function MRC_Session(host, port, user, pass, alias) {
             this[e] = commands[e].callback;
         } else if (commands[e].command) {
             this[e] = function () {
-                this.send_command(commands[e].command.toUpperCase());
+                this.send_command(commands[e].command);
             }
         } else {
             this[e] = function () {
-                this.send_command(e.toUpperCase());
+                this.send_command(e);
             }
         }
     }, this);
diff --git a/xtrn/mrc/mrc-splash.msg b/xtrn/mrc/mrc-splash.msg
new file mode 100644
index 0000000000000000000000000000000000000000..a47724bd3ea8c009dd0ff6a9f65b6251a1648f7a
--- /dev/null
+++ b/xtrn/mrc/mrc-splash.msg
@@ -0,0 +1,8 @@
+N�HK.N HK.N . HK~N HK~N HK~N HK.N HK~N HK~N HK.N HK~N HK~N HK~N HK~N HK.N HK~N HK~N HK~N . HK.N HK.
+N HK.N HK.N . H.N HB88888b.d88b.N  HB888d888 .d8888b W.N .HK .N HK.
+ .N .HK W.K NB.W B8H88N HB"888N HB"88b NB8H88P"NB  dH88P"N�B.HK W.N . HK.
+ N.HK W.K NB.W B.W B8H88N  B8H88  NB8H88 NB8H88N�B8H88NB�.W B.HK W.N .
+HK . N.HK W.K NB.W B8H88N  B8H88  NB8H88 NB8H88N�BY8H8b.N�B.HK W.N . HK.
+ . .N . H.N B8H88N  B8H88  NB8H88 NB8H88N�B"Y8H888  W.N . HK.N HK.
+ ` .N HK.N  HK=N==H=========================N==HK=N  HK.N HK. '
+N�HK-=[N HCMNCulti HRNCelay HCNChat forW HCSynchronetN HK]=-
diff --git a/xtrn/mrc/readme.txt b/xtrn/mrc/readme.txt
index 4fb29996f63965c8e915f200e571ae917ebc9a88..8f8482e6699069235acd3312d5bc1063d8f1466f 100644
--- a/xtrn/mrc/readme.txt
+++ b/xtrn/mrc/readme.txt
@@ -60,7 +60,6 @@ colour codes are permitted:
 
   http://wiki.mysticbbs.com/doku.php?id=displaycodes#color_codes_pipe_colors
 
-I may write a CTRL-A to pipe converter at some later date.
 
 mrc-connector.ini:
 
@@ -77,11 +76,16 @@ mrc-client.ini:
     above instructions while editing '/sbbs/ctrl/services.ini'.
   - The 'ping_interval' setting should be left at the default value unless you
     have a good reason for changing it.
-  - The values in the [startup] section determine which room the client joins
-    on startup, and whether the Message of the Day and banners are displayed.
-  - Change show_nicks in the [client] section to always display the nick list
-    when connecting to the MRC server.
-
+  - The values in the [startup] section control the following:
+      room:     Which room the client joins on startup.
+      motd:     Whether the Message of the Day is displayed.
+      commands: Any additional commands to execute, eg: "chatters" to display
+                the list of active users in the room on startup. Use commas 
+                to separate multiple commands.
+      splash:   Whether the "Multi Relay Chat for Synchronet" splash is shown
+                on startup.
+  - The [aliases], [theme], [msg_color], [show_nicks], and [twit_list] sections
+    are for user settings which get added or updated any time a user uses them.
 
 
 4) MRC Stats
@@ -123,19 +127,35 @@ see "Yes" next to your BBS in the SSL column.
 6) Themes
 
 MRC comes with several customizable theme files. These can be added/edited as
-the system wishes. The client automatically detects any available theme files 
+the sysop wishes. The client automatically detects any available theme files 
 matching the pattern:
 
     mrc-theme-<theme_name>.ini
 
-Available themes are listed in /help. User selects a theme by typing 
+Available themes are listed in /help theme. User selects a theme by typing 
 /theme <name>, and the selected theme gets saved to settings for future
 sessions.
 
 If a theme file is not available, the classic blue theme gets used by default.
 
+To customize the splash that gets shown on startup, edit the mrc-splash.msg
+file. Or just disable it by setting splash = false in mrc-client.ini
 
-7) Support
+
+7) Known issues
+
+- Outgoing messages beginning with a semicolon do not get sent out to the MRC
+  server. For example, a winking face emoticon:  ;)  or  ;-)
+  
+  This is because the ; character is used by the inputline.js library to 
+  indicate the start of a sysop command. In the case of the MRC client, such
+  text is simply ignored and does not get sent over.
+  
+  As a workround, you may simply insert a space in front of the message you 
+  want to send that starts with a semicolon.
+
+
+8) Support
 
 - Post a message to 'echicken' in the Synchronet Sysops area on DOVE-Net
 - Find me on irc.synchro.net in #synchronet