diff --git a/src/d_main.c b/src/d_main.c
index ebc618be849428912e6d3ed8f297ff3b86a02f2c..9ffed4461242723fe72a27044d61bf76986b45d5 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -435,6 +435,15 @@ static void D_Display(void)
 		if (rendermode != render_none)
 		{
 			F_WipeEndScreen();
+			// Funny.
+			if (WipeStageTitle && st_overlay)
+			{
+				lt_ticker--;
+				lt_lasttic = lt_ticker;
+				ST_preLevelTitleCardLoop(0, false);
+				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol);
+				F_WipeStartScreen();
+			}
 			F_RunWipe(wipetypepost, gamestate != GS_TIMEATTACK && gamestate != GS_TITLESCREEN);
 		}
 
@@ -444,7 +453,7 @@ static void D_Display(void)
 			framecount = 0;
 			demostarttime = I_GetTime();
 		}
-		
+
 		wipetypepost = -1;
 	}
 	else
diff --git a/src/f_finale.h b/src/f_finale.h
index 3fa7106a98e867909b077ccfd591879d1e346131..a2a96f2de2577ec441661d3356d0a0d03ca1637a 100644
--- a/src/f_finale.h
+++ b/src/f_finale.h
@@ -141,8 +141,13 @@ void F_MenuPresTicker(boolean run);
 #define FORCEWIPEOFF -2
 
 extern boolean WipeInAction;
+extern boolean WipeStageTitle;
 extern INT32 lastwipetic;
 
+// Don't know where else to place this constant
+// But this file seems appropriate
+#define PRELEVELTIME 24 // frames in tics
+
 void F_WipeStartScreen(void);
 void F_WipeEndScreen(void);
 void F_RunWipe(UINT8 wipetype, boolean drawMenu);
diff --git a/src/f_wipe.c b/src/f_wipe.c
index 05229f844dfd6f11bc59311f351cff335d50b30f..d048037d8d914eca193fd5581d4d0e9cdc244975 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -18,6 +18,7 @@
 
 #include "r_draw.h" // transtable
 #include "p_pspr.h" // tr_transxxx
+#include "st_stuff.h"
 #include "w_wad.h"
 #include "z_zone.h"
 
@@ -31,6 +32,10 @@
 #include "hardware/hw_main.h"
 #endif
 
+#ifdef HAVE_BLUA
+#include "lua_hud.h"
+#endif
+
 #if NUMSCREENS < 5
 #define NOWIPE // do not enable wipe image post processing for ARM, SH and MIPS CPUs
 #endif
@@ -82,6 +87,7 @@ UINT8 wipedefs[NUMWIPEDEFS] = {
 //--------------------------------------------------------------------------
 
 boolean WipeInAction = false;
+boolean WipeStageTitle = false;
 INT32 lastwipetic = 0;
 
 #ifndef NOWIPE
@@ -366,6 +372,19 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
 		else
 #endif
 		F_DoWipe(fmask);
+
+		// draw level title
+		if ((WipeStageTitle && st_overlay)
+		&& *mapheaderinfo[gamemap-1]->lvlttl != '\0'
+#ifdef HAVE_BLUA
+		&& LUA_HudEnabled(hud_stagetitle)
+#endif
+		)
+		{
+			ST_runTitleCard();
+			ST_drawWipeTitleCard();
+		}
+
 		I_OsPolling();
 		I_UpdateNoBlit();
 
@@ -377,7 +396,9 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
 		if (moviemode)
 			M_SaveFrame();
 	}
+
 	WipeInAction = false;
+	WipeStageTitle = false;
 #endif
 }
 
diff --git a/src/g_game.c b/src/g_game.c
index 63caaa15cb0879a6f6c1c495784eac116b3f33bb..2119c3fe0d9ae7b14c6a0f8cf68bdbfc96f1e1d6 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1992,7 +1992,7 @@ void G_Ticker(boolean run)
 			if (titledemo)
 				F_TitleDemoTicker();
 			P_Ticker(run); // tic the game
