Files
memory-infrastructure-palace/code/websites/pokedex.online/src/views/ChallongeTest.vue

1431 lines
28 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="challonge-test">
<div class="container">
<div class="header">
<router-link to="/" class="back-button"> Back Home </router-link>
<h1>Challonge API Test</h1>
</div>
<p class="description">
Test your Challonge API connection and verify your configuration.
</p>
<!-- API Version & Settings Controls -->
<div v-if="apiKey" class="section controls-section">
<div class="controls-grid">
<!-- API Version Selector Component -->
<ApiVersionSelector
v-model="apiVersion"
v-model:per-page="perPage"
v-model:scope="tournamentScope"
:show-per-page="true"
:show-scope="true"
@update:per-page="changePerPage"
/>
<!-- API Key Configuration -->
<div class="control-group collapsible-group">
<div
class="collapsible-header"
@click="apiKeyCollapsed = !apiKeyCollapsed"
>
<label class="control-label">API Key Configuration</label>
<span class="collapse-icon">{{
apiKeyCollapsed ? '▶' : '▼'
}}</span>
</div>
<div v-show="!apiKeyCollapsed" class="collapsible-content">
<div class="status success">
API Key Loaded: {{ maskedApiKey }}
</div>
<router-link to="/api-key-manager" class="btn-link">
Manage your API key
</router-link>
<p class="api-note">
💡 Your API v1 key works with both API versions
</p>
</div>
</div>
<!-- OAuth Authentication (v2.1 only) -->
<div
v-if="apiVersion === 'v2.1'"
class="control-group oauth-section collapsible-group"
>
<div
class="collapsible-header"
@click="oauthCollapsed = !oauthCollapsed"
>
<label class="control-label">OAuth Authentication</label>
<span class="collapse-icon">{{
oauthCollapsed ? '▶' : '▼'
}}</span>
</div>
<div v-show="!oauthCollapsed" class="collapsible-content">
<div v-if="isAuthenticated" class="oauth-status">
<span class="status-badge status-connected"> Connected</span>
<button @click="oauthLogout" class="btn btn-secondary btn-sm">
Logout
</button>
</div>
<div v-else class="oauth-status">
<span class="status-badge status-disconnected"
> Not Connected</span
>
<button
@click="oauthLogin('me tournaments:read tournaments:write')"
class="btn btn-primary btn-sm"
:disabled="oauthLoading"
>
{{ oauthLoading ? 'Connecting...' : 'Connect with OAuth' }}
</button>
</div>
<span class="oauth-hint">
{{
isAuthenticated
? 'Using OAuth - APPLICATION scope available'
: 'Connect to enable APPLICATION scope'
}}
</span>
</div>
</div>
<!-- Client Credentials (v2.1 only) -->
<div
v-if="apiVersion === 'v2.1'"
class="control-group collapsible-group"
>
<div
class="collapsible-header"
@click="clientCredsCollapsed = !clientCredsCollapsed"
>
<label class="control-label">Client Credentials</label>
<span class="collapse-icon">{{
clientCredsCollapsed ? '▶' : '▼'
}}</span>
</div>
<div v-show="!clientCredsCollapsed" class="collapsible-content">
<div class="info-badge" v-if="isClientCredsAuthenticated">
Client Credentials Active
</div>
<div
class="info-badge warning"
v-if="
isClientCredsAuthenticated &&
tournamentScope === ScopeType.USER
"
>
⚠️ Client credentials may not work with USER scope - switch to
APPLICATION scope
</div>
<router-link
to="/client-credentials"
class="btn btn-secondary btn-sm"
>
Manage Client Credentials
</router-link>
<span class="scope-hint">
Client credentials should use APPLICATION scope for tournament
access
</span>
</div>
</div>
</div>
</div>
<!-- No API Key Stored -->
<div v-if="!apiKey" class="section warning-section no-key-section">
<div class="warning-content">
<h2>⚠️ No API Key Found</h2>
<p class="warning-text">
Please store your Challonge API key in the API Key Manager to get
started.
</p>
<router-link to="/api-key-manager" class="btn btn-primary btn-lg">
Go to API Key Manager
</router-link>
<p class="hint-text">
The API Key Manager securely stores your key in your browser. Set it
up once and use it across all tools.
</p>
</div>
</div>
<!-- List Tournaments Test -->
<div v-if="apiKey" class="section">
<h2>Test API Connection</h2>
<button
@click="testListTournaments()"
:disabled="loading"
class="btn btn-primary"
>
{{ loading ? 'Loading...' : 'List My Tournaments' }}
</button>
<div v-if="error" class="error-message">{{ error }}</div>
<div v-if="paginationInfo" class="pagination-info">
{{ paginationInfo }}
</div>
<!-- Tournament Grid Component -->
<TournamentGrid
:tournaments="tournaments"
:loading="loading"
:loading-more="loadingMore"
:error="error"
v-model:search-query="searchQuery"
:filtered-tournaments="filteredTournaments"
:expanded-tournament-id="expandedTournamentId"
:has-next-page="hasNextPage"
:api-version="apiVersion"
@load-more="loadMoreTournaments"
@toggle-details="toggleTournamentDetails"
>
<template #tournament-details="{ tournament, isExpanded }">
<TournamentDetail
:tournament-details="tournamentDetails"
:loading="tournamentDetailsState.isLoading"
:error="tournamentDetailsState.error.value"
:is-expanded="isExpanded"
/>
</template>
</TournamentGrid>
</div>
<!-- Configuration Instructions -->
<div v-if="!apiKey" class="section info-section">
<h2> How to Set Up Your API Key</h2>
<div class="info-steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h3>Get Your API Key</h3>
<p>
Visit
<a
href="https://challonge.com/settings/developer"
target="_blank"
>
Challonge Developer Settings
</a>
and copy your API key
</p>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h3>Store in API Key Manager</h3>
<p>
Go to the API Key Manager and paste your key. It will be saved
securely in your browser.
</p>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h3>Start Testing</h3>
<p>
Return to this page and test your API connection with the stored
key.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
import { useChallongeApiKey } from '../composables/useChallongeApiKey.js';
import { useChallongeOAuth } from '../composables/useChallongeOAuth.js';
import { useChallongeClientCredentials } from '../composables/useChallongeClientCredentials.js';
import { useChallongeClient } from '../composables/useChallongeClient.js';
import { useChallongeTests } from '../composables/useChallongeTests.js';
import ApiVersionSelector from '../components/challonge/ApiVersionSelector.vue';
import TournamentGrid from '../components/challonge/TournamentGrid.vue';
import TournamentDetail from '../components/challonge/TournamentDetail.vue';
import { ScopeType } from '../services/challonge.service.js';
const { getApiKey } = useChallongeApiKey();
const apiKey = computed(() => getApiKey());
const maskedApiKey = computed(() => {
if (!apiKey.value) return '';
return apiKey.value.slice(0, 4) + '•••••••' + apiKey.value.slice(-4);
});
// OAuth Management
const {
isAuthenticated,
accessToken,
login: oauthLogin,
logout: oauthLogout,
loading: oauthLoading
} = useChallongeOAuth();
// Client Credentials Management
const {
isAuthenticated: isClientCredsAuthenticated,
accessToken: clientCredsToken
} = useChallongeClientCredentials();
// API Configuration
const apiVersion = ref('v2.1');
const tournamentScope = ref(ScopeType.USER);
const perPage = ref(100);
// Debug mode
const debugMode = ref(true);
// Collapsible section states
const apiKeyCollapsed = ref(false);
const oauthCollapsed = ref(false);
const clientCredsCollapsed = ref(false);
// Initialize Challonge Client (replaces ~100 lines of inline client creation)
const {
client,
apiVersion: clientApiVersion,
tournamentScope: clientTournamentScope,
maskedApiKey: clientMaskedApiKey,
authType
} = useChallongeClient(
apiKey,
apiVersion,
tournamentScope,
{
oauthToken: accessToken,
oauthAuthenticated: isAuthenticated,
clientCredsToken: clientCredsToken,
clientCredsAuthenticated: isClientCredsAuthenticated
},
debugMode
);
// Initialize Tournament Tests (replaces ~200 lines of tournament logic)
const {
tournaments,
loading,
loadingMore,
error,
searchQuery,
expandedTournamentId,
currentPage,
hasNextPage,
tournamentDetails,
paginationInfo,
filteredTournaments,
testListTournaments,
loadMoreTournaments,
changePerPage,
toggleTournamentDetails,
resetState,
tournamentDetailsState
} = useChallongeTests(client, apiVersion, tournamentScope);
// Update perPage when selector changes
watch(perPage, newValue => {
changePerPage(newValue);
});
// Watch for API version changes
watch(apiVersion, () => {
resetState();
});
// Check for debug mode on mount
onMounted(() => {
debugMode.value = localStorage.getItem('DEBUG_CHALLONGE') === 'true';
});
</script>
<style scoped>
.challonge-test {
min-height: 100vh;
padding: 2rem 1rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.back-button {
padding: 0.5rem 1rem;
background: #667eea;
color: white;
text-decoration: none;
border-radius: 6px;
font-weight: 600;
transition: all 0.3s ease;
display: inline-block;
}
.back-button:hover {
background: #5568d3;
transform: translateX(-2px);
}
h1 {
color: #333;
margin: 0;
font-size: 2.5rem;
}
.description {
color: #666;
font-size: 1.1rem;
margin-bottom: 2rem;
}
.section {
margin: 2rem 0;
padding: 1.5rem;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
}
.section.warning-section {
background: #fef3c7;
border: 2px solid #fcd34d;
}
.no-key-section {
text-align: center;
padding: 3rem 2rem;
}
.warning-content h2 {
font-size: 1.75rem;
color: #92400e;
margin-bottom: 0.5rem;
}
.warning-text {
font-size: 1.1rem;
color: #78350f;
margin-bottom: 1.5rem;
font-weight: 500;
}
.btn-lg {
padding: 0.875rem 2rem;
font-size: 1.05rem;
display: inline-block;
margin: 0 auto 1.5rem;
}
.hint-text {
font-size: 0.95rem;
color: #78350f;
margin: 0;
font-style: italic;
opacity: 0.9;
}
h2 {
color: #495057;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.status {
padding: 1rem;
border-radius: 6px;
font-weight: 500;
}
.status.success {
background: #d1fae5;
color: #065f46;
border-left: 4px solid #10b981;
}
.status.error {
background: #fee;
color: #c33;
border-left: 4px solid #c33;
}
.btn {
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: 600;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-small {
padding: 0.5rem 1rem;
font-size: 0.875rem;
background: #667eea;
color: white;
margin-top: 0.5rem;
transition: all 0.3s ease;
}
.btn-small:hover {
background: #5568d3;
}
.btn-small.btn-active {
background: #f59e0b;
}
.btn-small.btn-active:hover {
background: #d97706;
}
.tournament-details-inline {
margin-top: 1rem;
padding-top: 1rem;
border-top: 2px solid #e9ecef;
animation: slideDown 0.3s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
max-height: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
max-height: 2000px;
transform: translateY(0);
}
}
.details-header {
margin-bottom: 0.75rem;
}
.details-header h4 {
color: #667eea;
margin: 0;
font-size: 1.1rem;
}
.details-content {
background: #f8f9fa;
padding: 1rem;
border-radius: 6px;
overflow-x: auto;
font-size: 0.875rem;
line-height: 1.5;
max-height: 500px;
overflow-y: auto;
border: 1px solid #e9ecef;
}
.btn-link {
color: #667eea;
text-decoration: none;
font-weight: 600;
display: inline-block;
margin-top: 1rem;
padding: 0.5rem 0;
border-bottom: 2px solid #667eea;
transition: all 0.3s ease;
}
.btn-link:hover {
color: #5568d3;
border-bottom-color: #5568d3;
}
.error-box {
margin-top: 1rem;
padding: 1rem;
background: #fee;
color: #c33;
border-radius: 6px;
border-left: 4px solid #c33;
}
.info-box {
margin-top: 1rem;
padding: 1rem;
background: #e7f3ff;
color: #0c4a6e;
border-radius: 6px;
border-left: 4px solid #0284c7;
}
.results {
margin-top: 1rem;
}
.results h3 {
color: #065f46;
margin-bottom: 1rem;
}
.results-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
flex-wrap: wrap;
gap: 0.5rem;
}
.results-header h3 {
margin: 0;
}
.pagination-info {
color: #666;
font-size: 0.95rem;
margin: 0;
}
/* API Controls Section */
.controls-section {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border: 2px solid #667eea;
}
.controls-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
/* Allow regular control groups (non-collapsible) to be side-by-side at wider screens */
@media (min-width: 769px) {
.controls-grid {
grid-template-columns: repeat(2, 1fr);
}
/* Force collapsible groups to always span full width */
.controls-grid .collapsible-group {
grid-column: 1 / -1;
}
}
.control-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
/* Collapsible Groups */
.collapsible-group {
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 0;
overflow: hidden;
transition: all 0.3s ease;
}
.collapsible-group:hover {
border-color: #667eea;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
}
.collapsible-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
cursor: pointer;
user-select: none;
transition: background 0.2s ease;
}
.collapsible-header:hover {
background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%);
}
.collapsible-header .control-label {
margin: 0;
font-size: 1rem;
font-weight: 700;
color: #495057;
}
.collapse-icon {
font-size: 0.875rem;
color: #667eea;
font-weight: bold;
transition: transform 0.2s ease;
}
.collapsible-content {
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
animation: slideDown 0.3s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.control-label {
font-weight: 600;
color: #495057;
font-size: 0.95rem;
}
.radio-group {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.radio-option {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
padding: 0.5rem 1rem;
background: white;
border: 2px solid #dee2e6;
border-radius: 6px;
transition: all 0.3s ease;
}
.radio-option:hover {
border-color: #667eea;
background: #f8f9ff;
}
.radio-option input[type='radio'] {
cursor: pointer;
width: 18px;
height: 18px;
}
.radio-option span {
font-weight: 500;
color: #495057;
}
.version-badge {
display: inline-block;
padding: 0.375rem 0.75rem;
border-radius: 6px;
font-size: 0.875rem;
font-weight: 600;
margin-top: 0.5rem;
}
.badge-v1 {
background: #fef3c7;
color: #92400e;
border: 1px solid #fbbf24;
}
.badge-v2-1 {
background: #d1fae5;
color: #065f46;
border: 1px solid #10b981;
}
.select-input {
padding: 0.5rem 0.75rem;
border: 2px solid #dee2e6;
border-radius: 6px;
font-size: 0.95rem;
background: white;
cursor: pointer;
transition: all 0.3s ease;
max-width: 200px;
}
.select-input:hover {
border-color: #667eea;
}
.select-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.text-input,
.select-input {
padding: 0.5rem 0.75rem;
border: 2px solid #dee2e6;
border-radius: 6px;
font-size: 0.95rem;
background: white;
transition: all 0.3s ease;
width: 100%;
max-width: 400px;
}
.text-input:hover,
.select-input:hover {
border-color: #667eea;
}
.text-input:focus,
.select-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.text-input::placeholder {
color: #9ca3af;
}
.select-input {
cursor: pointer;
margin-top: 0.5rem;
}
/* OAuth Section */
.oauth-section {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.oauth-status {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.75rem;
background: white;
border: 2px solid #e9ecef;
border-radius: 6px;
flex-wrap: wrap;
}
@media (max-width: 480px) {
.oauth-status {
flex-direction: column;
align-items: flex-start;
gap: 0.75rem;
}
}
.status-badge {
padding: 0.375rem 0.75rem;
border-radius: 4px;
font-size: 0.875rem;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.status-connected {
background: #d1fae5;
color: #065f46;
}
.status-disconnected {
background: #fee2e2;
color: #991b1b;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.875rem;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #5568d3;
transform: translateY(-1px);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-secondary {
background: #e5e7eb;
color: #374151;
}
.btn-secondary:hover {
background: #d1d5db;
}
.btn-sm {
padding: 0.375rem 0.875rem;
font-size: 0.8125rem;
}
/* OAuth Toggle (legacy - can be removed) */
.oauth-label {
display: flex;
align-items: flex-start;
gap: 0.5rem;
padding: 0.75rem;
background: white;
border: 2px dashed #dee2e6;
border-radius: 6px;
cursor: not-allowed;
opacity: 0.6;
}
.checkbox-input {
cursor: not-allowed;
width: 18px;
height: 18px;
margin-top: 2px;
}
.oauth-text {
font-weight: 500;
color: #495057;
}
.oauth-hint {
display: block;
font-size: 0.85rem;
color: #6c757d;
font-weight: 400;
margin-top: 0.25rem;
}
.scope-info {
display: block;
font-size: 0.85rem;
color: #0284c7;
font-weight: 500;
margin-top: 0.25rem;
font-style: italic;
}
.info-badge {
padding: 0.5rem 0.75rem;
background: #d1fae5;
color: #065f46;
border-radius: 6px;
font-size: 0.875rem;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.info-badge.warning {
background: #fef3c7;
color: #92400e;
}
.scope-hint {
display: block;
font-size: 0.8125rem;
color: #6b7280;
margin-top: 0.5rem;
font-style: italic;
}
.api-note {
margin-top: 0.75rem;
padding: 0.75rem;
background: #e7f3ff;
border-left: 3px solid #0284c7;
border-radius: 4px;
font-size: 0.95rem;
color: #0c4a6e;
}
/* Error Improvements */
.error-list {
list-style: none;
padding: 0;
margin: 0.75rem 0;
}
.error-list li {
margin-bottom: 0.5rem;
padding: 0.5rem;
background: rgba(255, 255, 255, 0.5);
border-radius: 4px;
}
.error-status {
font-weight: 700;
margin-right: 0.5rem;
}
.error-message {
color: #991b1b;
}
.error-field {
font-style: italic;
color: #b91c1c;
margin-left: 0.5rem;
}
.help-text {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.help-text p {
margin: 0.5rem 0;
font-weight: 600;
}
.help-text ul {
margin: 0.5rem 0;
padding-left: 1.5rem;
}
.help-text li {
margin: 0.25rem 0;
}
/* Load More Section */
.load-more-section {
margin-top: 2rem;
text-align: center;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover:not(:disabled) {
background: #5a6268;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(108, 117, 125, 0.4);
}
/* Search Box */
.search-box {
margin-bottom: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.search-input {
width: 100%;
padding: 0.75rem 1rem;
border: 2px solid #dee2e6;
border-radius: 6px;
font-size: 1rem;
transition: all 0.3s ease;
}
.search-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.search-info {
color: #6c757d;
font-size: 0.9rem;
font-style: italic;
}
/* Tournament List */
.tournament-list {
display: grid;
gap: 1.5rem;
}
.tournament-card {
background: white;
padding: 1.5rem;
border-radius: 8px;
border: 2px solid #e9ecef;
transition: all 0.3s ease;
}
.tournament-card:hover {
border-color: #667eea;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
transform: translateY(-2px);
}
.tournament-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
gap: 1rem;
flex-wrap: wrap;
}
.tournament-header h4 {
margin: 0;
color: #333;
font-size: 1.25rem;
flex: 1;
}
.tournament-state {
padding: 0.375rem 0.75rem;
border-radius: 6px;
font-size: 0.875rem;
font-weight: 600;
text-transform: capitalize;
}
.tournament-state.pending {
background: #fef3c7;
color: #92400e;
}
.tournament-state.underway,
.tournament-state.in_progress {
background: #dbeafe;
color: #1e40af;
}
.tournament-state.complete {
background: #d1fae5;
color: #065f46;
}
.tournament-details p {
margin: 0.5rem 0;
color: #495057;
font-size: 0.95rem;
}
.tournament-details strong {
color: #333;
}
/* Info Section */
.info-section {
background: linear-gradient(135deg, #e7f3ff 0%, #dbeafe 100%);
border: 2px solid #0284c7;
}
.info-steps {
display: grid;
gap: 1.5rem;
margin-top: 1.5rem;
}
.step {
display: flex;
gap: 1rem;
align-items: flex-start;
background: white;
padding: 1.5rem;
border-radius: 8px;
border-left: 4px solid #0284c7;
}
.step-number {
flex-shrink: 0;
width: 40px;
height: 40px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 1.25rem;
}
.step-content h3 {
margin: 0 0 0.5rem 0;
color: #333;
font-size: 1.1rem;
}
.step-content p {
margin: 0;
color: #495057;
line-height: 1.6;
}
.step-content a {
color: #667eea;
text-decoration: none;
font-weight: 600;
border-bottom: 1px solid #667eea;
}
.step-content a:hover {
color: #5568d3;
border-bottom-color: #5568d3;
}
/* Responsive Design */
@media (max-width: 768px) {
h1 {
font-size: 2rem;
}
.tournament-header {
flex-direction: column;
align-items: flex-start;
}
.step {
flex-direction: column;
}
.step-number {
width: 36px;
height: 36px;
font-size: 1.1rem;
}
.btn {
width: 100%;
}
}
.search-box {
margin-bottom: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.search-input {
width: 100%;
padding: 0.75rem 1rem;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.3s ease;
}
.search-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.search-info {
color: #666;
font-size: 0.9rem;
font-style: italic;
}
.tournament-list {
display: grid;
gap: 1rem;
margin-top: 1rem;
}
.tournament-card {
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.tournament-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.tournament-header h4 {
margin: 0;
color: #333;
font-size: 1.25rem;
}
.tournament-state {
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.tournament-state.pending {
background: #fef3c7;
color: #92400e;
}
.tournament-state.underway {
background: #dbeafe;
color: #1e40af;
}
.tournament-state.complete {
background: #d1fae5;
color: #065f46;
}
.tournament-details p {
margin: 0.5rem 0;
color: #666;
font-size: 0.95rem;
}
.tournament-details-box {
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.tournament-details-box pre {
background: #f8f9fa;
padding: 1rem;
border-radius: 6px;
overflow-x: auto;
font-size: 0.875rem;
line-height: 1.5;
}
.info-section {
background: #e7f3ff;
border-color: #667eea;
}
.info-section h2 {
color: #667eea;
}
.info-steps {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-top: 1.5rem;
}
.step {
display: flex;
gap: 1rem;
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.step-number {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 50%;
background: #667eea;
color: white;
font-weight: 700;
font-size: 1.1rem;
flex-shrink: 0;
}
.step-content h3 {
margin: 0 0 0.5rem 0;
color: #333;
font-size: 1.1rem;
}
.step-content p {
margin: 0;
color: #666;
font-size: 0.95rem;
line-height: 1.5;
}
.step-content a {
color: #667eea;
text-decoration: none;
font-weight: 600;
}
.step-content a:hover {
text-decoration: underline;
}
.code-block {
background: #f8f9fa;
padding: 0.75rem;
border-radius: 6px;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
overflow-x: auto;
margin: 0.5rem 0;
}
code {
background: #f8f9fa;
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
}
.help-text {
margin-top: 0.5rem;
font-size: 0.9rem;
opacity: 0.9;
}
.help-text ul {
margin: 0.5rem 0;
padding-left: 1.5rem;
}
.help-text li {
margin: 0.25rem 0;
}
</style>