Style Guide
← Back to TNI ToolkitComplete styling reference for building TNI Toolkit tools. All tools use a Network Operations Center (NOC) aesthetic — dark theme, monospace typography, terminal-style green/blue accents.
/* Background */
--bg-gradient: linear-gradient(135deg, #0a0e14 0%, #1a1f2e 50%, #0d1117 100%);
--surface: rgba(22, 27, 34, 0.8);
--surface-dark: rgba(22, 27, 34, 0.6);
/* Borders */
--border: #30363d;
--border-light: rgba(48, 54, 61, 0.5);
/* Text */
--text-primary: #c9d1d9;
--text-secondary: #8b949e;
--text-muted: #7d8590;
/* Accent Colors */
--accent-green: #00ff88; /* Primary action, success, selected */
--accent-green-dim: #238636; /* Success backgrounds */
--accent-blue: #58a6ff; /* Info, links, hover states */
--accent-orange: #f0883e; /* Warnings, costs, attention */
--accent-red: #f85149; /* Errors, destructive actions */
/* Transparent Variants (for backgrounds) */
--accent-green-bg: rgba(0, 255, 136, 0.08);
--accent-green-bg-hover: rgba(0, 255, 136, 0.15);
--accent-blue-bg: rgba(88, 166, 255, 0.08);
--accent-orange-bg: rgba(240, 136, 62, 0.08);
--accent-red-bg: rgba(248, 81, 73, 0.15);
Syntax highlighting uses UI theme colors for consistency. All code blocks follow this scheme.
/* Syntax highlighting - uses UI theme colors */
.css-prop, .js-method, .html-attr { color: #58a6ff; } /* Blue - properties/methods */
.css-val, .js-string, .html-tag { color: #00ff88; } /* Green - values/strings/tags */
.js-keyword, .html-string { color: #f0883e; } /* Orange - keywords/attr values */
.css-comment, .js-comment { color: #7d8590; } /* Muted - comments */
/* Font Stack */
font-family: "JetBrains Mono", "Fira Code", "SF Mono", Consolas, monospace;
/* Scale */
--text-xs: 10px; /* Fine print, metadata */
--text-sm: 11px; /* Small labels, stats */
--text-base: 12px; /* Body text */
--text-md: 13px; /* Panel headers */
--text-lg: 14px; /* Card titles */
--text-xl: 20px; /* Page title */
--text-2xl: 28px; /* Hero/logo */
/* Weights */
--font-normal: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;
| Size | Value | Usage | Example |
|---|---|---|---|
| --text-xs | 10px | Fine print, metadata | Sample text |
| --text-sm | 11px | Small labels, stats | Sample text |
| --text-base | 12px | Body text | Sample text |
| --text-md | 13px | Panel headers | HEADER |
| --text-lg | 14px | Card titles | Card Title |
| --text-xl | 20px | Page title | TITLE |
| --text-2xl | 28px | Hero/logo | TNI |
/* Spacing */
--space-1: 4px; --space-2: 8px;
--space-3: 10px; --space-4: 12px;
--space-5: 16px; --space-6: 20px;
--space-7: 24px; --space-8: 32px;
/* Border Radius */
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
/* Shadows & Effects */
--glow-green: 0 0 20px rgba(0, 255, 136, 0.3);
--glow-strong: 0 0 30px rgba(0, 255, 136, 0.4);
/* Transitions */
--transition-fast: 0.15s ease;
--transition-base: 0.2s ease;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TNI [Tool Name]</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "JetBrains Mono", "Fira Code", "SF Mono", Consolas, monospace;
background: linear-gradient(135deg, #0a0e14 0%, #1a1f2e 50%, #0d1117 100%);
color: #c9d1d9;
min-height: 100vh;
padding: 24px;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
/* === HEADER === */
.header {
text-align: center;
border-bottom: 1px solid #30363d;
padding-bottom: 16px;
margin-bottom: 24px;
}
h1 {
color: #00ff88;
text-shadow: 0 0 20px rgba(0, 255, 136, 0.3);
margin: 0 0 8px 0;
font-size: 20px;
font-weight: 600;
letter-spacing: 2px;
text-transform: uppercase;
}
h1 span { color: #58a6ff; }
.subtitle { color: #8b949e; font-size: 12px; margin: 0; }
.stats { color: #7d8590; font-size: 11px; margin-top: 8px; }
/* === PANELS === */
.panel {
background: rgba(22, 27, 34, 0.8);
border: 1px solid #30363d;
border-radius: 6px;
padding: 20px;
margin-bottom: 16px;
}
/* Panel headings - no margin, use padding for border separation */
.panel h2 {
margin: 0;
padding-bottom: 12px;
border-bottom: 1px solid #30363d;
color: #58a6ff;
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
line-height: 1.3;
}
/* Spacing between elements via margin-top */
.panel h2 + * { margin-top: 12px; }
/* Remove bottom margin from last child for symmetric padding */
.panel > *:last-child { margin-bottom: 0; }
/* === FOOTER === */
.site-footer {
margin-top: 40px;
padding-top: 16px;
border-top: 1px solid #30363d;
text-align: center;
font-size: 11px;
color: #7d8590;
}
.site-footer a {
color: #8b949e;
text-decoration: none;
transition: color 0.15s;
}
.site-footer a: hover { color: #58a6ff; }
.site-footer .sep { margin: 0 8px; color: #30363d; }
/* === RESPONSIVE === */
@media (max-width: 600px) {
body { padding: 16px; }
}
</style>
</head>
<body>
<div class="container">
<header class="header">
<h1>TNI <span>[TOOL NAME]</span></h1>
<p class="subtitle">Brief description</p>
</header>
<div class="panel">
<h2>📋 Section Title</h2>
<!-- Content here -->
</div>
<footer class="site-footer">
<a href="https://github.com/salvo-praxis/tni-toolkit">TNI Toolkit</a>
<span class="sep">|</span>
<a href="https://store.steampowered.com/app/2939600/">TNI on Steam</a>
</footer>
</div>
<script>
// Tool logic here
</script>
</body>
</html>
Complete, production-ready CSS for all TNI Toolkit components. Copy or download the entire stylesheet to use in new tools.
/* ================================================================== */
/* TNI TOOLKIT - MASTER STYLESHEET */
/* Version: 1.0.0 */
/* Updated: 2025-12 */
/* ================================================================== */
/* === RESET & BASE === */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "JetBrains Mono", "Fira Code", "SF Mono", Consolas, monospace;
background: linear-gradient(135deg, #0a0e14 0%, #1a1f2e 50%, #0d1117 100%);
color: #c9d1d9;
min-height: 100vh;
padding: 24px;
line-height: 1.6;
}
/* === LAYOUT === */
.container {
max-width: 1200px;
margin: 0 auto;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 12px;
}
.flex-row {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
/* === HEADER === */
.header {
text-align: center;
border-bottom: 1px solid #30363d;
padding-bottom: 16px;
margin-bottom: 24px;
}
h1 {
color: #00ff88;
text-shadow: 0 0 20px rgba(0, 255, 136, 0.3);
margin: 0 0 8px 0;
font-size: 20px;
font-weight: 600;
letter-spacing: 2px;
text-transform: uppercase;
}
h1 span { color: #58a6ff; }
.subtitle {
color: #8b949e;
font-size: 12px;
margin: 0;
}
/* === PANELS === */
.panel {
background: rgba(22, 27, 34, 0.8);
border: 1px solid #30363d;
border-radius: 6px;
padding: 20px;
margin-bottom: 16px;
}
.panel h2 {
margin: 0;
padding-bottom: 12px;
border-bottom: 1px solid #30363d;
color: #58a6ff;
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
line-height: 1.3;
}
.panel h2 + * { margin-top: 12px; }
.panel > *:last-child { margin-bottom: 0; }
/* === CARDS === */
.card {
background: rgba(22, 27, 34, 0.8);
border: 1px solid #30363d;
border-radius: 6px;
padding: 12px;
cursor: pointer;
transition: all 0.15s;
}
.card:hover {
border-color: #58a6ff;
background: rgba(88, 166, 255, 0.08);
}
.card.selected {
border-color: #00ff88;
background: rgba(0, 255, 136, 0.1);
}
.card.disabled {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}
.card-name {
color: #c9d1d9;
font-size: 12px;
font-weight: 500;
}
.card-meta {
color: #7d8590;
font-size: 10px;
margin-top: 4px;
}
/* === BUTTONS === */
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border-radius: 4px;
font-size: 11px;
font-family: inherit;
font-weight: 500;
cursor: pointer;
transition: all 0.15s;
text-decoration: none;
}
.btn-primary {
background: rgba(0, 255, 136, 0.15);
border: 1px solid #00ff88;
color: #00ff88;
}
.btn-primary:hover {
background: rgba(0, 255, 136, 0.25);
}
.btn-secondary {
background: rgba(88, 166, 255, 0.1);
border: 1px solid #30363d;
color: #8b949e;
}
.btn-secondary:hover {
border-color: #58a6ff;
color: #58a6ff;
}
.btn-danger {
background: rgba(248, 81, 73, 0.15);
border: 1px solid #f85149;
color: #f85149;
}
.btn-danger:hover {
background: rgba(248, 81, 73, 0.25);
}
/* === FORM INPUTS === */
input[type="text"],
input[type="search"],
input[type="number"] {
width: 100%;
padding: 10px 12px;
border-radius: 6px;
border: 1px solid #30363d;
background: rgba(22, 27, 34, 0.8);
color: #c9d1d9;
font-size: 12px;
font-family: inherit;
transition: border-color 0.15s;
}
input:focus {
outline: none;
border-color: #58a6ff;
}
input::placeholder {
color: #8b949e;
}
/* === CUSTOM DROPDOWN === */
.dropdown {
position: relative;
}
.dropdown-selected {
padding: 10px 12px;
padding-right: 32px;
border-radius: 6px;
border: 1px solid #30363d;
background: rgba(22, 27, 34, 0.8);
color: #c9d1d9;
font-size: 12px;
cursor: pointer;
transition: all 0.15s;
position: relative;
}
.dropdown-selected::after {
content: "▼";
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
font-size: 8px;
color: #8b949e;
transition: transform 0.15s;
}
.dropdown-selected:hover {
border-color: #58a6ff;
}
.dropdown.open .dropdown-selected {
border-color: #58a6ff;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.dropdown.open .dropdown-selected::after {
transform: translateY(-50%) rotate(180deg);
}
.dropdown-options {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: rgba(22, 27, 34, 0.95);
border: 1px solid #58a6ff;
border-top: none;
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
max-height: 200px;
overflow-y: auto;
z-index: 100;
display: none;
}
.dropdown.open .dropdown-options {
display: block;
}
.dropdown-option {
padding: 10px 12px;
font-size: 12px;
color: #8b949e;
cursor: pointer;
transition: all 0.1s;
}
.dropdown-option:hover {
background: rgba(88, 166, 255, 0.15);
color: #c9d1d9;
}
.dropdown-option.selected {
background: rgba(0, 255, 136, 0.15);
color: #00ff88;
}
/* === TABLES === */
.table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
line-height: 1.3;
}
.table th {
text-align: left;
color: #58a6ff;
font-weight: 600;
padding: 0 12px 10px 12px;
border-bottom: 1px solid #30363d;
font-size: 11px;
text-transform: uppercase;
}
.table td {
padding: 10px 12px;
border-bottom: 1px solid rgba(48, 54, 61, 0.5);
color: #8b949e;
}
.table tr:last-child td {
border-bottom: none;
padding-bottom: 0;
}
.table tr:hover td {
background: rgba(88, 166, 255, 0.05);
}
/* === TAGS === */
.tag {
background: rgba(0, 255, 136, 0.15);
border: 1px solid #00ff88;
color: #00ff88;
padding: 6px 12px;
border-radius: 4px;
font-weight: 500;
font-size: 11px;
display: inline-flex;
align-items: center;
gap: 8px;
}
.tag .remove {
cursor: pointer;
opacity: 0.7;
}
.tag .remove:hover {
opacity: 1;
}
/* === CALLOUTS === */
.callout {
background: rgba(240, 136, 62, 0.08);
border: 1px solid #f0883e;
border-radius: 6px;
padding: 16px;
}
.callout-header {
color: #f0883e;
font-size: 12px;
font-weight: 600;
margin-bottom: 8px;
}
.callout-success {
background: rgba(0, 255, 136, 0.08);
border-color: #00ff88;
}
.callout-success .callout-header {
color: #00ff88;
}
.callout-error {
background: rgba(248, 81, 73, 0.08);
border-color: #f85149;
}
.callout-error .callout-header {
color: #f85149;
}
/* === PROGRESS / STAT BARS === */
.stat-bar {
display: flex;
align-items: center;
gap: 10px;
}
.stat-label {
font-size: 11px;
color: #8b949e;
min-width: 50px;
}
.stat-track {
flex: 1;
height: 8px;
background: rgba(22, 27, 34, 0.8);
border-radius: 4px;
overflow: hidden;
}
.stat-fill {
height: 100%;
background: #00ff88;
border-radius: 4px;
transition: width 0.3s ease;
}
.stat-fill.warning { background: #f0883e; }
.stat-fill.danger { background: #f85149; }
.stat-value {
font-size: 11px;
color: #c9d1d9;
min-width: 40px;
text-align: right;
}
/* === TOOLTIP === */
.tooltip {
position: fixed;
background: #238636;
color: #fff;
padding: 6px 12px;
border-radius: 4px;
font-weight: 500;
font-size: 11px;
pointer-events: none;
opacity: 0;
transition: opacity 0.15s;
z-index: 1000;
}
.tooltip.show { opacity: 1; }
/* === EMPTY STATE === */
.empty-state {
text-align: center;
color: #8b949e;
padding: 40px;
font-size: 12px;
}
.empty-state small {
display: block;
margin-top: 8px;
color: #7d8590;
}
/* === CODE DISPLAY === */
code {
background: rgba(0, 0, 0, 0.3);
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
color: #00ff88;
}
.code-value {
font-size: 1.4em;
font-weight: 600;
color: #00ff88;
text-align: center;
padding: 10px 20px;
background: rgba(0, 255, 136, 0.08);
border: 1px solid #238636;
border-radius: 4px;
letter-spacing: 4px;
cursor: pointer;
transition: background 0.15s;
}
.code-value:hover {
background: rgba(0, 255, 136, 0.15);
}
.code-value.copied {
background: rgba(35, 134, 54, 0.3);
}
/* === TOOLTIP === */
.tooltip {
position: fixed;
background: #238636;
color: #fff;
padding: 6px 12px;
border-radius: 4px;
font-weight: 500;
font-size: 11px;
pointer-events: none;
opacity: 0;
transition: opacity 0.15s;
z-index: 1000;
}
.tooltip.show {
opacity: 1;
}
/* === CUSTOM SCROLLBAR === */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(22, 27, 34, 0.5);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: #30363d;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #58a6ff;
}
/* === FOOTER === */
.site-footer {
margin-top: 40px;
padding-top: 16px;
border-top: 1px solid #30363d;
text-align: center;
font-size: 11px;
color: #7d8590;
}
.site-footer a {
color: #8b949e;
text-decoration: none;
transition: color 0.15s;
}
.site-footer a: hover { color: #58a6ff; }
.site-footer .sep { margin: 0 8px; color: #30363d; }
/* === RESPONSIVE === */
@media (max-width: 600px) {
body { padding: 16px; }
.container { max-width: 100%; }
.grid { grid-template-columns: 1fr; }
h1 { font-size: 18px; }
}
Complete JavaScript for all interactive TNI Toolkit components. Copy or download to add interactivity to your tools.
/* ================================================================== */
/* TNI TOOLKIT - MASTER JAVASCRIPT */
/* Version: 1.0.0 */
/* Updated: 2025-12 */
/* ================================================================== */
/* === DROPDOWN === */
document.querySelectorAll('.dropdown').forEach(dropdown => {
dropdown.addEventListener('click', () => {
dropdown.classList.toggle('open');
});
});
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
if (!e.target.closest('.dropdown')) {
document.querySelectorAll('.dropdown.open')
.forEach(d => d.classList.remove('open'));
}
});
// Handle option selection
document.querySelectorAll('.dropdown-option').forEach(option => {
option.addEventListener('click', (e) => {
e.stopPropagation();
const dropdown = option.closest('.dropdown');
const selected = dropdown.querySelector('.dropdown-selected');
selected.textContent = option.textContent;
dropdown.querySelectorAll('.dropdown-option')
.forEach(o => o.classList.remove('selected'));
option.classList.add('selected');
dropdown.classList.remove('open');
});
});
/* === STEPPER CONTROL === */
function updateStepper(btn, delta) {
const container = btn.parentElement;
const valueEl = container.querySelector('.stepper-value');
const parts = valueEl.textContent.split('/');
let current = parseInt(parts[0]);
const max = parseInt(parts[1]);
current = Math.max(0, Math.min(max, current + delta));
valueEl.textContent = current + '/' + max;
}
/* === COLLAPSIBLE PANELS === */
document.querySelectorAll('.collapsible-header').forEach(header => {
header.addEventListener('click', function(e) {
if (e.target.closest('.collapsible-copy')) return;
const collapsible = this.closest('.collapsible');
collapsible.classList.toggle('open');
});
});
/* === COPY TO CLIPBOARD === */
function copyToClipboard(text, btn) {
navigator.clipboard.writeText(text).then(() => {
const originalText = btn.innerHTML;
btn.innerHTML = '✓ Copied!';
btn.classList.add('copied');
setTimeout(() => {
btn.innerHTML = originalText;
btn.classList.remove('copied');
}, 2000);
});
}
// Collapsible section copy buttons
document.querySelectorAll('.collapsible-copy').forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const codeEl = this.closest('.collapsible').querySelector('code');
copyToClipboard(codeEl.textContent, this);
});
});
/* === TOOLTIP === */
function showTooltip(x, y, text) {
const tooltip = document.getElementById('tooltip');
tooltip.textContent = text;
tooltip.style.left = x + 'px';
tooltip.style.top = y + 'px';
tooltip.classList.add('show');
setTimeout(() => tooltip.classList.remove('show'), 1500);
}
/* === COPYABLE VALUES === */
// Uses Tooltip (defined above)
function copyValue(el) {
navigator.clipboard.writeText(el.textContent.trim()).then(() => {
el.classList.add('copied');
const tooltip = document.getElementById('tooltip');
const rect = el.getBoundingClientRect();
tooltip.style.left = rect.left + rect.width/2 - 40 + 'px';
tooltip.style.top = rect.top - 40 + 'px';
tooltip.classList.add('show');
setTimeout(() => {
el.classList.remove('copied');
tooltip.classList.remove('show');
}, 1500);
});
}
/* === SELECTABLE CARDS === */
document.querySelectorAll('.card:not(.disabled)').forEach(card => {
card.addEventListener('click', () => {
card.classList.toggle('selected');
});
});
.container {
max-width: 1200px; /* Or 1100px for narrower layouts */
margin: 0 auto;
padding: 24px;
}
Max-widths: Default tools: 1200px | Narrower/reading: 1100px | Compact: 900px
/* Auto-fill responsive grid */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 10px;
}
/* Larger cards */
.grid-lg {
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 15px;
}
/* Two-column info layout */
.grid-2 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
/* Horizontal group with wrap */
.flex-row {
display: flex;
align-items: center;
gap: 15px;
flex-wrap: wrap;
}
/* Space between header */
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
/* Vertical stack */
.flex-col {
display: flex;
flex-direction: column;
gap: 8px;
}
Main container for grouping content. Features border, padding, and optional header with bottom border.
Panel content goes here. This is the standard container for toolkit sections.
.panel {
background: rgba(22, 27, 34, 0.8);
border: 1px solid #30363d;
border-radius: 6px;
padding: 20px;
margin-bottom: 16px;
}
.panel h2 {
margin: 0;
padding-bottom: 12px;
border-bottom: 1px solid #30363d;
color: #58a6ff;
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
}
/* Spacing between elements */
.panel h2 + * { margin-top: 12px; }
/* Symmetric padding */
.panel > *:last-child { margin-bottom: 0; }
<div class="panel">
<h2>Panel Header</h2>
<p>Panel content goes here.</p>
</div>
Selectable cards with hover, selected, and disabled states. Used for equipment/option selection.
.card {
background: rgba(22, 27, 34, 0.8);
border: 1px solid #30363d;
border-radius: 6px;
padding: 12px;
cursor: pointer;
transition: all 0.15s;
}
.card:hover {
border-color: #58a6ff;
background: rgba(88, 166, 255, 0.08);
}
.card.selected {
border-color: #00ff88;
background: rgba(0, 255, 136, 0.1);
}
.card.disabled {
opacity: 0.4;
cursor: not-allowed;
}
<!-- Default card -->
<div class="card">
<div class="card-name">Server Name</div>
<div class="card-meta">💰 500</div>
</div>
<!-- Selected state -->
<div class="card selected">...</div>
<!-- Disabled state -->
<div class="card disabled">...</div>
Action buttons in three variants: primary (green), secondary (subtle), and danger (red).
/* Primary Button (Green) */
.btn-primary {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border-radius: 4px;
font-size: 11px;
font-family: inherit;
font-weight: 500;
cursor: pointer;
transition: all 0.15s;
background: rgba(0, 255, 136, 0.15);
border: 1px solid #00ff88;
color: #00ff88;
}
.btn-primary:hover {
background: rgba(0, 255, 136, 0.25);
box-shadow: 0 0 12px rgba(0, 255, 136, 0.3);
}
/* Secondary Button (Subtle) */
.btn-secondary {
background: rgba(88, 166, 255, 0.1);
border: 1px solid #30363d;
color: #8b949e;
}
.btn-secondary:hover {
border-color: #58a6ff;
color: #58a6ff;
}
/* Danger Button (Red) */
.btn-danger {
background: rgba(248, 81, 73, 0.15);
border: 1px solid #f85149;
color: #f85149;
}
.btn-danger:hover {
background: rgba(248, 81, 73, 0.25);
box-shadow: 0 0 12px rgba(248, 81, 73, 0.3);
}
<button class="btn-primary">Primary Button</button>
<button class="btn-secondary">Secondary Button</button>
<button class="btn-danger">Danger Button</button>
Inline labels for selections, filters, or status indicators. Optional remove button for dismissible tags.
.tag {
background: rgba(0, 255, 136, 0.15);
border: 1px solid #00ff88;
color: #00ff88;
padding: 6px 12px;
border-radius: 4px;
font-weight: 500;
font-size: 11px;
display: inline-flex;
align-items: center;
gap: 8px;
}
.tag .remove {
cursor: pointer;
opacity: 0.7;
}
.tag .remove:hover {
opacity: 1;
}
<!-- Simple tag -->
<span class="tag">Selected</span>
<!-- Dismissible tag -->
<span class="tag">
DNS-Lite
<span class="remove">✕</span>
</span>
Text inputs and custom dropdowns. Native selects don't style well on dark themes, so we use custom dropdowns.
/* Text Input */
input[type="text"] {
width: 100%;
padding: 10px 12px;
border-radius: 6px;
border: 1px solid #30363d;
background: rgba(22, 27, 34, 0.8);
color: #c9d1d9;
font-size: 12px;
font-family: inherit;
}
input:focus {
outline: none;
border-color: #58a6ff;
}
/* Custom Dropdown Container */
.dropdown {
position: relative;
}
.dropdown-selected {
padding: 10px 32px 10px 12px;
border: 1px solid #30363d;
border-radius: 6px;
background: rgba(22, 27, 34, 0.8);
color: #c9d1d9;
cursor: pointer;
}
.dropdown-selected::after {
content: "▼";
position: absolute;
right: 12px;
color: #8b949e;
}
.dropdown-options {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: rgba(22, 27, 34, 0.95);
border: 1px solid #58a6ff;
border-top: none;
max-height: 150px;
overflow-y: auto;
display: none;
}
.dropdown.open .dropdown-options {
display: block;
}
.dropdown-option {
padding: 10px 12px;
cursor: pointer;
}
.dropdown-option:hover {
background: rgba(88, 166, 255, 0.15);
}
.dropdown-option.selected {
background: rgba(0, 255, 136, 0.15);
color: #00ff88;
}
<!-- Text input -->
<input type="text" placeholder="Enter text...">
<!-- Custom dropdown -->
<div class="dropdown">
<div class="dropdown-selected">Select option...</div>
<div class="dropdown-options">
<div class="dropdown-option">Option 1</div>
<div class="dropdown-option selected">Option 2</div>
<div class="dropdown-option">Option 3</div>
</div>
</div>
// Toggle dropdown on click
document.querySelectorAll('.dropdown').forEach(dropdown => {
dropdown.addEventListener('click', () => {
dropdown.classList.toggle('open');
});
});
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
if (!e.target.closest('.dropdown')) {
document.querySelectorAll('.dropdown.open')
.forEach(d => d.classList.remove('open'));
}
});
// Handle option selection
document.querySelectorAll('.dropdown-option').forEach(option => {
option.addEventListener('click', (e) => {
e.stopPropagation();
const dropdown = option.closest('.dropdown');
const selected = dropdown.querySelector('.dropdown-selected');
selected.textContent = option.textContent;
dropdown.querySelectorAll('.dropdown-option')
.forEach(o => o.classList.remove('selected'));
option.classList.add('selected');
dropdown.classList.remove('open');
});
});
Numeric input with −/+ buttons. Used for SATA expansion slots, quantities, etc.
/* Stepper Container */
.stepper {
display: flex;
align-items: center;
gap: 0;
background: #21262d;
border-radius: 4px;
overflow: hidden;
border: 1px solid #30363d;
}
.stepper button {
width: 28px;
height: 26px;
border: none;
background: linear-gradient(180deg, #2d333b 0%, #22272e 100%);
color: #58a6ff;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.15s;
}
.stepper button:hover:not(:disabled) {
background: linear-gradient(180deg, #3d444d 0%, #2d333b 100%);
color: #00ff88;
}
.stepper button:disabled {
color: #484f58;
cursor: not-allowed;
background: #21262d;
}
.stepper-value {
min-width: 40px;
text-align: center;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: #00ff88;
padding: 0 6px;
background: rgba(0, 255, 136, 0.05);
}
/* Optional: Row wrapper with label */
.stepper-row {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
background: rgba(22, 27, 34, 0.8);
border: 1px solid #30363d;
border-radius: 6px;
}
<div class="stepper-row">
<span class="stepper-label">Quantity</span>
<div class="stepper">
<button class="stepper-minus">−</button>
<span class="stepper-value">3</span>
<button class="stepper-plus">+</button>
</div>
<span class="stepper-info">(0-10 available)</span>
</div>
function updateStepper(btn, delta) {
const container = btn.parentElement;
const valueEl = container.querySelector('.stepper-value');
const parts = valueEl.textContent.split('/');
let current = parseInt(parts[0]);
const max = parseInt(parts[1]);
current = Math.max(0, Math.min(max, current + delta));
valueEl.textContent = current + '/' + max;
}
Expandable sections for organizing code examples. Click header to toggle, copy button copies code without toggling.
// This section starts expanded (has .open class)
// Click the header to collapse it
console.log("Hello from the collapsible panel!");
👇 Expand the code sections below to see how it's built.
/* Collapsible section */
.collapsible {
border: 1px solid #30363d;
border-radius: 6px;
margin-bottom: 8px;
overflow: hidden;
}
.collapsible-header {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
background: rgba(0, 0, 0, 0.3);
cursor: pointer;
user-select: none;
transition: background 0.15s;
}
.collapsible-header:hover {
background: rgba(0, 0, 0, 0.5);
}
.collapsible-arrow {
color: #8b949e;
font-size: 10px;
transition: transform 0.2s;
width: 12px;
}
.collapsible.open .collapsible-arrow {
transform: rotate(90deg);
}
.collapsible-title {
color: #c9d1d9;
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.collapsible-spacer { flex: 1; }
.collapsible-copy {
padding: 4px 8px;
background: rgba(88, 166, 255, 0.1);
border: 1px solid #30363d;
border-radius: 4px;
color: #8b949e;
font-size: 10px;
cursor: pointer;
transition: all 0.15s;
}
.collapsible-copy:hover {
border-color: #58a6ff;
color: #58a6ff;
}
.collapsible-copy.copied {
border-color: #00ff88;
color: #00ff88;
}
.collapsible-content {
display: none;
border-top: 1px solid #30363d;
}
.collapsible.open .collapsible-content {
display: block;
}
.collapsible-content pre {
margin: 0;
border: none;
border-radius: 0;
max-height: 250px;
overflow-y: auto;
}
<div class="collapsible">
<div class="collapsible-header">
<span class="collapsible-arrow">▶</span>
<span class="collapsible-title">Section Title</span>
<span class="collapsible-spacer"></span>
<button class="collapsible-copy">📋 Copy</button>
</div>
<div class="collapsible-content">
<pre><code>Your code here...</code></pre>
</div>
</div>
// Toggle collapsible on header click
document.querySelectorAll('.collapsible-header').forEach(header => {
header.addEventListener('click', function(e) {
// Don't toggle if clicking the copy button
if (e.target.closest('.collapsible-copy')) return;
const collapsible = this.closest('.collapsible');
collapsible.classList.toggle('open');
});
});
// Copy button functionality
document.querySelectorAll('.collapsible-copy').forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const collapsible = this.closest('.collapsible');
const codeEl = collapsible.querySelector('code');
const text = codeEl.textContent;
navigator.clipboard.writeText(text).then(() => {
const originalText = this.innerHTML;
this.innerHTML = '✓ Copied';
this.classList.add('copied');
setTimeout(() => {
this.innerHTML = originalText;
this.classList.remove('copied');
}, 2000);
});
});
});
Data tables with header styling, row hover, and proper spacing. Last row omits border for symmetric padding.
| Name | Type | Cost |
|---|---|---|
| Server 01 | Rack | $500 |
| Server 02 | Blade | $750 |
.table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
line-height: 1.3;
margin: 0;
}
.table th {
text-align: left;
color: #58a6ff;
font-weight: 600;
padding: 0 12px 10px 12px;
border-bottom: 1px solid #30363d;
font-size: 11px;
text-transform: uppercase;
}
.table td {
padding: 10px 12px;
border-bottom: 1px solid rgba(48, 54, 61, 0.5);
vertical-align: top;
}
/* Last row: no border, no bottom padding */
.table tr:last-child td {
border-bottom: none;
padding-bottom: 0;
}
.table tr:hover td {
background: rgba(88, 166, 255, 0.05);
}
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<tr>
<td>Server 01</td>
<td>Rack</td>
<td>$500</td>
</tr>
</tbody>
</table>
Alert boxes in three variants: warning (orange), success (green), and error (red).
/* Warning/Info Callout (default) */
.callout {
background: rgba(240, 136, 62, 0.08);
border: 1px solid #f0883e;
border-radius: 6px;
padding: 16px;
}
/* Success Callout */
.callout.success {
background: rgba(0, 255, 136, 0.08);
border-color: #00ff88;
}
/* Error Callout */
.callout.error {
background: rgba(248, 81, 73, 0.08);
border-color: #f85149;
}
<!-- Warning (default) -->
<div class="callout">
<strong>⚠️ Warning</strong>
<p>This action cannot be undone.</p>
</div>
<!-- Success -->
<div class="callout success">...</div>
<!-- Error -->
<div class="callout error">...</div>
Floating feedback tooltip for copy actions and hover hints. Fixed position with high z-index.
.tooltip {
position: fixed;
background: #238636;
color: #fff;
padding: 6px 12px;
border-radius: 4px;
font-weight: 500;
font-size: 11px;
z-index: 1000;
opacity: 0;
pointer-events: none;
transition: opacity 0.15s;
}
.tooltip.show {
opacity: 1;
}
<div class="tooltip" id="tooltip">Copied!</div>
function showTooltip(x, y, text) {
const tooltip = document.getElementById('tooltip');
tooltip.textContent = text;
tooltip.style.left = x + 'px';
tooltip.style.top = y + 'px';
tooltip.classList.add('show');
setTimeout(() => {
tooltip.classList.remove('show');
}, 1500);
}
Resource usage bars with three color states: normal (green), warning (orange), and danger (red).
.stat-bar {
display: flex;
align-items: center;
gap: 10px;
}
.stat-track {
flex: 1;
height: 8px;
background: rgba(22, 27, 34, 0.8);
border-radius: 4px;
overflow: hidden;
}
.stat-fill {
height: 100%;
background: #00ff88;
transition: width 0.3s;
}
.stat-fill.warning { background: #f0883e; }
.stat-fill.danger { background: #f85149; }
<div class="stat-bar">
<span class="stat-label">CPU</span>
<div class="stat-track">
<div class="stat-fill" style="width: 45%"></div>
</div>
<span class="stat-value">7/16</span>
</div>
<!-- Warning state (orange) -->
<div class="stat-fill warning" style="width: 75%"></div>
<!-- Danger state (red) -->
<div class="stat-fill danger" style="width: 92%"></div>
Placeholder message for empty containers with optional hint text.
.empty-state {
text-align: center;
color: #8b949e;
padding: 40px;
font-size: 12px;
}
.empty-state small {
display: block;
margin-top: 8px;
color: #7d8590;
}
<div class="empty-state">
No programs selected
<small>Click programs above to add them</small>
</div>
Click-to-copy elements with visual hover and copied states.
SEED-12345
← hover state
SEED-12345
✓ copied state
/* ─────────────────────────────────────────────────────────
DEPENDENCY: Tooltip (see UI Components › Tooltip)
Include once per page. Skip if already defined elsewhere.
───────────────────────────────────────────────────────── */
.tooltip {
position: fixed;
background: #238636;
color: #fff;
padding: 6px 12px;
border-radius: 4px;
font-weight: 500;
font-size: 11px;
pointer-events: none;
opacity: 0;
transition: opacity 0.15s;
z-index: 1000;
}
.tooltip.show { opacity: 1; }
/* ─────────────────────────────────────────────────────────
Copyable Element
───────────────────────────────────────────────────────── */
.copyable {
cursor: pointer;
transition: background 0.15s;
}
.copyable:hover {
background: rgba(0, 255, 136, 0.15);
}
.copyable.copied {
background: rgba(35, 134, 54, 0.3);
}
<!-- DEPENDENCY: Tooltip (see UI Components › Tooltip)
Include once per page. Skip if already defined elsewhere. -->
<div class="tooltip" id="tooltip">Copied!</div>
<!-- Copyable element -->
<code class="copyable" onclick="copyValue(this)">SEED-12345</code>
// Requires: Tooltip HTML element with id="tooltip"
function copyValue(el) {
navigator.clipboard.writeText(el.textContent.trim()).then(() => {
el.classList.add('copied');
const tooltip = document.getElementById('tooltip');
const rect = el.getBoundingClientRect();
tooltip.style.left = rect.left + rect.width/2 - 40 + 'px';
tooltip.style.top = rect.top - 40 + 'px';
tooltip.classList.add('show');
setTimeout(() => {
el.classList.remove('copied');
tooltip.classList.remove('show');
}, 1500);
});
}
Interactive card grid for selecting items. Click to toggle selection state. Uses the standard card component with selection behavior.
<div class="grid">
<div class="card" onclick="this.classList.toggle('selected')">
<div class="card-name">Item Name</div>
<div class="card-meta">💰 500</div>
</div>
</div>
// Toggle selection on click
document.querySelectorAll('.card:not(.disabled)').forEach(card => {
card.addEventListener('click', () => {
card.classList.toggle('selected');
});
});
Display prominent values like seeds or codes that users can click to copy. Uses green accent with subtle glow.
/* ─────────────────────────────────────────────────────────
DEPENDENCY: Tooltip (see UI Components › Tooltip)
Include once per page. Skip if already defined elsewhere.
───────────────────────────────────────────────────────── */
.tooltip {
position: fixed;
background: #238636;
color: #fff;
padding: 6px 12px;
border-radius: 4px;
font-weight: 500;
font-size: 11px;
pointer-events: none;
opacity: 0;
transition: opacity 0.15s;
z-index: 1000;
}
.tooltip.show { opacity: 1; }
/* ─────────────────────────────────────────────────────────
Copyable Code Value
───────────────────────────────────────────────────────── */
.code-value {
font-size: 1.4em;
font-weight: 600;
color: #00ff88;
text-align: center;
padding: 10px 20px;
background: rgba(0, 255, 136, 0.08);
border: 1px solid #238636;
border-radius: 4px;
letter-spacing: 4px;
cursor: pointer;
transition: background 0.15s;
}
.code-value:hover {
background: rgba(0, 255, 136, 0.15);
}
.code-value.copied {
background: rgba(35, 134, 54, 0.3);
}
<!-- DEPENDENCY: Tooltip (see UI Components › Tooltip)
Include once per page. Skip if already defined elsewhere. -->
<div class="tooltip" id="tooltip">Copied!</div>
<!-- Copyable Code Value -->
<div class="code-value" onclick="copyValue(this)">SEED-7X9K2</div>
// Requires: Tooltip HTML element with id="tooltip"
function copyValue(el) {
navigator.clipboard.writeText(el.textContent.trim()).then(() => {
el.classList.add('copied');
const tooltip = document.getElementById('tooltip');
const rect = el.getBoundingClientRect();
tooltip.style.left = rect.left + rect.width/2 - 40 + 'px';
tooltip.style.top = rect.top - 40 + 'px';
tooltip.classList.add('show');
setTimeout(() => {
el.classList.remove('copied');
tooltip.classList.remove('show');
}, 1500);
});
}
Style for code snippets within text. Uses green accent with dark background for visibility.
Edit tni-store.json to add items
Run program install dns-lite on 123
code {
background: rgba(0, 0, 0, 0.3);
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
color: #00ff88;
}
<p>Edit <code>tni-store.json</code> to add items</p>
Search input for filtering lists or grids. Uses the standard form input styling with search icon placeholder.
<input
type="text"
id="search"
class="input"
placeholder="🔍 Filter programs..."
oninput="filterItems(this.value)"
>
function filterItems(query) {
const lower = query.toLowerCase();
document.querySelectorAll('.card').forEach(card => {
const name = card.querySelector('.card-name').textContent.toLowerCase();
card.style.display = name.includes(lower) ? 'block' : 'none';
});
}
/* Mobile: 600px and below */
@media (max-width: 600px) {
body { padding: 16px; }
.container { padding: 16px; }
h1 { font-size: 18px; }
.grid { grid-template-columns: 1fr; }
.flex-row {
flex-direction: column;
align-items: stretch;
}
}
max-width: 1200pxmax-width: 1100pxmax-width: 900pxtransition: all 0.15s for hover effectscursor: pointer on clickable elementstitle attributes on interactive elements| Emoji | Usage |
|---|---|
| 📋 | Selection, configuration |
| 📊 | Data, statistics, results |
| 🎯 | Results, matches, targets |
| 🛠️ | Tools, settings |
| 🗺️ | Roadmap, planning |
| 💰 | Cost, currency |
| ⚡ | Power, energy |
| 📜 | Policy, rules |
| 🦄 | Unique/rare items |
| ✓ | Complete, done |
| ○ | Pending, incomplete |
All project files include standardized headers for version tracking, attribution, and changelog. This ensures consistency across all file types and makes contributions traceable.
JSON data files include a _meta object as the first key:
{
"_meta": {
"game": "Tower Networking Inc.",
"dataset": "dataset-name",
"version": "1.0.0",
"last_updated": "YYYY-MM-DD",
"description": "What this dataset contains",
"sources": [...],
"contributors": ["Name or Handle"],
"corrections": [...],
"future_additions": [...]
}
}
| Field | Required | Description |
|---|---|---|
| game | Yes | Always "Tower Networking Inc." |
| dataset | Yes | Identifier for the dataset (e.g., "programs", "servers") |
| version | Yes | Semantic version (X.Y.Z) |
| last_updated | Yes | Last modified date (YYYY-MM-DD) |
| description | Yes | What this dataset contains |
| sources | No | Array of data sources (in-game, wiki, etc.) |
| contributors | Yes | Array of contributor names/handles |
| corrections | No | Array of data corrections made |
| future_additions | No | Array of planned additions |
HTML tools include a comment header before <!DOCTYPE html>:
<!--
╔══════════════════════════════════════════════════════════════════════════════╗
║ Tool Name ║
╠══════════════════════════════════════════════════════════════════════════════╣
║ Version: X.Y.Z ║
║ Updated: YYYY-MM-DD ║
║ Part of: TNI Toolkit (https://github.com/salvo-praxis/tni-toolkit) ║
╠══════════════════════════════════════════════════════════════════════════════╣
║ Description: ║
║ Brief description of what this tool does. ║
║ ║
║ Contributors: ║
║ - Name (contribution type) ║
║ ║
║ Changelog: ║
║ X.Y.Z - Description of changes ║
╚══════════════════════════════════════════════════════════════════════════════╝
-->
| Field | Required | Description |
|---|---|---|
| Tool Name | Yes | Name displayed in the header box |
| Version | Yes | Semantic version (X.Y.Z) |
| Updated | Yes | Last modified date (YYYY-MM-DD) |
| Part of | Yes | Always "TNI Toolkit" with GitHub URL |
| Description | Yes | What the tool does (can be multi-line) |
| Contributors | Yes | List with contribution types in parentheses |
| Changelog | Yes | Version history with descriptions |
YAML configuration files (GitHub Actions, CI/CD) use comment headers:
# ============================================================================
# TNI Toolkit - [File Type/Purpose]
# File: [path/to/file.yml]
# Name: [Workflow or Config Name]
# Version: X.Y.Z
# Updated: YYYY-MM-DD
# Part of: TNI Toolkit (https://github.com/salvo-praxis/tni-toolkit)
# Description:
# Brief description of what this file does.
# Can span multiple lines if needed.
# Contributors:
# - Name (@handle) - contribution type
# - Claude - contribution type
# Changelog:
# X.Y.Z - Description of changes
# ============================================================================
| Field | Required | Description |
|---|---|---|
| File | Yes | Path relative to repo root |
| Name | Yes | Human-readable name (matches workflow name: key) |
| Version | Yes | Semantic version (X.Y.Z) |
| Updated | Yes | Last modified date (YYYY-MM-DD) |
| Part of | Yes | Always "TNI Toolkit" with GitHub URL |
| Description | Yes | What the file does (can be multi-line) |
| Contributors | Yes | List of contributors with @handles and roles |
| Changelog | Yes | Version history with descriptions |
Python scripts use docstring headers:
#!/usr/bin/env python3
"""
TNI Toolkit - [Script Name]
File: [path/to/script.py]
Version: X.Y.Z
Updated: YYYY-MM-DD
Part of: TNI Toolkit (https://github.com/salvo-praxis/tni-toolkit)
Description:
Brief description of what this script does.
Can span multiple lines if needed.
Contributors:
- Name (@handle) - contribution type
- Claude - contribution type
Changelog:
X.Y.Z - Description of changes
"""
| Field | Required | Description |
|---|---|---|
| Shebang | Yes | Always #!/usr/bin/env python3 |
| Script Name | Yes | Human-readable name of the script |
| File | Yes | Path relative to repo root |
| Version | Yes | Semantic version (X.Y.Z) |
| Updated | Yes | Last modified date (YYYY-MM-DD) |
| Part of | Yes | Always "TNI Toolkit" with GitHub URL |
| Description | Yes | What the script does (indented, can be multi-line) |
| Contributors | Yes | List of contributors with @handles and roles |
| Changelog | Yes | Version history with descriptions |
#!/bin/bash
# ============================================================================
# TNI Toolkit - [Script Name]
# File: [path/to/script.sh]
# Version: X.Y.Z
# Updated: YYYY-MM-DD
# Part of: TNI Toolkit (https://github.com/salvo-praxis/tni-toolkit)
# Description:
# Brief description of what this script does.
# Contributors:
# - Name (@handle) - contribution type
# Changelog:
# X.Y.Z - Description of changes
# ============================================================================
<#
============================================================================
TNI Toolkit - [Script Name]
File: [path/to/script.ps1]
Version: X.Y.Z
Updated: YYYY-MM-DD
Part of: TNI Toolkit (https://github.com/salvo-praxis/tni-toolkit)
Description:
Brief description of what this script does.
Contributors:
- Name (@handle) - contribution type
Changelog:
X.Y.Z - Description of changes
============================================================================
#>
| Field | Required | Description |
|---|---|---|
| Shebang | Yes | #!/bin/bash for Bash; none for PowerShell |
| Script Name | Yes | Human-readable name of the script |
| File | Yes | Path relative to repo root |
| Version | Yes | Semantic version (X.Y.Z) |
| Updated | Yes | Last modified date (YYYY-MM-DD) |
| Part of | Yes | Always "TNI Toolkit" with GitHub URL |
| Description | Yes | What the script does |
| Contributors | Yes | List of contributors with @handles and roles |
| Changelog | Yes | Version history with descriptions |