-			ST_Ticker();
+			ST_Ticker(run);
 			F_TextPromptTicker();
 			AM_Ticker();
 			HU_Ticker();
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index e493dafacbb74406517bc3a4425de717ed11db6e..4519e1280018d4f69015cba9e32b4df015d190bb 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -380,9 +380,9 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t
 	{
 		FSurfaceInfo Surf;
 		Surf.FlatColor.s.red = Surf.FlatColor.s.green = Surf.FlatColor.s.blue = 0xff;
-		if (alphalevel == 13) Surf.FlatColor.s.alpha = softwaretranstogl_lo[cv_translucenthud.value];
-		else if (alphalevel == 14) Surf.FlatColor.s.alpha = softwaretranstogl[cv_translucenthud.value];
-		else if (alphalevel == 15) Surf.FlatColor.s.alpha = softwaretranstogl_hi[cv_translucenthud.value];
+		if (alphalevel == 13) Surf.FlatColor.s.alpha = softwaretranstogl_lo[st_translucency];
+		else if (alphalevel == 14) Surf.FlatColor.s.alpha = softwaretranstogl[st_translucency];
+		else if (alphalevel == 15) Surf.FlatColor.s.alpha = softwaretranstogl_hi[st_translucency];
 		else Surf.FlatColor.s.alpha = softwaretranstogl[10-alphalevel];
 		flags |= PF_Modulated;
 		HWD.pfnDrawPolygon(&Surf, v, 4, flags);
@@ -538,9 +538,9 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 	{
 		FSurfaceInfo Surf;
 		Surf.FlatColor.s.red = Surf.FlatColor.s.green = Surf.FlatColor.s.blue = 0xff;
-		if (alphalevel == 13) Surf.FlatColor.s.alpha = softwaretranstogl_lo[cv_translucenthud.value];
-		else if (alphalevel == 14) Surf.FlatColor.s.alpha = softwaretranstogl[cv_translucenthud.value];
-		else if (alphalevel == 15) Surf.FlatColor.s.alpha = softwaretranstogl_hi[cv_translucenthud.value];
+		if (alphalevel == 13) Surf.FlatColor.s.alpha = softwaretranstogl_lo[st_translucency];
+		else if (alphalevel == 14) Surf.FlatColor.s.alpha = softwaretranstogl[st_translucency];
+		else if (alphalevel == 15) Surf.FlatColor.s.alpha = softwaretranstogl_hi[st_translucency];
 		else Surf.FlatColor.s.alpha = softwaretranstogl[10-alphalevel];
 		flags |= PF_Modulated;
 		HWD.pfnDrawPolygon(&Surf, v, 4, flags);
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 62be6128300eac40493c55cfebbefe78cf40d731..f12bb9ec56994b56ae9592aad3b857ef9abe7df8 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -910,9 +910,17 @@ static int libd_RandomChance(lua_State *L)
 	return 1;
 }
 
-// 30/10/18 Lat': Get cv_translucenthud's value for HUD rendering as a normal V_xxTRANS int
+// 30/10/18 Lat': Get st_translucency's value for HUD rendering as a normal V_xxTRANS int
 // Could as well be thrown in global vars for ease of access but I guess it makes sense for it to be a HUD fn
 static int libd_getlocaltransflag(lua_State *L)
+{
+	HUDONLY
+	lua_pushinteger(L, (10-st_translucency)*V_10TRANS);
+	return 1;
+}
+
+// Get cv_translucenthud's value for HUD rendering as a normal V_xxTRANS int
+static int libd_getusertransflag(lua_State *L)
 {
 	HUDONLY
 	lua_pushinteger(L, (10-cv_translucenthud.value)*V_10TRANS);	// A bit weird that it's called "translucenthud" yet 10 is fully opaque :V
@@ -954,6 +962,7 @@ static luaL_Reg lib_draw[] = {
 	{"dupy", libd_dupy},
 	{"renderer", libd_renderer},
 	{"localTransFlag", libd_getlocaltransflag},
+	{"userTransFlag", libd_getusertransflag},
 	{NULL, NULL}
 };
 
diff --git a/src/p_setup.c b/src/p_setup.c
index 461c81b05e0b76e630afc30837b547f34be9f87f..736141ea01125be0ee183aad66d4e327151d2e43 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -3227,8 +3227,10 @@ boolean P_SetupLevel(boolean skipprecip)
 	}
 
 	// Stage title!
+	WipeStageTitle = (!titlemapinaction);
+	ST_startTitleCard();
 	if (rendermode != render_none
-		&& (!titlemapinaction)
+		&& WipeStageTitle
 		&& ranspecialwipe != 2
 		&& *mapheaderinfo[gamemap-1]->lvlttl != '\0'
 #ifdef HAVE_BLUA
@@ -3236,33 +3238,8 @@ boolean P_SetupLevel(boolean skipprecip)
 #endif
 	)
 	{
-		tic_t starttime = I_GetTime();
-		tic_t endtime = starttime + (10*NEWTICRATERATIO);
-		tic_t nowtime = starttime;
-		tic_t lasttime = starttime;
-		while (nowtime < endtime)
-		{
-			// draw loop
-			while (!((nowtime = I_GetTime()) - lasttime))
-				I_Sleep();
-			lasttime = nowtime;
-
-			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol);
-			stplyr = &players[consoleplayer];
-			ST_drawLevelTitle(nowtime - starttime);
-			if (splitscreen)
-			{
-				stplyr = &players[secondarydisplayplayer];
-				ST_drawLevelTitle(nowtime - starttime);
-			}
-
-			I_OsPolling();
-			I_UpdateNoBlit();
-			I_FinishUpdate(); // page flip or blit buffer
-
-			if (moviemode) // make sure we save frames for the white hold too
-				M_SaveFrame();
-		}
+		ST_runTitleCard();
+		ST_runPreLevelTitleCard(lt_ticker);
 	}
 
 	return true;
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 8844773779fdaaa69045fa4c3ea6da04c2e51506..86a87c543cc4ccb454768612604dbe95356befc7 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -186,14 +186,18 @@ boolean ST_SameTeam(player_t *a, player_t *b)
 
 static boolean st_stopped = true;
 
