Three perceived-performance features: - Preload on hover: internal links prefetch target JSON data after 150ms hover debounce using <link rel=prefetch>. Touch events prefetch immediately. - Skeleton screens: every async page shows a shimmer-animated placeholder matching the final content layout (leaderboard rows, bot profile card, replay canvas, playlist grid, etc.) instead of generic "Loading..." text. - Instant back-cache: back/forward navigation restores scroll position and cached HTML from an in-memory LRU cache (8 pages), making back navigation 0ms. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
566 lines
11 KiB
CSS
566 lines
11 KiB
CSS
/* ──────────────────────────────────────────────────────────────────────────────── */
|
|
/* Component Styles */
|
|
/* ──────────────────────────────────────────────────────────────────────────────── */
|
|
|
|
/* Buttons */
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--space-sm);
|
|
padding: var(--space-sm) var(--space-md);
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
border: none;
|
|
border-radius: var(--radius-md);
|
|
cursor: pointer;
|
|
transition: background-color var(--transition-fast), color var(--transition-fast);
|
|
text-decoration: none;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.btn.primary {
|
|
background-color: var(--accent);
|
|
color: white;
|
|
}
|
|
|
|
.btn.primary:hover:not(:disabled) {
|
|
background-color: var(--accent-hover);
|
|
}
|
|
|
|
.btn.secondary {
|
|
background-color: var(--bg-tertiary);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.btn.secondary:hover:not(:disabled) {
|
|
background-color: var(--text-muted);
|
|
}
|
|
|
|
.btn.small {
|
|
padding: var(--space-xs) var(--space-sm);
|
|
font-size: 0.75rem;
|
|
min-height: 32px;
|
|
min-width: 32px;
|
|
}
|
|
|
|
.btn.large {
|
|
padding: var(--space-md) var(--space-lg);
|
|
font-size: 1rem;
|
|
}
|
|
|
|
/* Cards */
|
|
.card {
|
|
background-color: var(--bg-secondary);
|
|
border-radius: var(--radius-lg);
|
|
padding: var(--space-md);
|
|
margin-bottom: var(--space-md);
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 1.125rem;
|
|
font-weight: 600;
|
|
margin-bottom: var(--space-sm);
|
|
}
|
|
|
|
.card-subtitle {
|
|
font-size: 0.875rem;
|
|
color: var(--text-muted);
|
|
margin-bottom: var(--space-md);
|
|
}
|
|
|
|
/* Form Elements */
|
|
input[type="text"],
|
|
input[type="number"],
|
|
input[type="email"],
|
|
input[type="password"],
|
|
input[type="url"],
|
|
select,
|
|
textarea {
|
|
width: 100%;
|
|
padding: var(--space-sm);
|
|
background-color: var(--bg-primary);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-md);
|
|
color: var(--text-primary);
|
|
font-size: 0.875rem;
|
|
transition: border-color var(--transition-fast);
|
|
}
|
|
|
|
input:focus,
|
|
select:focus,
|
|
textarea:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
}
|
|
|
|
input::placeholder,
|
|
textarea::placeholder {
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
label {
|
|
display: block;
|
|
font-size: 0.875rem;
|
|
color: var(--text-muted);
|
|
margin-bottom: var(--space-xs);
|
|
}
|
|
|
|
/* Tables */
|
|
.table-container {
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
thead {
|
|
background-color: var(--bg-tertiary);
|
|
}
|
|
|
|
th {
|
|
padding: var(--space-sm) var(--space-md);
|
|
text-align: left;
|
|
font-weight: 600;
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
font-size: 0.75rem;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
td {
|
|
padding: var(--space-sm) var(--space-md);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
tr:last-child td {
|
|
border-bottom: none;
|
|
}
|
|
|
|
tbody tr:hover {
|
|
background-color: var(--bg-tertiary);
|
|
}
|
|
|
|
/* Badges */
|
|
.badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 2px var(--space-sm);
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
border-radius: var(--radius-sm);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.badge.success { background-color: rgba(34, 197, 94, 0.2); color: var(--success); }
|
|
.badge.warning { background-color: rgba(245, 158, 11, 0.2); color: var(--warning); }
|
|
.badge.error { background-color: rgba(239, 68, 68, 0.2); color: var(--error); }
|
|
.badge.info { background-color: rgba(59, 130, 246, 0.2); color: var(--accent); }
|
|
|
|
/* Loading Spinner */
|
|
.spinner {
|
|
display: inline-block;
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 2px solid var(--bg-tertiary);
|
|
border-top-color: var(--accent);
|
|
border-radius: 50%;
|
|
animation: spin 0.8s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
.loading {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--space-sm);
|
|
padding: var(--space-xl);
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* Empty State */
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: var(--space-xl);
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.empty-state p {
|
|
margin-bottom: var(--space-md);
|
|
}
|
|
|
|
/* Error State */
|
|
.error {
|
|
background-color: rgba(239, 68, 68, 0.1);
|
|
border: 1px solid var(--error);
|
|
border-radius: var(--radius-md);
|
|
padding: var(--space-md);
|
|
color: var(--error);
|
|
}
|
|
|
|
.error p {
|
|
margin-bottom: var(--space-sm);
|
|
}
|
|
|
|
.error .hint {
|
|
font-size: 0.875rem;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
/* Code Block */
|
|
.code-block {
|
|
background-color: var(--bg-primary);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-md);
|
|
padding: var(--space-md);
|
|
overflow-x: auto;
|
|
font-family: var(--font-mono);
|
|
font-size: 0.875rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
pre {
|
|
margin: 0;
|
|
}
|
|
|
|
code {
|
|
font-family: var(--font-mono);
|
|
font-size: 0.875em;
|
|
background-color: var(--bg-tertiary);
|
|
padding: 2px 6px;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
/* Page Layout */
|
|
.page-title {
|
|
margin-bottom: var(--space-lg);
|
|
}
|
|
|
|
.page-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: var(--space-lg);
|
|
gap: var(--space-md);
|
|
}
|
|
|
|
.page-header h1 {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
/* Grid System */
|
|
.grid {
|
|
display: grid;
|
|
gap: var(--space-md);
|
|
}
|
|
|
|
.grid-2 { grid-template-columns: repeat(2, 1fr); }
|
|
.grid-3 { grid-template-columns: repeat(3, 1fr); }
|
|
.grid-4 { grid-template-columns: repeat(4, 1fr); }
|
|
|
|
@media (max-width: 768px) {
|
|
.grid-2, .grid-3, .grid-4 {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
/* Flex Utilities */
|
|
.flex { display: flex; }
|
|
.flex-col { flex-direction: column; }
|
|
.flex-wrap { flex-wrap: wrap; }
|
|
.items-center { align-items: center; }
|
|
.justify-between { justify-content: space-between; }
|
|
.justify-center { justify-content: center; }
|
|
.gap-sm { gap: var(--space-sm); }
|
|
.gap-md { gap: var(--space-md); }
|
|
.gap-lg { gap: var(--space-lg); }
|
|
|
|
/* Panel */
|
|
.panel {
|
|
background-color: var(--bg-secondary);
|
|
border-radius: var(--radius-lg);
|
|
padding: var(--space-md);
|
|
}
|
|
|
|
.panel-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: var(--space-md);
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
/* Slider */
|
|
.slider-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-xs);
|
|
}
|
|
|
|
.slider-group label {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-size: 0.875rem;
|
|
color: var(--text-muted);
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.slider-group input[type="range"] {
|
|
width: 100%;
|
|
height: 6px;
|
|
padding: 0;
|
|
border: none;
|
|
background: var(--bg-tertiary);
|
|
border-radius: var(--radius-sm);
|
|
appearance: none;
|
|
-webkit-appearance: none;
|
|
}
|
|
|
|
.slider-group input[type="range"]::-webkit-slider-thumb {
|
|
appearance: none;
|
|
-webkit-appearance: none;
|
|
width: 18px;
|
|
height: 18px;
|
|
background: var(--accent);
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.slider-group input[type="range"]::-moz-range-thumb {
|
|
width: 18px;
|
|
height: 18px;
|
|
background: var(--accent);
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
border: none;
|
|
}
|
|
|
|
/* Checkbox */
|
|
.checkbox-label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-sm);
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
|
|
.checkbox-label input[type="checkbox"] {
|
|
width: 18px;
|
|
height: 18px;
|
|
accent-color: var(--accent);
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Leaderboard Table */
|
|
.leaderboard-table {
|
|
width: 100%;
|
|
border-collapse: separate;
|
|
border-spacing: 0;
|
|
}
|
|
|
|
.leaderboard-table tbody tr {
|
|
transition: background-color var(--transition-fast);
|
|
}
|
|
|
|
.leaderboard-table tbody tr.rank-1 {
|
|
background-color: rgba(245, 158, 11, 0.1);
|
|
}
|
|
|
|
.leaderboard-table tbody tr.rank-2 {
|
|
background-color: rgba(148, 163, 184, 0.1);
|
|
}
|
|
|
|
.leaderboard-table tbody tr.rank-3 {
|
|
background-color: rgba(180, 83, 9, 0.1);
|
|
}
|
|
|
|
.leaderboard-table .rank {
|
|
font-weight: 600;
|
|
width: 60px;
|
|
}
|
|
|
|
.leaderboard-table .rank-1 .rank { color: var(--warning); }
|
|
.leaderboard-table .rank-2 .rank { color: #94a3b8; }
|
|
.leaderboard-table .rank-3 .rank { color: #b45309; }
|
|
|
|
.leaderboard-table .rating {
|
|
text-align: right;
|
|
}
|
|
|
|
.leaderboard-table .rating-value {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.leaderboard-table .rating-dev {
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.leaderboard-table .win-rate {
|
|
text-align: right;
|
|
}
|
|
|
|
.leaderboard-table .status {
|
|
text-align: center;
|
|
}
|
|
|
|
.status-healthy { color: var(--success); }
|
|
.status-unhealthy { color: var(--error); }
|
|
.status-unknown { color: var(--text-muted); }
|
|
|
|
/* Replay Canvas */
|
|
.canvas-wrapper {
|
|
background-color: var(--bg-secondary);
|
|
border-radius: var(--radius-lg);
|
|
padding: var(--space-sm);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.canvas-wrapper canvas {
|
|
display: block;
|
|
width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
/* Event Log */
|
|
.event-log {
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
font-size: 0.75rem;
|
|
font-family: var(--font-mono);
|
|
}
|
|
|
|
.event-log .event {
|
|
padding: var(--space-xs) 0;
|
|
border-bottom: 1px solid var(--bg-tertiary);
|
|
}
|
|
|
|
.event-log .event:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
/* Keyboard Shortcuts */
|
|
.keyboard-shortcuts {
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
.keyboard-shortcuts kbd {
|
|
background-color: var(--bg-tertiary);
|
|
padding: 2px 6px;
|
|
border-radius: var(--radius-sm);
|
|
font-family: var(--font-mono);
|
|
}
|
|
|
|
/* ─── Skeleton Screens (§16.14) ────────────────────────────────────────────── */
|
|
|
|
@keyframes skeleton-shimmer {
|
|
0% { background-position: -200% 0; }
|
|
100% { background-position: 200% 0; }
|
|
}
|
|
|
|
.skeleton-bar,
|
|
.skeleton-circle,
|
|
.skeleton-canvas {
|
|
background: linear-gradient(
|
|
90deg,
|
|
var(--bg-tertiary) 25%,
|
|
var(--border) 37%,
|
|
var(--bg-tertiary) 63%
|
|
);
|
|
background-size: 200% 100%;
|
|
animation: skeleton-shimmer 1.5s ease-in-out infinite;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
.skeleton-circle {
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.skeleton-page {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
opacity: 1;
|
|
transition: opacity 150ms ease;
|
|
}
|
|
|
|
.skeleton-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-md);
|
|
padding: var(--space-sm) 0;
|
|
}
|
|
|
|
.skeleton-table-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-md);
|
|
padding: var(--space-sm) var(--space-md);
|
|
background: var(--bg-tertiary);
|
|
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.skeleton-profile-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-lg);
|
|
padding: var(--space-lg);
|
|
background: var(--bg-secondary);
|
|
border-radius: var(--radius-lg);
|
|
}
|
|
|
|
.skeleton-profile-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.skeleton-profile-stats {
|
|
display: flex;
|
|
gap: var(--space-md);
|
|
margin-left: auto;
|
|
}
|
|
|
|
.skeleton-card {
|
|
background: var(--bg-secondary);
|
|
border-radius: var(--radius-lg);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.skeleton-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
gap: var(--space-lg);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.skeleton-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
.skeleton-profile-header {
|
|
flex-direction: column;
|
|
text-align: center;
|
|
}
|
|
.skeleton-profile-stats {
|
|
margin-left: 0;
|
|
justify-content: center;
|
|
}
|
|
}
|