ai-code-battle/web/src/styles/components.css
jedarden 77f713697e feat(web): add enriched replay badge to bot profile match history
§13.3: Display "Narrated" badge on enriched matches in bot profile
recent matches section, matching the match list page behavior.

- Add enriched badge rendering to renderMatchItem() in bot-profile.ts
- Add .enriched-badge CSS style (pink/magenta color to match
  playlist category styling)

The index builder already sets the Enriched flag via isMatchEnriched()
which checks R2 for commentary file existence. The match list page
(matches.ts) already displays this badge.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 18:00:07 -04:00

986 lines
19 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); }
/* Enriched replay badge (§13.3) */
.enriched-badge {
display: inline-flex;
align-items: center;
padding: 2px var(--space-sm);
font-size: 0.7rem;
font-weight: 500;
border-radius: var(--radius-sm);
white-space: nowrap;
background-color: rgba(236, 72, 153, 0.2);
color: #ec4899;
}
/* 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;
}
}
/* ─── §16.15 Progressive Disclosure ─────────────────────────────────────────── */
/* Virtual list (leaderboard) */
.virtual-list {
position: relative;
}
.virtual-list-scroll {
max-height: 70vh;
overflow-y: auto;
outline: none;
}
.virtual-list-scroll:focus-visible {
box-shadow: inset 0 0 0 2px var(--accent);
}
.virtual-list-rows {
position: relative;
}
.virtual-list-row {
display: flex;
align-items: center;
gap: var(--space-md);
padding: var(--space-sm) var(--space-md);
border-bottom: 1px solid var(--border);
cursor: pointer;
transition: background-color var(--transition-fast);
min-height: 48px;
box-sizing: border-box;
}
.virtual-list-row:hover {
background-color: var(--bg-tertiary);
}
.virtual-list-row:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
.virtual-list-row.expanded {
background-color: var(--bg-secondary);
}
.virtual-list-expanded {
padding: var(--space-sm) 0 var(--space-md);
animation: expand-in 200ms ease-out;
}
.virtual-list-show-more {
display: block;
margin: var(--space-md) auto;
min-width: 200px;
}
/* Leaderboard row (desktop — both virtual and static) */
.lb-row {
display: flex;
align-items: center;
gap: var(--space-md);
padding: var(--space-sm) var(--space-md);
border-bottom: 1px solid var(--border);
cursor: pointer;
transition: background-color var(--transition-fast);
min-height: 48px;
box-sizing: border-box;
}
.lb-row:hover {
background-color: var(--bg-tertiary);
}
.lb-row.row-expanded {
background-color: var(--bg-secondary);
}
.lb-rank {
font-weight: 600;
width: 40px;
flex-shrink: 0;
}
.lb-row.rank-1 .lb-rank { color: var(--warning); }
.lb-row.rank-2 .lb-rank { color: #94a3b8; }
.lb-row.rank-3 .lb-rank { color: #b45309; }
.lb-row.rank-1 { background-color: rgba(245, 158, 11, 0.05); }
.lb-row.rank-2 { background-color: rgba(148, 163, 184, 0.05); }
.lb-row.rank-3 { background-color: rgba(180, 83, 9, 0.05); }
.lb-name {
flex: 1;
min-width: 120px;
}
.lb-name a {
color: var(--text-primary);
text-decoration: none;
}
.lb-name a:hover {
text-decoration: underline;
}
.lb-rating {
width: 100px;
text-align: right;
}
.lb-rating .rating-value {
font-weight: 600;
}
.lb-rating .rating-dev {
font-size: 0.75rem;
color: var(--text-muted);
margin-left: 4px;
}
.lb-wl {
width: 60px;
text-align: center;
}
.lb-winrate {
width: 70px;
text-align: right;
}
.lb-status {
width: 80px;
text-align: center;
font-size: 0.8rem;
}
.lb-expand-icon {
width: 20px;
text-align: center;
color: var(--text-muted);
font-size: 0.75rem;
transition: transform var(--transition-fast);
}
.lb-expanded {
padding: var(--space-sm) var(--space-md) var(--space-md);
animation: expand-in 200ms ease-out;
}
.lb-expanded-stats {
display: flex;
gap: var(--space-lg);
margin-bottom: var(--space-sm);
flex-wrap: wrap;
}
.lb-stat {
display: flex;
flex-direction: column;
align-items: center;
min-width: 60px;
}
.lb-stat-val {
font-weight: 600;
font-size: 1.1rem;
}
.lb-stat-label {
font-size: 0.75rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.03em;
}
.lb-profile-link {
display: inline-block;
margin-top: var(--space-xs);
}
.lb-hint {
font-size: 0.8rem;
color: var(--text-muted);
margin-bottom: var(--space-sm);
}
/* Expand/collapse animation */
@keyframes expand-in {
from {
opacity: 0;
max-height: 0;
}
to {
opacity: 1;
max-height: 200px;
}
}
/* Expandable section (profile page) */
.expandable-section {
border: 1px solid var(--border);
border-radius: var(--radius-md);
overflow: hidden;
}
.section-toggle {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
padding: var(--space-md);
background: none;
border: none;
color: var(--text-primary);
cursor: pointer;
font-size: 1rem;
text-align: left;
transition: background-color var(--transition-fast);
}
.section-toggle:hover {
background-color: var(--bg-tertiary);
}
.section-toggle:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
.section-toggle h2 {
margin: 0;
font-size: 1rem;
}
.section-toggle-icon {
color: var(--text-muted);
font-size: 0.75rem;
transition: transform var(--transition-fast);
}
.section-content {
max-height: 0;
overflow: hidden;
transition: max-height 250ms ease-out, padding 250ms ease-out;
padding: 0 var(--space-md);
}
.section-content.expanded {
max-height: 600px;
padding: var(--space-md);
}
/* Match card expand/collapse */
.match-card-toggle {
display: block;
width: 100%;
background: none;
border: none;
color: inherit;
cursor: pointer;
text-align: left;
padding: 0;
}
.match-card-toggle:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
.match-card-details {
max-height: 0;
overflow: hidden;
transition: max-height 200ms ease-out;
padding: 0 var(--space-md);
}
.match-card-details.expanded {
max-height: 100px;
padding: var(--space-sm) var(--space-md) var(--space-md);
}
.match-expand-icon {
color: var(--text-muted);
font-size: 0.75rem;
transition: transform var(--transition-fast);
}
/* Mobile card expand (leaderboard) */
.mobile-card-toggle {
display: flex;
width: 100%;
align-items: center;
background: none;
border: none;
color: inherit;
cursor: pointer;
text-align: left;
padding: 0;
}
.mobile-card-toggle:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
.mobile-card-arrow {
color: var(--text-muted);
font-size: 0.75rem;
margin-left: auto;
padding-right: var(--space-sm);
transition: transform var(--transition-fast);
}
/* "Show more" button (generic) */
.show-more-btn {
display: block;
margin: var(--space-md) auto;
min-width: 200px;
}
.show-more-btn:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* Lazy section placeholder */
.lazy-placeholder {
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);
}
.lazy-section-revealed {
animation: expand-in 200ms ease-out;
}
/* ─── Reduced Motion ────────────────────────────────────────────────────────── */
/* §16.15: all expand/collapse transitions become instant when the user prefers
reduced motion. Prevents animation sickness for vestibular disorders. */
@media (prefers-reduced-motion: reduce) {
.section-content {
transition: none;
}
.match-card-details {
transition: none;
}
.virtual-list-expanded,
.lb-expanded,
.lazy-section-revealed {
animation: none;
}
@keyframes expand-in {
from { opacity: 1; max-height: none; }
to { opacity: 1; max-height: none; }
}
.lb-expand-icon,
.section-toggle-icon,
.match-expand-icon,
.mobile-card-arrow {
transition: none;
}
.leaderboard-mobile-details {
transition: none;
}
}
/* ─── Mobile responsive for progressive disclosure ──────────────────────────── */
@media (max-width: 768px) {
.virtual-list-scroll {
max-height: none; /* Let it flow naturally on mobile */
}
.lb-expanded-stats {
gap: var(--space-md);
}
.lb-row {
gap: var(--space-sm);
padding: var(--space-sm);
}
.lb-wl,
.lb-status {
display: none; /* Hidden on narrow screens, available in expanded */
}
.section-content.expanded {
max-height: 1000px;
}
}