-void ST_Ticker(void)
+void ST_Ticker(boolean run)
 {
 	if (st_stopped)
 		return;
+
+	if (run)
+		ST_runTitleCard();
 }
 
 // 0 is default, any others are special palettes.
 INT32 st_palette = 0;
+INT32 st_translucency = 0;
 
 void ST_doPaletteStuff(void)
 {
@@ -818,7 +822,7 @@ static void ST_drawLivesArea(void)
 			face = superprefix[stplyr->skin];
 		V_DrawSmallMappedPatch(hudinfo[HUD_LIVES].x, hudinfo[HUD_LIVES].y,
 			hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, face, colormap);
-		if (cv_translucenthud.value == 10 && stplyr->powers[pw_super] == 1 && stplyr->mo->tracer)
+		if (st_translucency == 10 && stplyr->powers[pw_super] == 1 && stplyr->mo->tracer)
 		{
 			INT32 v_supertrans = (stplyr->mo->tracer->frame & FF_TRANSMASK) >> FF_TRANSSHIFT;
 			if (v_supertrans < 10)
@@ -1160,20 +1164,132 @@ static void ST_drawInput(void)
 		V_DrawThinString(x, y, hudinfo[HUD_LIVES].f|((leveltime & 4) ? V_YELLOWMAP : V_REDMAP), "BAD DEMO!!");
 }
 
-void ST_drawLevelTitle(tic_t titletime)
+static patch_t *lt_patches[3];
+static INT32 lt_scroll = 0;
+static INT32 lt_mom = 0;
+static INT32 lt_zigzag = 0;
+
+tic_t lt_ticker = 0, lt_lasttic = 0;
+tic_t lt_exitticker = 0, lt_endtime = 0;
+
+//
+// Load the graphics for the title card.
+//
+static void ST_cacheLevelTitle(void)
+{
+	SINT8 bonustype = mapheaderinfo[gamemap-1]->bonustype;
+	if ((bonustype != 1) && (bonustype != 2))
+	{
+		lt_patches[0] = (patch_t *)W_CachePatchName("LTACTBLU", PU_HUDGFX);
+		lt_patches[1] = (patch_t *)W_CachePatchName("LTZIGZAG", PU_HUDGFX);
+		lt_patches[2] = (patch_t *)W_CachePatchName("LTZZTEXT", PU_HUDGFX);
+	}
+	else // boss map
+	{
+		lt_patches[0] = (patch_t *)W_CachePatchName("LTACTRED", PU_HUDGFX);
+		lt_patches[1] = (patch_t *)W_CachePatchName("LTZIGRED", PU_HUDGFX);
+		lt_patches[2] = (patch_t *)W_CachePatchName("LTZZWARN", PU_HUDGFX);
+	}
+}
+
+//
+// Start the title card.
+//
+void ST_startTitleCard(void)
+{
+	ST_cacheLevelTitle();
+	lt_ticker = lt_exitticker = lt_lasttic = 0;
+	lt_endtime = 2*TICRATE;
+	lt_scroll = BASEVIDWIDTH * FRACUNIT;
+	lt_zigzag = -((lt_patches[1])->width * FRACUNIT);
+	lt_mom = 0;
+}
+
+//
+// What happens before drawing the title card.
+// Which is just setting the HUD translucency.
+//
+void ST_preDrawTitleCard(void)
+{
+	if (lt_ticker >= (lt_endtime + TICRATE))
+		return;
+
+	if (!lt_exitticker)
+		st_translucency = 0;
+	else
+		st_translucency = max(0, min((INT32)lt_exitticker-4, cv_translucenthud.value));
+}
+
+//
+// Run the title card.
+// Called from ST_Ticker.
+//
+void ST_runTitleCard(void)
+{
+	if (lt_ticker >= (lt_endtime + TICRATE))
+		return;
+
+	if (!(paused || P_AutoPause()))
+	{
+		// tick
+		lt_ticker++;
+		if (lt_ticker >= lt_endtime)
+			lt_exitticker++;
+
+		// scroll to screen (level title)
+		if (!lt_exitticker)
+		{
+			if (abs(lt_scroll) > FRACUNIT)
+				lt_scroll -= (lt_scroll>>2);
+			else
+				lt_scroll = 0;
+		}
+		// scroll away from screen (level title)
+		else
+		{
+			lt_mom -= FRACUNIT*6;
+			lt_scroll += lt_mom;
+		}
+
+		// scroll to screen (zigzag)
+		if (!lt_exitticker)
+		{
+			if (abs(lt_zigzag) > FRACUNIT)
+				lt_zigzag -= (lt_zigzag>>2);
+			else
+				lt_zigzag = 0;
+		}
+		// scroll away from screen (zigzag)
+		else
+			lt_zigzag += lt_mom;
+	}
+}
+
+//
+// Draw the title card itself.
+//
+void ST_drawTitleCard(void)
 {
 	char *lvlttl = mapheaderinfo[gamemap-1]->lvlttl;
 	char *subttl = mapheaderinfo[gamemap-1]->subttl;
 	INT32 actnum = mapheaderinfo[gamemap-1]->actnum;
-	INT32 lvlttly, zoney, lvlttlxpos, ttlnumxpos, zonexpos;
+	INT32 lvlttlxpos, ttlnumxpos, zonexpos;
 	INT32 subttlxpos = BASEVIDWIDTH/2;
-	patch_t *tzigzag = W_CachePatchName("TCARDFG1", PU_CACHE);
-	INT32 i;
-	INT32 height = (SHORT(tzigzag->height));
+	INT32 ttlscroll = FixedInt(lt_scroll);
+	INT32 zzticker;
+	patch_t *actpat, *zigzag, *zztext;
 
-	if (!(titletime > 2 && titletime-3 < 110))
+	if (lt_ticker >= (lt_endtime + TICRATE))
 		return;
 
+	if ((lt_ticker-lt_lasttic) > 1)
+		lt_ticker = lt_lasttic+1;
+
+	ST_cacheLevelTitle();
+	actpat = lt_patches[0];
+	zigzag = lt_patches[1];
+	zztext = lt_patches[2];
+
 	lvlttlxpos = ((BASEVIDWIDTH/2) - (V_LevelNameWidth(lvlttl)/2));
 
 	if (actnum > 0)
@@ -1186,72 +1302,78 @@ void ST_drawLevelTitle(tic_t titletime)
 	if (lvlttlxpos < 0)
 		lvlttlxpos = 0;
 
-#if 0 // toaster's experiment. srb2&toast.exe one day, maybe? Requires stuff below to be converted to fixed point.
-#define MIDTTLY 79
-#define MIDZONEY 105
-#define MIDDIFF 4
+	zzticker = lt_ticker;
+	V_DrawScaledPatch(FixedInt(lt_zigzag), (-zzticker) % zigzag->height, V_SNAPTOTOP|V_SNAPTOLEFT, zigzag);
+	V_DrawScaledPatch(FixedInt(lt_zigzag), (zigzag->height-zzticker) % zigzag->height, V_SNAPTOTOP|V_SNAPTOLEFT, zigzag);
+	V_DrawScaledPatch(FixedInt(lt_zigzag), (-zigzag->height+zzticker) % zztext->height, V_SNAPTOTOP|V_SNAPTOLEFT, zztext);
+	V_DrawScaledPatch(FixedInt(lt_zigzag), (zzticker) % zztext->height, V_SNAPTOTOP|V_SNAPTOLEFT, zztext);
 
-	if (titletime < 10)
-	{
-		fixed_t z = ((titletime - 3)<<FRACBITS)/7;
-		INT32 ttlh = V_LevelNameHeight(lvlttl);
-		zoney = (200<<FRACBITS) - ((200 - (MIDZONEY + MIDDIFF))*z);
-		lvlttly = ((MIDTTLY + ttlh - MIDDIFF)*z) - (ttlh<<FRACBITS);
-	}
-	else if (titletime < 105)
-	{
-		fixed_t z = (((titletime - 10)*MIDDIFF)<<(FRACBITS+1))/95;
-		zoney = ((MIDZONEY + MIDDIFF)<<FRACBITS) - z;
-		lvlttly = ((MIDTTLY - MIDDIFF)<<FRACBITS) + z;
-	}
-	else
+	if (actnum)
 	{
-		fixed_t z = ((titletime - 105)<<FRACBITS)/7;
-		INT32 zoneh = V_LevelNameHeight(M_GetText("ZONE"));
-		zoney = (MIDZONEY + zoneh - MIDDIFF)*(FRACUNIT - z) - (zoneh<<FRACBITS);
-		lvlttly = ((MIDTTLY + MIDDIFF)<<FRACBITS) + ((200 - (MIDTTLY + MIDDIFF))*z);
-	}
-
-#undef MIDTTLY
-#undef MIDZONEY
-#undef MIDDIFF
-#else
-	// There's no consistent algorithm that can accurately define the old positions
-	// so I just ended up resorting to a single switch statement to define them
-	switch (titletime-3)
-	{
-		case 0:   zoney = 200; lvlttly =   0; break;
-		case 1:   zoney = 188; lvlttly =  12; break;
-		case 2:   zoney = 176; lvlttly =  24; break;
-		case 3:   zoney = 164; lvlttly =  36; break;
-		case 4:   zoney = 152; lvlttly =  48; break;
-		case 5:   zoney = 140; lvlttly =  60; break;
-		case 6:   zoney = 128; lvlttly =  72; break;
-		case 105: zoney =  80; lvlttly = 104; break;
-		case 106: zoney =  56; lvlttly = 128; break;
-		case 107: zoney =  32; lvlttly = 152; break;
-		case 108: zoney =   8; lvlttly = 176; break;
-		case 109: zoney =   0; lvlttly = 200; break;
-		default:  zoney = 104; lvlttly =  80; break;
+		V_DrawScaledPatch(ttlnumxpos + ttlscroll, 104 - ttlscroll, 0, actpat);
+		V_DrawLevelActNum(ttlnumxpos + ttlscroll, 104, V_PERPLAYER, actnum);
 	}
-#endif
 
-	if (actnum)
-		V_DrawLevelActNum(ttlnumxpos, zoney, V_PERPLAYER, actnum);
+	V_DrawLevelTitle(lvlttlxpos - ttlscroll, 80, V_PERPLAYER, lvlttl);
+	if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
+		V_DrawLevelTitle(zonexpos + ttlscroll, 104, V_PERPLAYER, M_GetText("ZONE"));
+	V_DrawCenteredString(subttlxpos - ttlnumxpos, 128, V_PERPLAYER|V_ALLOWLOWERCASE, subttl);
 
-	V_DrawLevelTitle(lvlttlxpos, lvlttly, V_PERPLAYER, lvlttl);
+	lt_lasttic = lt_ticker;
+}
 
-	if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
-		V_DrawLevelTitle(zonexpos, zoney, V_PERPLAYER, M_GetText("ZONE"));
+//
+// Drawer for ST_runPreLevelTitleCard.
+//
+void ST_preLevelTitleCardLoop(tic_t ticker, boolean update)
+{
+	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol);
+	if (ticker < PRELEVELTIME-1)
+		ST_drawWipeTitleCard();
+
+	I_OsPolling();
+	I_UpdateNoBlit();
+	if (update)
+		I_FinishUpdate(); // page flip or blit buffer
 
-	if (lvlttly+48 < 200)
-		V_DrawCenteredString(subttlxpos, lvlttly+48, V_PERPLAYER|V_ALLOWLOWERCASE, subttl);
+	if (moviemode) // make sure we save frames for the white hold too
+		M_SaveFrame();
+}
+
+//
+// Run the title card before fading in to the level.
+//
+void ST_runPreLevelTitleCard(tic_t ticker)
+{
+	tic_t starttime = I_GetTime();
+	tic_t endtime = starttime + (PRELEVELTIME*NEWTICRATERATIO);
+	tic_t nowtime = starttime;
+	tic_t lasttime = starttime;
+	while (nowtime < endtime)
+	{
+		// draw loop
+		while (!((nowtime = I_GetTime()) - lasttime))
+			I_Sleep();
+		lasttime = nowtime;
+		ST_runTitleCard();
+		ST_preLevelTitleCardLoop(ticker, true);
+	}
+}
 
