diff --git a/AccessQueuePlayground/AccessQueuePlayground.csproj b/AccessQueuePlayground/AccessQueuePlayground.csproj
new file mode 100644
index 0000000..4a7df85
--- /dev/null
+++ b/AccessQueuePlayground/AccessQueuePlayground.csproj
@@ -0,0 +1,17 @@
+
+
+  
+    net8.0 
+    enable 
+    enable 
+   
+
+  
+     
+
+  
+     
+
+ 
diff --git a/AccessQueuePlayground/Components/App.razor b/AccessQueuePlayground/Components/App.razor
new file mode 100644
index 0000000..58fede0
--- /dev/null
+++ b/AccessQueuePlayground/Components/App.razor
@@ -0,0 +1,23 @@
+
+
+
+
+    
+    An unhandled error has occurred.
+    
Reload 
+    
🗙 
+
Error 
+
+Error. 
+An error occurred while processing your request. 
+
+@if (ShowRequestId)
+{
+    
+        Request ID:  @RequestId
+    
+}
+
+Development Mode 
+
+    Swapping to Development  environment will display more detailed information about the error that occurred.
+
+
+    The Development environment shouldn't be enabled for deployed applications. 
+    It can result in displaying sensitive information from exceptions to end users.
+    For local debugging, enable the Development  environment by setting the ASPNETCORE_ENVIRONMENT  environment variable to Development 
+    and restarting the app.
+
+
+@code{
+    [CascadingParameter]
+    private HttpContext? HttpContext { get; set; }
+
+    private string? RequestId { get; set; }
+    private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
+
+    protected override void OnInitialized() =>
+        RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
+}
diff --git a/AccessQueuePlayground/Components/Pages/Home.razor b/AccessQueuePlayground/Components/Pages/Home.razor
new file mode 100644
index 0000000..33cd1d9
--- /dev/null
+++ b/AccessQueuePlayground/Components/Pages/Home.razor
@@ -0,0 +1,95 @@
+@page "/"
+@using AccessQueuePlayground.Models
+@using AccessQueuePlayground.Services
+@using BlazorBootstrap
+
+@inject IAccessQueueManager Manager
+
+AccessQueue Playground 
+
+Add User 
+Refresh 
+
+@if (Status != null)
+{
+	Users with access 
+	
+		
+			
+				@context.Id
+			 
+			
+				@context.LatestResponse?.ExpiresOn
+			 
+			
+				
+					 
+			 
+		 
+	 
+	Users in queue 
+	
+		
+			
+				@context.Id
+			 
+			
+				@(context.LatestResponse?.RequestsAhead ?? 0 + 1)
+			 
+			
+				
+					 
+			 
+		 
+	 
+	Inactive users 
+	
+		
+			
+				@context.Id
+			 
+			
+				
+					 
+			 
+		 
+	 
+}
+
+@code {
+	public AccessQueueStatus? Status;
+
+	protected override void OnInitialized()
+	{
+		Manager.StatusUpdated += OnStatusUpdated;
+		Status = Manager.GetStatus();
+	}
+
+	private void OnStatusUpdated()
+	{
+		InvokeAsync(() =>
+		{
+			Status = Manager.GetStatus();
+			StateHasChanged();
+		});
+	}
+
+	public void Refresh()
+	{
+		Status = Manager.GetStatus();
+	}
+
+	public void AddUser()
+	{
+		Manager.AddUser();
+		Status = Manager.GetStatus();
+	}
+
+	public void ToggleUserActive(Guid userId)
+	{
+		Manager.ToggleUserActivity(userId);
+	}
+}
\ No newline at end of file
diff --git a/AccessQueuePlayground/Components/Routes.razor b/AccessQueuePlayground/Components/Routes.razor
new file mode 100644
index 0000000..f756e19
--- /dev/null
+++ b/AccessQueuePlayground/Components/Routes.razor
@@ -0,0 +1,6 @@
+
+    
+         
+ 
diff --git a/AccessQueuePlayground/Components/_Imports.razor b/AccessQueuePlayground/Components/_Imports.razor
new file mode 100644
index 0000000..4559e04
--- /dev/null
+++ b/AccessQueuePlayground/Components/_Imports.razor
@@ -0,0 +1,10 @@
+@using System.Net.Http
+@using System.Net.Http.Json
+@using Microsoft.AspNetCore.Components.Forms
+@using Microsoft.AspNetCore.Components.Routing
+@using Microsoft.AspNetCore.Components.Web
+@using static Microsoft.AspNetCore.Components.Web.RenderMode
+@using Microsoft.AspNetCore.Components.Web.Virtualization
+@using Microsoft.JSInterop
+@using AccessQueuePlayground
+@using AccessQueuePlayground.Components
diff --git a/AccessQueuePlayground/Models/AccessQueueStatus.cs b/AccessQueuePlayground/Models/AccessQueueStatus.cs
new file mode 100644
index 0000000..c773658
--- /dev/null
+++ b/AccessQueuePlayground/Models/AccessQueueStatus.cs
@@ -0,0 +1,11 @@
+using AccessQueueService.Models;
+
+namespace AccessQueuePlayground.Models
+{
+    public class AccessQueueStatus
+    {
+        public List AccessUsers { get; set; } = [];
+        public List QueuedUsers { get; set; } = [];
+        public List InactiveUsers { get; set; } = [];
+    }
+}
diff --git a/AccessQueuePlayground/Models/User.cs b/AccessQueuePlayground/Models/User.cs
new file mode 100644
index 0000000..e005ed9
--- /dev/null
+++ b/AccessQueuePlayground/Models/User.cs
@@ -0,0 +1,11 @@
+using AccessQueueService.Models;
+
+namespace AccessQueuePlayground.Models
+{
+    public class User
+    {
+        public Guid Id { get; set; }
+        public bool Active { get; set; }
+        public AccessResponse? LatestResponse { get; set; }
+    }
+}
diff --git a/AccessQueuePlayground/Program.cs b/AccessQueuePlayground/Program.cs
new file mode 100644
index 0000000..9ad2124
--- /dev/null
+++ b/AccessQueuePlayground/Program.cs
@@ -0,0 +1,34 @@
+using AccessQueuePlayground.Components;
+using AccessQueuePlayground.Services;
+using AccessQueueService.Data;
+using AccessQueueService.Services;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+builder.Services.AddRazorComponents()
+    .AddInteractiveServerComponents();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddHostedService();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (!app.Environment.IsDevelopment())
+{
+    app.UseExceptionHandler("/Error", createScopeForErrors: true);
+    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
+    app.UseHsts();
+}
+
+app.UseHttpsRedirection();
+
+app.UseStaticFiles();
+app.UseAntiforgery();
+
+app.MapRazorComponents()
+    .AddInteractiveServerRenderMode();
+
+app.Run();
diff --git a/AccessQueuePlayground/Properties/launchSettings.json b/AccessQueuePlayground/Properties/launchSettings.json
new file mode 100644
index 0000000..f310682
--- /dev/null
+++ b/AccessQueuePlayground/Properties/launchSettings.json
@@ -0,0 +1,38 @@
+{
+  "$schema": "http://json.schemastore.org/launchsettings.json",
+    "iisSettings": {
+      "windowsAuthentication": false,
+      "anonymousAuthentication": true,
+      "iisExpress": {
+        "applicationUrl": "http://localhost:25310",
+        "sslPort": 44353
+      }
+    },
+    "profiles": {
+      "http": {
+        "commandName": "Project",
+        "dotnetRunMessages": true,
+        "launchBrowser": true,
+        "applicationUrl": "http://localhost:5108",
+        "environmentVariables": {
+          "ASPNETCORE_ENVIRONMENT": "Development"
+        }
+      },
+      "https": {
+        "commandName": "Project",
+        "dotnetRunMessages": true,
+        "launchBrowser": true,
+        "applicationUrl": "https://localhost:7211;http://localhost:5108",
+        "environmentVariables": {
+          "ASPNETCORE_ENVIRONMENT": "Development"
+        }
+      },
+      "IIS Express": {
+        "commandName": "IISExpress",
+        "launchBrowser": true,
+        "environmentVariables": {
+          "ASPNETCORE_ENVIRONMENT": "Development"
+        }
+      }
+    }
+  }
diff --git a/AccessQueuePlayground/Services/AccessQueueBackgroundService.cs b/AccessQueuePlayground/Services/AccessQueueBackgroundService.cs
new file mode 100644
index 0000000..77765ce
--- /dev/null
+++ b/AccessQueuePlayground/Services/AccessQueueBackgroundService.cs
@@ -0,0 +1,25 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+
+namespace AccessQueuePlayground.Services
+{
+    public class AccessQueueBackgroundService : BackgroundService
+    {
+        private readonly IAccessQueueManager _accessQueueManager;
+
+        public AccessQueueBackgroundService(IAccessQueueManager accessQueueManager)
+        {
+            _accessQueueManager = accessQueueManager;
+        }
+
+        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+        {
+            while (!stoppingToken.IsCancellationRequested)
+            {
+                await _accessQueueManager.RecalculateStatus();
+                await Task.Delay(1000, stoppingToken); // Run every second
+            }
+        }
+    }
+}
diff --git a/AccessQueuePlayground/Services/AccessQueueManager.cs b/AccessQueuePlayground/Services/AccessQueueManager.cs
new file mode 100644
index 0000000..69846b3
--- /dev/null
+++ b/AccessQueuePlayground/Services/AccessQueueManager.cs
@@ -0,0 +1,83 @@
+using System.Collections.Concurrent;
+using AccessQueuePlayground.Models;
+using AccessQueueService.Models;
+using AccessQueueService.Services;
+
+namespace AccessQueuePlayground.Services
+{
+    public class AccessQueueManager : IAccessQueueManager
+    {
+        private readonly IAccessService _accessService;
+        private readonly ConcurrentDictionary _users;
+        private AccessQueueStatus _status;
+        public event Action? StatusUpdated;
+
+        private void NotifyStatusUpdated()
+        {
+            StatusUpdated?.Invoke();
+        }
+
+        public AccessQueueManager(IAccessService accessService)
+        {
+            _accessService = accessService;
+            _users = new ConcurrentDictionary();
+            _status = new AccessQueueStatus();
+        }
+
+        public AccessQueueStatus GetStatus() => _status;
+
+        public Guid AddUser()
+        {
+            var id = Guid.NewGuid();
+            _users[id] = new User
+            {
+                Id = id,
+                Active = true,
+            };
+            return id;
+        }
+
+        public void ToggleUserActivity(Guid userId)
+        {
+            var user = _users[userId];
+            if (user != null)
+            {
+                user.Active = !user.Active;
+            }
+        }
+
+        public async Task RecalculateStatus()
+        {
+            var userList = _users.Values.ToList();
+            var newStatus = new AccessQueueStatus();
+            foreach (var user in userList)
+            {
+                if (user.Active)
+                {
+                    user.LatestResponse = await _accessService.RequestAccess(user.Id);
+                    if (user.LatestResponse?.HasAccess ?? false)
+                    {
+                        newStatus.AccessUsers.Add(user);
+                    }
+                    else
+                    {
+                        newStatus.QueuedUsers.Add(user);
+                    }
+                }
+                else
+                {
+                    if(user.LatestResponse?.ExpiresOn != null && user.LatestResponse.ExpiresOn > DateTime.UtcNow)
+                    {
+                        newStatus.AccessUsers.Add(user);
+                    }
+                    else
+                    {
+                        newStatus.InactiveUsers.Add(user);
+                    }
+                }
+            }
+            _status = newStatus;
+            NotifyStatusUpdated();
+        }
+    }
+}
diff --git a/AccessQueuePlayground/Services/IAccessQueueManager.cs b/AccessQueuePlayground/Services/IAccessQueueManager.cs
new file mode 100644
index 0000000..29f6b3f
--- /dev/null
+++ b/AccessQueuePlayground/Services/IAccessQueueManager.cs
@@ -0,0 +1,14 @@
+using AccessQueuePlayground.Models;
+
+namespace AccessQueuePlayground.Services
+{
+    public interface IAccessQueueManager
+    {
+        public event Action? StatusUpdated;
+        public Task RecalculateStatus();
+        public AccessQueueStatus GetStatus();
+        public Guid AddUser();
+        public void ToggleUserActivity(Guid userId);
+
+    }
+}
diff --git a/AccessQueuePlayground/appsettings.Development.json b/AccessQueuePlayground/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/AccessQueuePlayground/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  }
+}
diff --git a/AccessQueuePlayground/appsettings.json b/AccessQueuePlayground/appsettings.json
new file mode 100644
index 0000000..fcc9c4b
--- /dev/null
+++ b/AccessQueuePlayground/appsettings.json
@@ -0,0 +1,15 @@
+{
+    "Logging": {
+        "LogLevel": {
+            "Default": "Information",
+            "Microsoft.AspNetCore": "Warning"
+        }
+    },
+    "AccessQueue": {
+        "CapacityLimit": 3, // Maximum number of active users
+        "ActivitySeconds": 2, // Time since last access before a user is considered inactive
+        "ExpirationSeconds": 10, // 12 hours - Time before a user access is revoked
+        "RollingExpiration": true // Whether to extend expiration time on access
+    },
+    "AllowedHosts": "*"
+}
diff --git a/AccessQueuePlayground/wwwroot/app.css b/AccessQueuePlayground/wwwroot/app.css
new file mode 100644
index 0000000..e398853
--- /dev/null
+++ b/AccessQueuePlayground/wwwroot/app.css
@@ -0,0 +1,29 @@
+h1:focus {
+    outline: none;
+}
+
+.valid.modified:not([type=checkbox]) {
+    outline: 1px solid #26b050;
+}
+
+.invalid {
+    outline: 1px solid #e50000;
+}
+
+.validation-message {
+    color: #e50000;
+}
+
+.blazor-error-boundary {
+    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
+    padding: 1rem 1rem 1rem 3.7rem;
+    color: white;
+}
+
+    .blazor-error-boundary::after {
+        content: "An error has occurred."
+    }
+
+.darker-border-checkbox.form-check-input {
+    border-color: #929292;
+}
diff --git a/AccessQueueService.sln b/AccessQueueService.sln
index a3ecab9..82ba322 100644
--- a/AccessQueueService.sln
+++ b/AccessQueueService.sln
@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccessQueueService", "Acces
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccessQueueServiceTests", "AccessQueueServiceTests\AccessQueueServiceTests.csproj", "{1DF48A19-A2B3-4B0C-B726-E65B8E023760}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccessQueuePlayground", "AccessQueuePlayground\AccessQueuePlayground.csproj", "{65D5E841-7B02-4A55-89C6-12082FA1BCAF}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,10 @@ Global
 		{1DF48A19-A2B3-4B0C-B726-E65B8E023760}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{1DF48A19-A2B3-4B0C-B726-E65B8E023760}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{1DF48A19-A2B3-4B0C-B726-E65B8E023760}.Release|Any CPU.Build.0 = Release|Any CPU
+		{65D5E841-7B02-4A55-89C6-12082FA1BCAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{65D5E841-7B02-4A55-89C6-12082FA1BCAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{65D5E841-7B02-4A55-89C6-12082FA1BCAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{65D5E841-7B02-4A55-89C6-12082FA1BCAF}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE