Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions src/components/CardClues.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import type { ScryfallCard } from "../models/scryfall-card";
import { useAppSelector } from "../store";

const props = defineProps<{
card: ScryfallCard;
}>();

const activeClue = ref<"cmc" | "set" | "type" | "oracle" | null>(null);
const storeCard = useAppSelector((state) => state.game.card);

// Dismiss the active clue when the card changes
watch(storeCard, () => {
activeClue.value = null;
});

const clues = [
{
id: "cmc",
label: "Show Mana Value",
},
{
id: "set",
label: "Show Set",
},
{
id: "type",
label: "Show Type",
},
{
id: "oracle",
label: "Show Rules Text",
},
] as const;

const visibleClues = computed(() =>
clues.filter((clue) => clue.id === "type" || clue.id === "oracle" || !props.card.card_faces)
);
</script>

<template>
<div class="clue-buttons">
<button
v-for="clue in visibleClues"
:key="clue.id"
class="clue-button"
@click="activeClue = activeClue === clue.id ? null : clue.id"
:class="{ active: activeClue === clue.id }"
>
{{ clue.label }}
</button>
</div>

<div v-if="activeClue" class="clues">
<div v-if="activeClue === 'cmc' && !card.card_faces" class="clue">
Mana Value: {{ card.cmc }}
</div>
<div v-if="activeClue === 'set' && !card.card_faces" class="clue">Set: {{ card.set_name }}</div>
<div v-if="activeClue === 'type'" class="clue">Type: {{ card.type_line }}</div>
<div v-if="activeClue === 'oracle'" class="clue oracle-text">
{{ card.oracle_text }}
</div>
</div>
</template>

<style scoped lang="scss">
.clue-buttons {
display: flex;
gap: 8px;
margin: 16px 0;
flex-wrap: wrap;
justify-content: center;
}

.clue-button {
padding: 8px 16px;
background: var(--clue-button-background, #4a5568);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1em;
transition: all 0.2s;

&:hover {
background: var(--clue-button-hover-background, #2d3748);
}

&:focus {
outline: 2px solid var(--clue-button-focus-outline, #4299e1);
outline-offset: 2px;
}

&.active {
background: var(--clue-button-active-background, #2d3748);
transform: translateY(1px);
}
}

.clues {
margin: 16px 0;
padding: 16px;
border-radius: 8px;
text-align: center;
width: 100%;
max-width: 400px;
animation: slideDown 0.2s ease-out;
}

.clue {
font-size: 1.1em;
font-weight: 500;
}

.oracle-text {
white-space: pre-line;
text-align: left;
line-height: 1.5;
}
</style>
8 changes: 5 additions & 3 deletions src/components/GameScreen.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<script setup lang="ts">
import { Outcome } from "../models/outcome";
import { fetchNextCard, LoadingStatus, setGuess, useAppDispatch, useAppSelector } from "../store";
import CardArt from "./CardArt.vue";
import GuessInput from "./GuessInput.vue";
import { isGuessOk } from "../utils/guess";
import CardArt from "./CardArt.vue";

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, maybe my linter is not set up correctly (I am using Cursor)

import CardClues from "./CardClues.vue";
import GuessFeedback from "./GuessFeedback.vue";
import GuessInput from "./GuessInput.vue";
import LoadingHammer from "./LoadingHammer.vue";

const dispatch = useAppDispatch();
Expand Down Expand Up @@ -35,7 +36,6 @@ const retry = () => {

const submitGuess = (guess: string) => {
if (!card.value) {
console.debug("Guess was submitted somehow, but no card is loaded.");
return;
}

Expand Down Expand Up @@ -76,6 +76,8 @@ const submitGuess = (guess: string) => {

<p class="score">Score: {{ score }}</p>

<CardClues v-if="card" :card="card" />

<GuessFeedback v-if="prevGuess && prevCard" :guess="prevGuess" :card="prevCard" />
</div>
</template>
Expand Down
1 change: 1 addition & 0 deletions src/components/GuessAutocompleteList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const vScrollIntoView: Directive<HTMLElement, boolean> = {
align-items: stretch;
max-height: calc((var(--count) + 0.6) * (1em + var(--v-padding) * 2));
overflow-y: scroll;
z-index: 10;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixes this issue, admittedly a bit haphazard since there is no z-indexes.ts yet, which we can add in this PR too.
CleanShot 2025-05-20 at 22 46 42


@include mixins.bp-large {
--count: 4;
Expand Down
9 changes: 9 additions & 0 deletions src/models/scryfall-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export type DefaultScryfallCard = {
set: string;
image_uris: ScryfallImageUris;
card_faces?: never;
cmc: number;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is duplicated for now and I notice this exists https://github.com/scryfall/api-types, checking if this can be in another PR or in this one?

set_name: string;
oracle_text: string;
};

export type SplitScryfallCard = {
Expand All @@ -46,6 +49,9 @@ export type SplitScryfallCard = {
set: string;
image_uris: ScryfallImageUris;
card_faces: ScryfallSplitCardFace[];
cmc: number;
set_name: string;
oracle_text: string;
};

export type DfcScryfallCard = {
Expand All @@ -59,6 +65,9 @@ export type DfcScryfallCard = {
card_faces: ScryfallCardFace[];
image_uris?: never;
flavor_name?: never;
cmc: number;
set_name: string;
oracle_text: string;
};

export type ScryfallCard = DefaultScryfallCard | SplitScryfallCard | DfcScryfallCard;