Updated all 4 demo pages to use new action field and added navigation footers for easy cross-linking between demo pages. Changes: - demo-decisions.vue: Updated to use action field, added runner prop bindings - demo.vue: Added footer with links to other demos - demo-gameplay.vue: Added footer with links to other demos - demo-substitutions.vue: Added footer with links to other demos Demo page updates: - OffensiveDecision now uses action field instead of approach/hit_and_run/bunt - Action labels properly mapped (swing_away, steal, check_jump, etc.) - Added runner state props (runnerOnFirst, runnerOnSecond, runnerOnThird) - Added outs prop for smart filtering Footer features: - 3-column grid layout responsive to mobile - Icons and descriptions for each demo - Hover effects on links - Consistent styling across all pages Demo pages now fully connected: - /demo → Game State Demo (ScoreBoard, GameBoard, etc.) - /demo-decisions → Decision Components Demo (new action-based) - /demo-gameplay → Gameplay Components Demo (DiceRoller, ManualOutcome) - /demo-substitutions → Substitution Components Demo Files modified: - pages/demo-decisions.vue - pages/demo.vue - pages/demo-gameplay.vue - pages/demo-substitutions.vue 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
728 lines
20 KiB
Vue
728 lines
20 KiB
Vue
<template>
|
|
<div class="demo-page">
|
|
<!-- Header -->
|
|
<div class="demo-header">
|
|
<h1 class="demo-title">Phase F4: Gameplay Components Demo</h1>
|
|
<p class="demo-subtitle">
|
|
Dice Rolling, Outcome Entry, and Play Results
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<div class="demo-tabs">
|
|
<button
|
|
v-for="tab in tabs"
|
|
:key="tab.id"
|
|
:class="['tab-button', activeTab === tab.id ? 'tab-active' : 'tab-inactive']"
|
|
@click="activeTab = tab.id"
|
|
>
|
|
{{ tab.label }}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="demo-content">
|
|
<!-- Tab 1: DiceRoller -->
|
|
<div v-if="activeTab === 'dice'" class="tab-panel">
|
|
<div class="section">
|
|
<h2 class="section-title">DiceRoller Component</h2>
|
|
<p class="section-description">
|
|
Visual dice roller showing all 4 dice values with roll animation and special event indicators.
|
|
</p>
|
|
|
|
<div class="demo-controls">
|
|
<button class="control-button" @click="toggleDiceRoll">
|
|
{{ demoRollData ? 'Clear Roll' : 'Simulate Roll' }}
|
|
</button>
|
|
<button class="control-button" @click="rollWithWildPitch">
|
|
Roll with Wild Pitch
|
|
</button>
|
|
<button class="control-button" @click="rollWithPassedBall">
|
|
Roll with Passed Ball
|
|
</button>
|
|
</div>
|
|
|
|
<div class="component-showcase">
|
|
<DiceRoller
|
|
:can-roll="!demoRollData"
|
|
:pending-roll="demoRollData"
|
|
@roll="handleDemoRoll"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab 2: ManualOutcomeEntry -->
|
|
<div v-if="activeTab === 'outcome'" class="tab-panel">
|
|
<div class="section">
|
|
<h2 class="section-title">ManualOutcomeEntry Component</h2>
|
|
<p class="section-description">
|
|
Outcome selection form with categorized outcomes and conditional hit location selector.
|
|
</p>
|
|
|
|
<div class="demo-controls">
|
|
<button class="control-button" @click="toggleOutcomeRoll">
|
|
{{ outcomeRollData ? 'Clear Roll' : 'Add Roll Data' }}
|
|
</button>
|
|
</div>
|
|
|
|
<div class="component-showcase">
|
|
<ManualOutcomeEntry
|
|
:roll-data="outcomeRollData"
|
|
:can-submit="!!outcomeRollData"
|
|
@submit="handleOutcomeSubmit"
|
|
@cancel="handleOutcomeCancel"
|
|
/>
|
|
</div>
|
|
|
|
<div v-if="lastSubmittedOutcome" class="result-box">
|
|
<h3 class="result-title">Last Submitted:</h3>
|
|
<pre class="result-code">{{ JSON.stringify(lastSubmittedOutcome, null, 2) }}</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab 3: PlayResult -->
|
|
<div v-if="activeTab === 'result'" class="tab-panel">
|
|
<div class="section">
|
|
<h2 class="section-title">PlayResult Component</h2>
|
|
<p class="section-description">
|
|
Animated play result display with stats, color-coding, and runner advancement visualization.
|
|
</p>
|
|
|
|
<div class="demo-controls">
|
|
<button class="control-button" @click="showStrikeout">
|
|
Strikeout (Out)
|
|
</button>
|
|
<button class="control-button" @click="showHomerun">
|
|
Homerun (Runs)
|
|
</button>
|
|
<button class="control-button" @click="showSingle">
|
|
Single (Hit)
|
|
</button>
|
|
<button class="control-button" @click="showDoublePlay">
|
|
Double Play (2 Outs)
|
|
</button>
|
|
<button class="control-button" @click="showTripleWithRunners">
|
|
Triple (Runners Advance)
|
|
</button>
|
|
<button class="control-button" @click="clearResult">
|
|
Clear Result
|
|
</button>
|
|
</div>
|
|
|
|
<div class="component-showcase">
|
|
<PlayResultDisplay
|
|
:result="demoPlayResult"
|
|
:auto-hide="false"
|
|
@dismiss="handleResultDismiss"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab 4: GameplayPanel (Full Integration) -->
|
|
<div v-if="activeTab === 'panel'" class="tab-panel">
|
|
<div class="section">
|
|
<h2 class="section-title">GameplayPanel Component</h2>
|
|
<p class="section-description">
|
|
Complete workflow orchestration - combines all components with state management.
|
|
</p>
|
|
|
|
<div class="demo-controls">
|
|
<div class="control-group">
|
|
<label class="control-label">
|
|
<input v-model="panelIsMyTurn" type="checkbox">
|
|
Is My Turn
|
|
</label>
|
|
<label class="control-label">
|
|
<input v-model="panelCanRoll" type="checkbox">
|
|
Can Roll Dice
|
|
</label>
|
|
<label class="control-label">
|
|
<input v-model="panelCanSubmit" type="checkbox">
|
|
Can Submit Outcome
|
|
</label>
|
|
</div>
|
|
<button class="control-button" @click="simulatePanelRoll">
|
|
Simulate Dice Roll
|
|
</button>
|
|
<button class="control-button" @click="simulatePanelResult">
|
|
Simulate Play Result
|
|
</button>
|
|
<button class="control-button" @click="resetPanelState">
|
|
Reset to Idle
|
|
</button>
|
|
</div>
|
|
|
|
<div class="component-showcase">
|
|
<GameplayPanel
|
|
game-id="demo-game-123"
|
|
:is-my-turn="panelIsMyTurn"
|
|
:can-roll-dice="panelCanRoll"
|
|
:pending-roll="panelRollData"
|
|
:last-play-result="panelPlayResult"
|
|
:can-submit-outcome="panelCanSubmit"
|
|
@roll-dice="handlePanelRollDice"
|
|
@submit-outcome="handlePanelSubmitOutcome"
|
|
@dismiss-result="handlePanelDismissResult"
|
|
/>
|
|
</div>
|
|
|
|
<div v-if="panelEvents.length > 0" class="events-log">
|
|
<h3 class="events-title">Event Log:</h3>
|
|
<div class="events-list">
|
|
<div v-for="(event, index) in panelEvents" :key="index" class="event-item">
|
|
<span class="event-time">{{ event.time }}</span>
|
|
<span class="event-type">{{ event.type }}</span>
|
|
<pre v-if="event.data" class="event-data">{{ JSON.stringify(event.data, null, 2) }}</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer with Demo Links -->
|
|
<div class="mt-12 border-t border-gray-200 dark:border-gray-700 pt-8">
|
|
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
|
|
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-4">
|
|
📚 Other Demo Pages
|
|
</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<NuxtLink
|
|
to="/demo"
|
|
class="p-4 rounded-lg border-2 border-gray-300 dark:border-gray-600 hover:border-blue-500 dark:hover:border-blue-400 transition-colors"
|
|
>
|
|
<div class="text-2xl mb-2">🎮</div>
|
|
<div class="font-semibold text-gray-900 dark:text-white">Game State Demo</div>
|
|
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
Live game state visualization
|
|
</div>
|
|
</NuxtLink>
|
|
<NuxtLink
|
|
to="/demo-decisions"
|
|
class="p-4 rounded-lg border-2 border-gray-300 dark:border-gray-600 hover:border-blue-500 dark:hover:border-blue-400 transition-colors"
|
|
>
|
|
<div class="text-2xl mb-2">⚔️</div>
|
|
<div class="font-semibold text-gray-900 dark:text-white">Decisions Demo</div>
|
|
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
Decision input components
|
|
</div>
|
|
</NuxtLink>
|
|
<NuxtLink
|
|
to="/demo-substitutions"
|
|
class="p-4 rounded-lg border-2 border-gray-300 dark:border-gray-600 hover:border-blue-500 dark:hover:border-blue-400 transition-colors"
|
|
>
|
|
<div class="text-2xl mb-2">🔄</div>
|
|
<div class="font-semibold text-gray-900 dark:text-white">Substitutions Demo</div>
|
|
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
Player substitution workflow
|
|
</div>
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import type { RollData, PlayResult, PlayOutcome } from '~/types'
|
|
import DiceRoller from '~/components/Gameplay/DiceRoller.vue'
|
|
import ManualOutcomeEntry from '~/components/Gameplay/ManualOutcomeEntry.vue'
|
|
import PlayResultDisplay from '~/components/Gameplay/PlayResult.vue'
|
|
import GameplayPanel from '~/components/Gameplay/GameplayPanel.vue'
|
|
|
|
// Tab state
|
|
const activeTab = ref('dice')
|
|
const tabs = [
|
|
{ id: 'dice', label: 'DiceRoller' },
|
|
{ id: 'outcome', label: 'Outcome Entry' },
|
|
{ id: 'result', label: 'Play Result' },
|
|
{ id: 'panel', label: 'Full Panel' },
|
|
]
|
|
|
|
// DiceRoller demo state
|
|
const demoRollData = ref<RollData | null>(null)
|
|
|
|
const createRandomRoll = (overrides = {}): RollData => ({
|
|
roll_id: `roll-${Date.now()}`,
|
|
d6_one: Math.floor(Math.random() * 6) + 1,
|
|
d6_two_a: Math.floor(Math.random() * 6) + 1,
|
|
d6_two_b: Math.floor(Math.random() * 6) + 1,
|
|
d6_two_total: 0,
|
|
chaos_d20: Math.floor(Math.random() * 20) + 1,
|
|
resolution_d20: Math.floor(Math.random() * 20) + 1,
|
|
check_wild_pitch: false,
|
|
check_passed_ball: false,
|
|
timestamp: new Date().toISOString(),
|
|
...overrides,
|
|
})
|
|
|
|
const toggleDiceRoll = () => {
|
|
if (demoRollData.value) {
|
|
demoRollData.value = null
|
|
} else {
|
|
const roll = createRandomRoll()
|
|
roll.d6_two_total = roll.d6_two_a + roll.d6_two_b
|
|
demoRollData.value = roll
|
|
}
|
|
}
|
|
|
|
const rollWithWildPitch = () => {
|
|
const roll = createRandomRoll({ check_wild_pitch: true })
|
|
roll.d6_two_total = roll.d6_two_a + roll.d6_two_b
|
|
demoRollData.value = roll
|
|
}
|
|
|
|
const rollWithPassedBall = () => {
|
|
const roll = createRandomRoll({ check_passed_ball: true })
|
|
roll.d6_two_total = roll.d6_two_a + roll.d6_two_b
|
|
demoRollData.value = roll
|
|
}
|
|
|
|
const handleDemoRoll = () => {
|
|
const roll = createRandomRoll()
|
|
roll.d6_two_total = roll.d6_two_a + roll.d6_two_b
|
|
demoRollData.value = roll
|
|
}
|
|
|
|
// ManualOutcomeEntry demo state
|
|
const outcomeRollData = ref<RollData | null>(null)
|
|
const lastSubmittedOutcome = ref<any>(null)
|
|
|
|
const toggleOutcomeRoll = () => {
|
|
if (outcomeRollData.value) {
|
|
outcomeRollData.value = null
|
|
} else {
|
|
const roll = createRandomRoll()
|
|
roll.d6_two_total = roll.d6_two_a + roll.d6_two_b
|
|
outcomeRollData.value = roll
|
|
}
|
|
}
|
|
|
|
const handleOutcomeSubmit = (payload: { outcome: PlayOutcome; hitLocation?: string }) => {
|
|
lastSubmittedOutcome.value = payload
|
|
}
|
|
|
|
const handleOutcomeCancel = () => {
|
|
lastSubmittedOutcome.value = null
|
|
}
|
|
|
|
// PlayResult demo state
|
|
const demoPlayResult = ref<PlayResult | null>(null)
|
|
|
|
const showStrikeout = () => {
|
|
demoPlayResult.value = {
|
|
play_number: Math.floor(Math.random() * 100) + 1,
|
|
outcome: 'STRIKEOUT',
|
|
description: 'Mike Trout strikes out swinging',
|
|
outs_recorded: 1,
|
|
runs_scored: 0,
|
|
runners_advanced: [],
|
|
batter_result: null,
|
|
new_state: {},
|
|
is_hit: false,
|
|
is_out: true,
|
|
is_walk: false,
|
|
is_strikeout: true,
|
|
}
|
|
}
|
|
|
|
const showHomerun = () => {
|
|
demoPlayResult.value = {
|
|
play_number: Math.floor(Math.random() * 100) + 1,
|
|
outcome: 'HOMERUN',
|
|
description: 'Aaron Judge crushes a solo homerun to left field',
|
|
outs_recorded: 0,
|
|
runs_scored: 1,
|
|
runners_advanced: [
|
|
{ from: 0, to: 4, out: false },
|
|
],
|
|
batter_result: 4,
|
|
new_state: {},
|
|
is_hit: true,
|
|
is_out: false,
|
|
is_walk: false,
|
|
is_strikeout: false,
|
|
}
|
|
}
|
|
|
|
const showSingle = () => {
|
|
demoPlayResult.value = {
|
|
play_number: Math.floor(Math.random() * 100) + 1,
|
|
outcome: 'SINGLE_1',
|
|
description: 'Mookie Betts singles to right field',
|
|
outs_recorded: 0,
|
|
runs_scored: 0,
|
|
runners_advanced: [
|
|
{ from: 0, to: 1, out: false },
|
|
],
|
|
batter_result: 1,
|
|
new_state: {},
|
|
is_hit: true,
|
|
is_out: false,
|
|
is_walk: false,
|
|
is_strikeout: false,
|
|
}
|
|
}
|
|
|
|
const showDoublePlay = () => {
|
|
demoPlayResult.value = {
|
|
play_number: Math.floor(Math.random() * 100) + 1,
|
|
outcome: 'DOUBLE_PLAY',
|
|
description: 'Ground ball to shortstop, double play 6-4-3',
|
|
outs_recorded: 2,
|
|
runs_scored: 0,
|
|
runners_advanced: [
|
|
{ from: 1, to: 2, out: true },
|
|
{ from: 0, to: 1, out: true },
|
|
],
|
|
batter_result: null,
|
|
new_state: {},
|
|
is_hit: false,
|
|
is_out: true,
|
|
is_walk: false,
|
|
is_strikeout: false,
|
|
}
|
|
}
|
|
|
|
const showTripleWithRunners = () => {
|
|
demoPlayResult.value = {
|
|
play_number: Math.floor(Math.random() * 100) + 1,
|
|
outcome: 'TRIPLE',
|
|
description: 'Ronald Acuna Jr. triples to the gap, clearing the bases',
|
|
outs_recorded: 0,
|
|
runs_scored: 2,
|
|
runners_advanced: [
|
|
{ from: 2, to: 4, out: false },
|
|
{ from: 1, to: 4, out: false },
|
|
{ from: 0, to: 3, out: false },
|
|
],
|
|
batter_result: 3,
|
|
new_state: {},
|
|
is_hit: true,
|
|
is_out: false,
|
|
is_walk: false,
|
|
is_strikeout: false,
|
|
}
|
|
}
|
|
|
|
const clearResult = () => {
|
|
demoPlayResult.value = null
|
|
}
|
|
|
|
const handleResultDismiss = () => {
|
|
demoPlayResult.value = null
|
|
}
|
|
|
|
// GameplayPanel demo state
|
|
const panelIsMyTurn = ref(true)
|
|
const panelCanRoll = ref(true)
|
|
const panelCanSubmit = ref(false)
|
|
const panelRollData = ref<RollData | null>(null)
|
|
const panelPlayResult = ref<PlayResult | null>(null)
|
|
const panelEvents = ref<Array<{ time: string; type: string; data?: any }>>([])
|
|
|
|
const addPanelEvent = (type: string, data?: any) => {
|
|
panelEvents.value.unshift({
|
|
time: new Date().toLocaleTimeString(),
|
|
type,
|
|
data,
|
|
})
|
|
if (panelEvents.value.length > 10) {
|
|
panelEvents.value.pop()
|
|
}
|
|
}
|
|
|
|
const handlePanelRollDice = () => {
|
|
addPanelEvent('rollDice')
|
|
// Simulate roll result
|
|
const roll = createRandomRoll()
|
|
roll.d6_two_total = roll.d6_two_a + roll.d6_two_b
|
|
panelRollData.value = roll
|
|
panelCanRoll.value = false
|
|
panelCanSubmit.value = true
|
|
}
|
|
|
|
const handlePanelSubmitOutcome = (payload: { outcome: PlayOutcome; hitLocation?: string }) => {
|
|
addPanelEvent('submitOutcome', payload)
|
|
// Simulate processing and result
|
|
panelRollData.value = null
|
|
panelCanSubmit.value = false
|
|
setTimeout(() => {
|
|
panelPlayResult.value = {
|
|
play_number: Math.floor(Math.random() * 100) + 1,
|
|
outcome: payload.outcome,
|
|
description: `Play resolved: ${payload.outcome}${payload.hitLocation ? ` to ${payload.hitLocation}` : ''}`,
|
|
outs_recorded: payload.outcome.includes('OUT') ? 1 : 0,
|
|
runs_scored: payload.outcome === 'HOMERUN' ? 1 : 0,
|
|
runners_advanced: [],
|
|
batter_result: payload.outcome === 'HOMERUN' ? 4 : null,
|
|
new_state: {},
|
|
is_hit: payload.outcome.includes('SINGLE') || payload.outcome.includes('DOUBLE') || payload.outcome.includes('TRIPLE') || payload.outcome === 'HOMERUN',
|
|
is_out: payload.outcome.includes('OUT'),
|
|
is_walk: payload.outcome === 'WALK',
|
|
is_strikeout: payload.outcome === 'STRIKEOUT',
|
|
}
|
|
}, 1000)
|
|
}
|
|
|
|
const handlePanelDismissResult = () => {
|
|
addPanelEvent('dismissResult')
|
|
panelPlayResult.value = null
|
|
panelCanRoll.value = true
|
|
}
|
|
|
|
const simulatePanelRoll = () => {
|
|
if (!panelRollData.value && panelCanRoll.value) {
|
|
handlePanelRollDice()
|
|
}
|
|
}
|
|
|
|
const simulatePanelResult = () => {
|
|
panelPlayResult.value = {
|
|
play_number: Math.floor(Math.random() * 100) + 1,
|
|
outcome: 'SINGLE_1',
|
|
description: 'Simulated single to center field',
|
|
outs_recorded: 0,
|
|
runs_scored: 0,
|
|
runners_advanced: [{ from: 0, to: 1, out: false }],
|
|
batter_result: 1,
|
|
new_state: {},
|
|
is_hit: true,
|
|
is_out: false,
|
|
is_walk: false,
|
|
is_strikeout: false,
|
|
}
|
|
panelRollData.value = null
|
|
panelCanRoll.value = false
|
|
panelCanSubmit.value = false
|
|
}
|
|
|
|
const resetPanelState = () => {
|
|
panelIsMyTurn.value = true
|
|
panelCanRoll.value = true
|
|
panelCanSubmit.value = false
|
|
panelRollData.value = null
|
|
panelPlayResult.value = null
|
|
panelEvents.value = []
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.demo-page {
|
|
@apply min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 pb-12;
|
|
}
|
|
|
|
/* Header */
|
|
.demo-header {
|
|
@apply bg-gradient-to-r from-blue-600 to-blue-700 text-white px-6 py-12 shadow-lg;
|
|
}
|
|
|
|
.demo-title {
|
|
@apply text-4xl font-bold mb-2;
|
|
}
|
|
|
|
.demo-subtitle {
|
|
@apply text-xl text-blue-100;
|
|
}
|
|
|
|
/* Tabs */
|
|
.demo-tabs {
|
|
@apply flex gap-2 px-6 pt-6 overflow-x-auto;
|
|
}
|
|
|
|
.tab-button {
|
|
@apply px-6 py-3 rounded-t-lg font-semibold transition-all duration-200;
|
|
@apply whitespace-nowrap;
|
|
}
|
|
|
|
.tab-active {
|
|
@apply bg-white text-blue-600 shadow-md;
|
|
}
|
|
|
|
.tab-inactive {
|
|
@apply bg-blue-100 text-blue-700 hover:bg-blue-200;
|
|
}
|
|
|
|
/* Content */
|
|
.demo-content {
|
|
@apply px-6;
|
|
}
|
|
|
|
.tab-panel {
|
|
@apply bg-white rounded-b-xl rounded-tr-xl shadow-xl p-8;
|
|
}
|
|
|
|
.section {
|
|
@apply space-y-6;
|
|
}
|
|
|
|
.section-title {
|
|
@apply text-2xl font-bold text-gray-900;
|
|
}
|
|
|
|
.section-description {
|
|
@apply text-gray-600 leading-relaxed;
|
|
}
|
|
|
|
/* Controls */
|
|
.demo-controls {
|
|
@apply flex flex-wrap gap-3 p-4 bg-gray-50 rounded-lg border border-gray-200;
|
|
}
|
|
|
|
.control-button {
|
|
@apply px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg;
|
|
@apply font-medium transition-colors duration-150 shadow-sm;
|
|
}
|
|
|
|
.control-group {
|
|
@apply flex flex-wrap gap-4;
|
|
}
|
|
|
|
.control-label {
|
|
@apply flex items-center gap-2 text-sm font-medium text-gray-700;
|
|
@apply cursor-pointer;
|
|
}
|
|
|
|
.control-label input[type="checkbox"] {
|
|
@apply w-4 h-4 text-blue-600 rounded focus:ring-blue-500;
|
|
}
|
|
|
|
/* Component Showcase */
|
|
.component-showcase {
|
|
@apply p-6 bg-gradient-to-br from-gray-50 to-gray-100 rounded-xl border border-gray-200;
|
|
@apply min-h-[300px] flex items-center justify-center;
|
|
}
|
|
|
|
/* Result Box */
|
|
.result-box {
|
|
@apply mt-6 p-4 bg-green-50 border border-green-200 rounded-lg;
|
|
}
|
|
|
|
.result-title {
|
|
@apply text-lg font-semibold text-green-900 mb-2;
|
|
}
|
|
|
|
.result-code {
|
|
@apply bg-green-900 text-green-100 p-4 rounded overflow-x-auto text-sm;
|
|
}
|
|
|
|
/* Events Log */
|
|
.events-log {
|
|
@apply mt-6 p-4 bg-gray-800 text-gray-100 rounded-lg;
|
|
}
|
|
|
|
.events-title {
|
|
@apply text-lg font-semibold mb-3;
|
|
}
|
|
|
|
.events-list {
|
|
@apply space-y-3 max-h-96 overflow-y-auto;
|
|
}
|
|
|
|
.event-item {
|
|
@apply p-3 bg-gray-700 rounded space-y-2;
|
|
}
|
|
|
|
.event-time {
|
|
@apply text-xs text-gray-400;
|
|
}
|
|
|
|
.event-type {
|
|
@apply ml-3 font-semibold text-blue-400;
|
|
}
|
|
|
|
.event-data {
|
|
@apply bg-gray-900 p-2 rounded text-xs overflow-x-auto;
|
|
}
|
|
|
|
/* Mobile optimizations */
|
|
@media (max-width: 640px) {
|
|
.demo-header {
|
|
@apply px-4 py-8;
|
|
}
|
|
|
|
.demo-title {
|
|
@apply text-2xl;
|
|
}
|
|
|
|
.demo-subtitle {
|
|
@apply text-base;
|
|
}
|
|
|
|
.demo-tabs {
|
|
@apply px-4 pt-4;
|
|
}
|
|
|
|
.tab-button {
|
|
@apply px-4 py-2 text-sm;
|
|
}
|
|
|
|
.demo-content {
|
|
@apply px-4;
|
|
}
|
|
|
|
.tab-panel {
|
|
@apply p-4;
|
|
}
|
|
|
|
.section-title {
|
|
@apply text-xl;
|
|
}
|
|
|
|
.demo-controls {
|
|
@apply p-3;
|
|
}
|
|
|
|
.control-button {
|
|
@apply px-3 py-2 text-sm;
|
|
}
|
|
|
|
.component-showcase {
|
|
@apply p-4;
|
|
}
|
|
}
|
|
|
|
/* Dark mode */
|
|
@media (prefers-color-scheme: dark) {
|
|
.demo-page {
|
|
@apply from-gray-900 to-gray-800;
|
|
}
|
|
|
|
.tab-panel {
|
|
@apply bg-gray-800;
|
|
}
|
|
|
|
.section-title {
|
|
@apply text-gray-100;
|
|
}
|
|
|
|
.section-description {
|
|
@apply text-gray-400;
|
|
}
|
|
|
|
.demo-controls {
|
|
@apply bg-gray-700 border-gray-600;
|
|
}
|
|
|
|
.control-label {
|
|
@apply text-gray-300;
|
|
}
|
|
|
|
.component-showcase {
|
|
@apply from-gray-700 to-gray-800 border-gray-600;
|
|
}
|
|
|
|
.result-box {
|
|
@apply bg-green-900 bg-opacity-20 border-green-700;
|
|
}
|
|
|
|
.result-title {
|
|
@apply text-green-400;
|
|
}
|
|
}
|
|
</style>
|