Files
firefrost-operations-manual/docs/consultations/gemini-trinity-console-2-round-5-response-2026-04-05.md
Claude (Chronicler #61) 174cf69186 docs: Gemini Round 5 — Branding, design system, auth flows
Complete UI/UX package:

Tailwind config:
- Firefrost brand colors (fire, frost, arcane, gold)
- Void surface variations for depth
- Inter + JetBrains Mono fonts

Layout:
- Dark theme master layout with sidebar
- Mobile-friendly hamburger menu
- User dropdown with avatar, role, logout
- AlpineJS for interactivity

Component library:
- Cards, buttons (fire/frost/arcane variants)
- Form inputs, alerts, badges
- Consistent Firefrost styling

Auth flows:
- Branded login page with Discord OAuth
- Profile page showing role + permissions
- Logout route with session destruction

Meg was right — it needs to look magical, not sterile.

Signed-off-by: Claude (Chronicler #61) <claude@firefrostgaming.com>
2026-04-05 11:54:23 +00:00

15 KiB

Gemini Response: Trinity Console 2.0 — Round 5 (Branding & User Flow)

Date: April 5, 2026
From: Gemini (Architectural Partner)
To: Michael (The Wizard) + Claude (Chronicler #61) + Meg (The Emissary)
Re: Branding, design system, user profile, and logout


Tell Meg she is 100% right. You cannot build the operational heart of a magical gaming community and make it look like a sterile 2014 accounting dashboard.

The Trinity Console should feel like stepping into the Command Center of Firefrost. Dark, sleek, accented with elemental magic, and heavily functional.

Here is the complete branding, design system, and user flow package to make that happen.


1. The Brand Configuration (Tailwind)

First, we need to inject the Firefrost DNA into Tailwind. We'll use your exact hex codes and add a few "surface" variations of your Dark (#0F0F1E) to create depth for cards and sidebars without breaking the color scheme.

tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{ejs,js,html}"],
  theme: {
    extend: {
      colors: {
        fire: {
          DEFAULT: '#FF6B35',
          hover: '#E55A2A',
          dim: 'rgba(255, 107, 53, 0.1)'
        },
        frost: {
          DEFAULT: '#4ECDC4',
          hover: '#3EBAB1',
          dim: 'rgba(78, 205, 196, 0.1)'
        },
        arcane: {
          DEFAULT: '#A855F7',
          hover: '#9333EA',
          dim: 'rgba(168, 85, 247, 0.1)'
        },
        gold: {
          DEFAULT: '#FFD700',
          hover: '#E6C200',
          dim: 'rgba(255, 215, 0, 0.1)'
        },
        void: {
          DEFAULT: '#0F0F1E', // Base background
          surface: '#16162C', // Cards and panels
          hover: '#1D1D3A',   // Hover states
          border: '#2A2A4A'   // Subtle dividers
        }
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'], // Recommend Inter for clean UI
        mono: ['JetBrains Mono', 'monospace']
      }
    },
  },
  plugins: [
    require('@tailwindcss/forms'), // Highly recommended for clean form inputs
  ],
}

2. The Master Layout & Navigation

This assumes you are using express-ejs-layouts. It includes a mobile-friendly sidebar, the branded header, and the user dropdown Meg requested.

views/layout.ejs

<!DOCTYPE html>
<html lang="en" class="bg-void text-gray-300">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Trinity Console | Firefrost</title>
    <link rel="stylesheet" href="/css/output.css">
    <script src="https://unpkg.com/htmx.org@1.9.10"></script>
    <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
    <script src="https://unpkg.com/lucide@latest"></script>
</head>
<body class="flex h-screen overflow-hidden selection:bg-arcane selection:text-white" x-data="{ sidebarOpen: false, userMenuOpen: false }">

    <div x-show="sidebarOpen" class="fixed inset-0 z-40 bg-void/80 backdrop-blur-sm lg:hidden" @click="sidebarOpen = false"></div>

    <aside :class="sidebarOpen ? 'translate-x-0' : '-translate-x-full'" class="fixed inset-y-0 left-0 z-50 w-64 bg-void-surface border-r border-void-border transition-transform duration-300 lg:static lg:translate-x-0 flex flex-col">
        <div class="flex items-center justify-center h-16 border-b border-void-border bg-void">
            <span class="text-xl font-bold tracking-wider text-transparent bg-clip-text bg-gradient-to-r from-fire to-frost">
                TRINITY CONSOLE
            </span>
        </div>

        <nav class="flex-1 overflow-y-auto p-4 space-y-6 scrollbar-thin scrollbar-thumb-void-border">
            <% Object.keys(navigation).forEach(section => { %>
                <div>
                    <h3 class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2"><%= section %></h3>
                    <ul class="space-y-1">
                        <% navigation[section].forEach(item => { %>
                            <li>
                                <a href="<%= item.path %>" class="flex items-center justify-between px-3 py-2 text-sm font-medium rounded-md hover:bg-void-hover hover:text-white transition-colors group">
                                    <div class="flex items-center">
                                        <i data-lucide="<%= item.icon %>" class="w-5 h-5 mr-3 text-gray-400 group-hover:text-frost"></i>
                                        <%= item.name %>
                                    </div>
                                    <% if (item.badge) { %>
                                        <span class="bg-fire-dim text-fire text-xs py-0.5 px-2 rounded-full"><%= item.badge %></span>
                                    <% } %>
                                </a>
                            </li>
                        <% }) %>
                    </ul>
                </div>
            <% }) %>
        </nav>
    </aside>

    <div class="flex-1 flex flex-col min-w-0 overflow-hidden">
        
        <header class="flex items-center justify-between h-16 px-4 sm:px-6 bg-void-surface border-b border-void-border">
            <button @click="sidebarOpen = true" class="lg:hidden text-gray-400 hover:text-white">
                <i data-lucide="menu" class="w-6 h-6"></i>
            </button>

            <div class="flex-1"></div>

            <div class="relative ml-3">
                <button @click="userMenuOpen = !userMenuOpen" @click.away="userMenuOpen = false" class="flex items-center space-x-3 focus:outline-none">
                    <div class="text-right hidden sm:block">
                        <p class="text-sm font-medium text-white"><%= user.username %></p>
                        <p class="text-xs text-arcane"><%= user.role_name || 'Member' %></p>
                    </div>
                    <img class="h-9 w-9 rounded-full border border-arcane p-0.5 object-cover" 
                         src="https://cdn.discordapp.com/avatars/<%= user.discord_id %>/<%= user.avatar %>.png" 
                         alt="Avatar">
                </button>

                <div x-show="userMenuOpen" x-transition class="absolute right-0 mt-2 w-48 bg-void-surface border border-void-border rounded-md shadow-lg py-1 z-50">
                    <a href="/profile" class="block px-4 py-2 text-sm text-gray-300 hover:bg-void-hover hover:text-white">My Profile</a>
                    <hr class="border-void-border my-1">
                    <a href="/auth/logout" class="block px-4 py-2 text-sm text-fire hover:bg-fire-dim hover:text-fire-hover">Sign Out</a>
                </div>
            </div>
        </header>

        <main class="flex-1 overflow-y-auto p-4 sm:p-6 lg:p-8 relative" id="main-content">
            <%- body %>
        </main>
    </div>

    <script>lucide.createIcons();</script>
</body>
</html>

3. The Firefrost Component Library

Store these snippets mentally or in a partials folder. This is how you structure the internal content of your modules.

Cards & Panels

<div class="bg-void-surface border border-void-border rounded-xl p-6 shadow-lg">
    <h2 class="text-lg font-bold text-white mb-4">Server Matrix</h2>
</div>

Buttons

<button class="bg-fire hover:bg-fire-hover text-white font-medium py-2 px-4 rounded-md transition-colors shadow-[0_0_15px_rgba(255,107,53,0.3)]">Deploy Firewall</button>

<button class="bg-frost hover:bg-frost-hover text-void font-bold py-2 px-4 rounded-md transition-colors">Sync Whitelist</button>

<button class="border border-arcane text-arcane hover:bg-arcane hover:text-white font-medium py-2 px-4 rounded-md transition-all">Audit Roles</button>

<button class="bg-gray-700 text-gray-400 cursor-not-allowed font-medium py-2 px-4 rounded-md" disabled>Processing...</button>

Form Inputs

<div>
    <label class="block text-sm font-medium text-gray-400 mb-1">Base Time (UTC)</label>
    <input type="time" class="w-full bg-void border border-void-border rounded-md text-white px-3 py-2 focus:ring-2 focus:ring-frost focus:border-transparent transition-all">
</div>

Alerts & Badges

<div class="bg-fire-dim border-l-4 border-fire p-4 rounded-r-md">
    <p class="text-fire-hover text-sm font-medium">Critical: Node NC1 is experiencing high latency.</p>
</div>

<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-frost-dim text-frost">Online</span>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-arcane-dim text-arcane">Trinity Admin</span>

4. Auth Routes & Profile Flow

src/core/auth/routes.js

const express = require('express');
const router = express.Router();
const db = require('../../db');

// --- LOGIN PAGE ---
router.get('/auth/login', (req, res) => {
    // If already logged in, send to dashboard
    if (req.user) return res.redirect('/dashboard');
    res.render('../src/core/auth/views/login', { layout: false }); // No sidebar layout
});

// --- LOGOUT ROUTE ---
router.get('/auth/logout', (req, res) => {
    req.session.destroy((err) => {
        if (err) console.error('Session destruction error:', err);
        res.redirect('/auth/login?loggedOut=true');
    });
});

// --- PROFILE PAGE ---
// Assuming standard layout protection is handled above this route
router.get('/profile', async (req, res) => {
    if (!req.user) return res.redirect('/auth/login');

    try {
        // Fetch User's Role Name
        const roleRes = await db.query(`
            SELECT r.name 
            FROM user_roles ur 
            JOIN roles r ON ur.role_id = r.id 
            WHERE ur.user_id = $1`, 
            [req.user.id]
        );
        const roleName = roleRes.rowCount > 0 ? roleRes.rows[0].name : 'Member';

        // Fetch explicitly granted permissions (from your resolver logic)
        const { fetchUserPermissions } = require('../permissions/resolver');
        const permissions = await fetchUserPermissions(db, req.user.id);

        res.render('../src/core/auth/views/profile', { 
            user: req.user, 
            roleName,
            permissions: permissions.sort()
        });
    } catch (error) {
        console.error(error);
        res.status(500).send("Error loading profile");
    }
});

module.exports = router;

5. Authentication Views

src/core/auth/views/login.ejs

(Note: Passed layout: false in the route so it renders independently)

<!DOCTYPE html>
<html lang="en" class="bg-void text-gray-300">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login | Trinity Console</title>
    <link rel="stylesheet" href="/css/output.css">
</head>
<body class="flex items-center justify-center min-h-screen relative overflow-hidden">
    <div class="absolute top-1/4 left-1/4 w-96 h-96 bg-fire/20 rounded-full blur-[100px] pointer-events-none"></div>
    <div class="absolute bottom-1/4 right-1/4 w-96 h-96 bg-frost/20 rounded-full blur-[100px] pointer-events-none"></div>

    <div class="z-10 w-full max-w-md bg-void-surface border border-void-border rounded-2xl shadow-2xl p-8 text-center relative overflow-hidden">
        <div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-fire via-arcane to-frost"></div>
        
        <h1 class="text-3xl font-bold text-white mb-2">Trinity Console</h1>
        <p class="text-gray-400 text-sm mb-8">Command Center for Firefrost Gaming</p>

        <a href="/auth/discord" class="inline-flex w-full justify-center items-center gap-3 bg-[#5865F2] hover:bg-[#4752C4] text-white font-medium py-3 px-4 rounded-lg transition-colors">
            <svg class="w-5 h-5 fill-current" viewBox="0 0 127.14 96.36" xmlns="http://www.w3.org/2000/svg"><path d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.31,60,73.31,53s5-12.74,11.43-12.74S96.33,46,96.22,53,91.08,65.69,84.69,65.69Z"/></svg>
            Sign in with Discord
        </a>
    </div>
</body>
</html>

src/core/auth/views/profile.ejs

<div class="max-w-4xl mx-auto space-y-6">
    <div class="bg-void-surface border border-void-border rounded-xl p-8 shadow-lg flex items-center gap-6 relative overflow-hidden">
        <div class="absolute -right-20 -top-20 w-64 h-64 bg-arcane/10 rounded-full blur-3xl pointer-events-none"></div>

        <img class="h-24 w-24 rounded-full border-2 border-arcane object-cover shadow-[0_0_15px_rgba(168,85,247,0.4)]" 
             src="https://cdn.discordapp.com/avatars/<%= user.discord_id %>/<%= user.avatar %>.png" 
             alt="Avatar">
        
        <div>
            <h1 class="text-3xl font-bold text-white"><%= user.username %></h1>
            <p class="text-gray-400 mt-1 flex items-center gap-2">
                <span class="inline-block w-2 h-2 rounded-full bg-frost"></span>
                Connected via Discord
            </p>
            <div class="mt-3">
                <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-semibold bg-arcane-dim text-arcane border border-arcane/20">
                    <%= roleName %>
                </span>
            </div>
        </div>
    </div>

    <div class="bg-void-surface border border-void-border rounded-xl p-6 shadow-lg">
        <h2 class="text-xl font-bold text-white mb-4 border-b border-void-border pb-2">Active Permissions</h2>
        <p class="text-sm text-gray-400 mb-6">These are the specific clearance levels assigned to you by the Trinity.</p>
        
        <% if (permissions && permissions.length > 0) { %>
            <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3">
                <% permissions.forEach(perm => { %>
                    <div class="bg-void p-3 border border-void-border rounded text-sm text-gray-300 font-mono flex items-center gap-2">
                        <div class="w-1.5 h-1.5 rounded-full bg-fire"></div>
                        <%= perm %>
                    </div>
                <% }) %>
            </div>
        <% } else { %>
            <div class="text-center py-8 bg-void border border-dashed border-void-border rounded-lg">
                <p class="text-gray-500">No explicit permissions assigned to this profile.</p>
            </div>
        <% } %>
    </div>
</div>

You Are Cleared for Deployment

Michael, Claude, and Meg: You now have the exact styling, routing, and HTML necessary to build the framework of Trinity Console 2.0. Meg, your instinct was perfect—a tool built to manage magic should look magical itself.

Log this final piece in your Ops Manual, and let me know when you successfully boot the Core Engine for the first time!


Response received April 5, 2026