-	for (i = -8; i < (BASEVIDHEIGHT/height) + 8; i++)
+//
+// Draw the title card while on a wipe.
+// Also used in ST_runPreLevelTitleCard.
+//
+void ST_drawWipeTitleCard(void)
+{
+	stplyr = &players[consoleplayer];
+	ST_preDrawTitleCard();
+	ST_drawTitleCard();
+	if (splitscreen)
 	{
-		INT32 y = ((i*height) + (height - ((titletime)%height)));
-		//CONS_Printf("%d\n", y);
-		V_DrawFixedPatch(0, y<<FRACBITS, FRACUNIT, V_SNAPTOLEFT|V_PERPLAYER, tzigzag, NULL);
+		stplyr = &players[secondarydisplayplayer];
+		ST_preDrawTitleCard();
+		ST_drawTitleCard();
 	}
 }
 
@@ -2404,12 +2526,29 @@ static void ST_doItemFinderIconsAndSound(void)
 		S_StartSound(NULL, sfx_emfind);
 }
 
+//
 // Draw the status bar overlay, customisable: the user chooses which
 // kind of information to overlay
 //
 static void ST_overlayDrawer(void)
 {
-	//hu_showscores = auto hide score/time/rings when tab rankings are shown
+	// Decide whether to draw the stage title or not
+	boolean stagetitle = false;
+
+	// Check for a valid level title
+	// If the HUD is enabled
+	// And, if Lua is running, if the HUD library has the stage title enabled
+	if (*mapheaderinfo[gamemap-1]->lvlttl != '\0' && !(hu_showscores && (netgame || multiplayer))
+#ifdef HAVE_BLUA
+	&& LUA_HudEnabled(hud_stagetitle)
+#endif
+	)
+	{
+		stagetitle = true;
+		ST_preDrawTitleCard();
+	}
+
+	// hu_showscores = auto hide score/time/rings when tab rankings are shown
 	if (!(hu_showscores && (netgame || multiplayer)))
 	{
 		if ((maptol & TOL_NIGHTS || G_IsSpecialStage(gamemap)) &&
@@ -2560,12 +2699,8 @@ static void ST_overlayDrawer(void)
 #endif
 
 	// draw level title Tails
-	if (*mapheaderinfo[gamemap-1]->lvlttl != '\0' && !(hu_showscores && (netgame || multiplayer))
-#ifdef HAVE_BLUA
-	&& LUA_HudEnabled(hud_stagetitle)
-#endif
-	)
-		ST_drawLevelTitle(timeinmap+70);
+	if (stagetitle && (!WipeInAction) && (!WipeStageTitle))
+		ST_drawTitleCard();
 
 	if (!hu_showscores && (netgame || multiplayer)
 #ifdef HAVE_BLUA
@@ -2641,6 +2776,8 @@ void ST_Drawer(void)
 		}
 	}
 
+	st_translucency = cv_translucenthud.value;
+
 	if (st_overlay)
 	{
 		// No deadview!
diff --git a/src/st_stuff.h b/src/st_stuff.h
index aaf01ca15a2c67ad7bfb84859d4df8efde9505fd..7c23d4f94830866cfafba75ad1e16566c226b97e 100644
--- a/src/st_stuff.h
+++ b/src/st_stuff.h
@@ -24,7 +24,7 @@
 //
 
 // Called by main loop.
-void ST_Ticker(void);
+void ST_Ticker(boolean run);
 
 // Called by main loop.
 void ST_Drawer(void);
@@ -47,8 +47,17 @@ void ST_ReloadSkinFaceGraphics(void);
 
 void ST_doPaletteStuff(void);
 
-// level title draw
-void ST_drawLevelTitle(tic_t titletime);
+// title card
+void ST_startTitleCard(void);
+void ST_preDrawTitleCard(void);
+void ST_runTitleCard(void);
+void ST_drawTitleCard(void);
+void ST_preLevelTitleCardLoop(tic_t ticker, boolean update);
+void ST_runPreLevelTitleCard(tic_t ticker);
+void ST_drawWipeTitleCard(void);
+
+extern tic_t lt_ticker, lt_lasttic;
+extern tic_t lt_exitticker, lt_endtime;
 
 // return if player a is in the same team as player b
 boolean ST_SameTeam(player_t *a, player_t *b);
@@ -59,6 +68,7 @@ boolean ST_SameTeam(player_t *a, player_t *b);
 
 extern boolean st_overlay; // sb overlay on or off when fullscreen
 extern INT32 st_palette; // 0 is default, any others are special palettes.
+extern INT32 st_translucency;
 
 extern lumpnum_t st_borderpatchnum;
 // patches, also used in intermission
diff --git a/src/v_video.c b/src/v_video.c
index 44e80c9f5698ebed10d0172eaeec425387898a50..a9f60fff1a510549feaecd2649e022f9035504c1 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -18,6 +18,7 @@
 #include "p_local.h" // stplyr
 #include "g_game.h" // players
 #include "v_video.h"
+#include "st_stuff.h"
 #include "hu_stuff.h"
 #include "r_draw.h"
 #include "console.h"
@@ -574,11 +575,11 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 	if ((alphalevel = ((scrn & V_ALPHAMASK) >> V_ALPHASHIFT)))
 	{
 		if (alphalevel == 13)
-			alphalevel = hudminusalpha[cv_translucenthud.value];
+			alphalevel = hudminusalpha[st_translucency];
 		else if (alphalevel == 14)
-			alphalevel = 10 - cv_translucenthud.value;
+			alphalevel = 10 - st_translucency;
 		else if (alphalevel == 15)
-			alphalevel = hudplusalpha[cv_translucenthud.value];
+			alphalevel = hudplusalpha[st_translucency];
 
 		if (alphalevel >= 10)
 			return; // invis
@@ -874,11 +875,11 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 	if ((alphalevel = ((scrn & V_ALPHAMASK) >> V_ALPHASHIFT)))
 	{
 		if (alphalevel == 13)
-			alphalevel = hudminusalpha[cv_translucenthud.value];
+			alphalevel = hudminusalpha[st_translucency];
 		else if (alphalevel == 14)
-			alphalevel = 10 - cv_translucenthud.value;
+			alphalevel = 10 - st_translucency;
 		else if (alphalevel == 15)
-			alphalevel = hudplusalpha[cv_translucenthud.value];
+			alphalevel = hudplusalpha[st_translucency];
 
 		if (alphalevel >= 10)
 			return; // invis
@@ -1393,11 +1394,11 @@ void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 	if ((alphalevel = ((c & V_ALPHAMASK) >> V_ALPHASHIFT)))
 	{
 		if (alphalevel == 13)
-			alphalevel = hudminusalpha[cv_translucenthud.value];
+			alphalevel = hudminusalpha[st_translucency];
 		else if (alphalevel == 14)
-			alphalevel = 10 - cv_translucenthud.value;
+			alphalevel = 10 - st_translucency;
 		else if (alphalevel == 15)
-			alphalevel = hudplusalpha[cv_translucenthud.value];
+			alphalevel = hudplusalpha[st_translucency];
 
 		if (alphalevel >= 10)
 			return; // invis
@@ -2964,7 +2965,7 @@ INT32 V_LevelNameHeight(const char *string)
 	return w;
 }
 
-// For ST_drawLevelTitle
+// For ST_drawTitleCard
 // Returns the width of the act num patch
 INT32 V_LevelActNumWidth(INT32 num)
 {