diff --git a/src/client.ts b/src/client.ts deleted file mode 100644 index 2d3b21e6ffa9d94142bbbd57f3ab1c19e23799eb..0000000000000000000000000000000000000000 --- a/src/client.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { HoudiniClient } from '$houdini'; - -export default new HoudiniClient({ - url: 'https://api.iiens.net/graphql/v0' - - // uncomment this to configure the network call (for things like authentication) - // for more information, please visit here: https://www.houdinigraphql.com/guides/authentication - // fetchParams({ session }) { - // return { - // headers: { - // Authentication: `Bearer ${session.token}`, - // } - // } - // } -}); diff --git a/src/lib/game.ts b/src/lib/game.ts index f93d0bb0943c56b2a136a6ef180eacbd618bd420..b403af40902f152a26d431d234bbdb1f1ae7a603 100644 --- a/src/lib/game.ts +++ b/src/lib/game.ts @@ -2,35 +2,53 @@ import type { Cookies } from '@sveltejs/kit'; import { secureCookie } from './cookie'; import { z } from 'zod'; +export enum GameStage { + PLAYING, + SOLUTION, + NEXT, + GAME_OVER +} + export const gameStateSchema = z.object({ history: z.array(z.string()), step: z.number(), - points: z.number(), + score: z.number(), options: z.array(z.string()), - state: z.enum(['playing', 'solution', 'next']), + stage: z.nativeEnum(GameStage), solution: z.string() }); export type GameState = z.infer<typeof gameStateSchema>; const defaultGameState: GameState = { - state: 'next', + stage: GameStage.NEXT, history: [], step: 0, - points: 0, + score: 0, options: [], solution: '' }; export function loadGameState(cookies: Cookies) { - const cookie = secureCookie(cookies, gameStateSchema, 'qui-est-ce'); + const COOKIE_NAME = 'qui-est-ce'; + const cookie = secureCookie(cookies, gameStateSchema, COOKIE_NAME); const gameState = cookie.read() ?? defaultGameState; + function saveGame() { + cookie.write(gameState); + } + + function resetGame() { + if (gameState.stage === GameStage.GAME_OVER) { + Object.assign(gameState, defaultGameState); + saveGame(); + } + } + return { gameState, - saveGame() { - cookie.write(gameState); - } + saveGame, + resetGame }; } diff --git a/src/lib/graphql/queries/promo.gql b/src/lib/graphql/queries/promo.gql index c057596b982a774222ca1db5d6fa4035d7a589dc..a24a18c4c193e2250ed8db47dde4220f59858801 100644 --- a/src/lib/graphql/queries/promo.gql +++ b/src/lib/graphql/queries/promo.gql @@ -10,7 +10,6 @@ query GetPromotion($first: Int!, $after: String, $promotion: Int!) { } nodes { id - uuid } } } diff --git a/src/lib/graphql/queries/user_details.gql b/src/lib/graphql/queries/user_details.gql index ae256bd0a0c4bfcb92a054b0ded57d1c04f47c5f..78ab2e4838b30025aaed41c6a3dc0fbd19e293ea 100644 --- a/src/lib/graphql/queries/user_details.gql +++ b/src/lib/graphql/queries/user_details.gql @@ -1,7 +1,6 @@ -query UserDetails($first: Int = 4, $uuidList: [String!]!) { - page: users(first: $first, filter: { id: { like: $uuidList } }) { +query UserDetails($first: Int = 4, $idList: [String!]!) { + page: users(first: $first, filter: { id: { like: $idList } }) { nodes { - uuid id nickname photo diff --git a/src/lib/graphql/query.ts b/src/lib/graphql/query.ts index 2179f16c34b5086566f594a2c14868b7d4ca68da..7619ffb411e3d7eed205e946c1a333f07396aa57 100644 --- a/src/lib/graphql/query.ts +++ b/src/lib/graphql/query.ts @@ -36,6 +36,10 @@ export async function* pageIterator< event, variables: { first: pageSize, after: cursor, ...variables } as Input }); + + if (result.errors) { + throw result.errors; + } yield* (result.data?.page.nodes as NodeType<Data>[]) ?? []; cursor = result.data?.page.pageInfo.endCursor ?? null; hasNextPage = !!result.data?.page.pageInfo.hasNextPage; diff --git a/src/lib/graphql/schema.gql b/src/lib/graphql/schema.gql index b5ee8c41567eb2ac6d26b1aaf8f6715af5288094..2bfded716899b2699ed44ca056eab67a07628e29 100644 --- a/src/lib/graphql/schema.gql +++ b/src/lib/graphql/schema.gql @@ -1218,7 +1218,7 @@ type User { Par exemple, `https://api.iiens.net/rest/v0/photo/acier2020` """ - photo: Url! + photo: Url """ Hash de la photo de trombinoscope de l'utilisateur @@ -1226,7 +1226,7 @@ type User { photoThumbnailHash: Base64 """ - URL de la photo de profil de l'utilisateur + URL de l'image de profil de l'utilisateur Cette URL fait référence à un fichier image (par exemple, un fichier image PNG, JPEG ou GIF), plutôt qu'à une page Web contenant une image. diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..51fcb1ce4820f4ccfd5f342d00463b53ba000fe2 --- /dev/null +++ b/src/routes/+page.server.ts @@ -0,0 +1,5 @@ +import { redirect } from '@sveltejs/kit'; + +export function load() { + redirect(303, '/quiz'); +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/src/routes/wip/+page.server.ts b/src/routes/quiz/+page.server.ts similarity index 72% rename from src/routes/wip/+page.server.ts rename to src/routes/quiz/+page.server.ts index 7bfea6d7d486f74a09d2f09524856a4eef2ce160..f96f04cc12e47c755a7bd1ee2fa984ae65dac284 100644 --- a/src/routes/wip/+page.server.ts +++ b/src/routes/quiz/+page.server.ts @@ -1,11 +1,11 @@ import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { schema } from './schema'; -import { fail } from '@sveltejs/kit'; +import { fail, redirect } from '@sveltejs/kit'; import { GetPromotionStore, UserDetailsStore } from '$houdini'; import { pageIterator } from '$lib/graphql/query'; import { getRandomItems } from '$lib/utils'; -import { loadGameState } from '$lib/game'; +import { GameStage, loadGameState } from '$lib/game'; type Option = { value: string; @@ -15,26 +15,33 @@ type Option = { export async function load(event) { let { gameState, saveGame } = loadGameState(event.cookies); - if (gameState.state === 'next') { + if (gameState.stage === GameStage.GAME_OVER) redirect(303, '/quiz/game-over'); + + if (gameState.stage === GameStage.NEXT) { const pagination = pageIterator(event, GetPromotionStore, { promotion: 2023 }); const promotion = await Array.fromAsync(pagination); const all = new Set(promotion.map((p) => p.id)); - const previous = new Set([]); + const previous = new Set(gameState.history); const available = all.difference(previous); gameState.options = getRandomItems(Array.from(available), 4); gameState.solution = getRandomItems([...gameState.options], 1)[0]; - gameState.state = 'playing'; + gameState.stage = GameStage.PLAYING; gameState.step++; } const details = await new UserDetailsStore().fetch({ event, - variables: { uuidList: gameState.options } + variables: { idList: gameState.options } }); - const users = details.data?.page.nodes ?? []; + + if (details.errors) { + throw details.errors; + } + + const users = details.data!.page.nodes ?? []; const options: Option[] = users.map((node) => ({ label: node.nickname!, value: node.id })) ?? []; @@ -46,7 +53,7 @@ export async function load(event) { const form = await superValidate(zod(schema)); - return { form, options, score: gameState.points, step: gameState.step, photo }; + return { form, options, score: gameState.score, step: gameState.step, photo }; } export const actions = { @@ -61,7 +68,7 @@ export const actions = { let { gameState, saveGame } = loadGameState(event.cookies); if (form.data.choice === gameState.solution) { - gameState.points += 10; + gameState.score += 10; } gameState.history.push(gameState.solution); saveGame(); @@ -77,7 +84,11 @@ export const actions = { let { gameState, saveGame } = loadGameState(event.cookies); - gameState.state = 'next'; + if (gameState.step >= 10) { + gameState.stage = GameStage.GAME_OVER; + } else { + gameState.stage = GameStage.NEXT; + } saveGame(); return { form }; diff --git a/src/routes/wip/+page.svelte b/src/routes/quiz/+page.svelte similarity index 97% rename from src/routes/wip/+page.svelte rename to src/routes/quiz/+page.svelte index edd92b7c9084b65d3af12c3cd8b4538bbfb61f10..9a3d34bd65845fffef73355ebf91031e8d3e4a9b 100644 --- a/src/routes/wip/+page.svelte +++ b/src/routes/quiz/+page.svelte @@ -33,7 +33,7 @@ <div class="relative z-[2] flex h-[250px] w-[250px] items-center justify-center overflow-hidden rounded-2xl border-6 border-[#333] border-[solid] bg-white before:absolute before:z-[-1] before:h-[100px] before:w-[100px] before:rounded-[50%] before:bg-[#c5d5ee] before:opacity-100 before:transition-[0.65s] before:duration-[ease-in-out] before:content-[''] after:absolute after:z-[-1] after:h-[100px] after:w-[100px] after:scale-0 after:rounded-[50%] after:border-[#c5d5ee] after:border-[solid] after:transition-[0.4s] after:duration-[ease-in-out] after:content-['']" > - <img src={data.photo} alt="Pas de triche" class="w-auto" /> + <img src={data.photo} alt="Chargement..." class="w-auto" /> </div> <Fieldset form={form2} name="choice"> <transition-group @@ -88,7 +88,7 @@ </div> </form> -<SuperDebug data={formData} /> +<!-- <SuperDebug data={formData} /> --> <style> :global(body) { diff --git a/src/routes/quiz/game-over/+page.server.ts b/src/routes/quiz/game-over/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..e4f80ae32dbfca88766f26cb85e0c18741f48472 --- /dev/null +++ b/src/routes/quiz/game-over/+page.server.ts @@ -0,0 +1,18 @@ +import { GameStage, loadGameState } from '$lib/game'; +import { redirect } from '@sveltejs/kit'; + +export async function load(event) { + let { gameState } = loadGameState(event.cookies); + + if (gameState.stage !== GameStage.GAME_OVER) redirect(303, '.'); + + return { score: gameState.score }; +} + +export const actions = { + async default(event) { + let { resetGame } = loadGameState(event.cookies); + + resetGame(); + } +}; diff --git a/src/routes/quiz/game-over/+page.svelte b/src/routes/quiz/game-over/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..528fb154a94713758ec7b482f678e21cb4c86a39 --- /dev/null +++ b/src/routes/quiz/game-over/+page.svelte @@ -0,0 +1,42 @@ +<script lang="ts"> + export let data; +</script> + +<div class="relative mx-auto my-[50px] w-full max-w-[400px]"> + <section class="text-center"> + <h2>Score final</h2> + <span + class="relative mb-4 block before:absolute before:left-2/4 before:top-2/4 before:z-[-1] before:h-[100px] before:w-[100px] before:-translate-x-2/4 before:-translate-y-2/4 before:animate-[grow_2s_infinite_ease-in-out] before:rounded-[50%] before:border-6 before:border-solid before:border-[#f32c22] before:opacity-30 before:content-['']" + > + <span class="translate-y-[-30px] text-9xl text-[#fa9f9b]">{data.score}</span> + pts + </span> + <form method="post"> + <button + type="submit" + class="cursor-pointer rounded-2xl border-transparent bg-[#f32c22] px-[1.5em] py-[0.5em] text-2xl text-[#333] transition-[0.35s] hover:bg-[#333] hover:text-[#f65a52] focus:border focus:border-dotted focus:border-[#f87f79] focus:[outline:none]" + >Rejouer</button + > + </form> + </section> +</div> + +<style> + :global(body) { + background-color: #f65a52; + font-family: 'Londrina Solid', monospace; + font-size: 16px; + line-height: 1.875em; + color: #333; + } + + @keyframes -global-grow { + 0%, + 100% { + transform: translate(-50%, -50%) scale(1.2); + } + 50% { + transform: translate(-50%, -50%) scale(0.6); + } + } +</style> diff --git a/src/routes/wip/schema.ts b/src/routes/quiz/schema.ts similarity index 100% rename from src/routes/wip/schema.ts rename to src/routes/quiz/schema.ts diff --git a/src/routes/who/+page.svelte b/src/routes/who/+page.svelte deleted file mode 100644 index 155c82935faf5efb3bcd11832a0885fd224cad64..0000000000000000000000000000000000000000 --- a/src/routes/who/+page.svelte +++ /dev/null @@ -1,236 +0,0 @@ -<script lang="ts"> - import './style.css'; - import { onMount } from 'svelte'; - import Trainer from './Trainer.svelte'; - import Final from './Final.svelte'; - - const pkmnTotal = 802; - const url = `https://pokeapi.co/api/v2/pokemon/?limit=${pkmnTotal}`; - const optionAmount = 4; - let pokemonData: Answer[] = []; - const prettyNames = { - 'nidoran-f': 'nidoran♀', - 'nidoran-m': 'nidoran♂', - 'mr-mime': 'mr. mime', - 'deoxys-normal': 'deoxys', - 'wormadam-plant': 'wormadam', - 'mime-jr': 'mime jr.', - 'giratina-altered': 'giratina', - 'shaymin-land': 'shaymin', - 'basculin-red-striped': 'basculin', - 'darmanitan-standard': 'darmanitan', - 'tornadus-incarnate': 'tornadus', - 'thundurus-incarnate': 'thundurus', - 'landorus-incarnate': 'landorus', - 'keldeo-ordinary': 'keldeo', - 'meloetta-aria': 'meloetta', - 'meowstic-male': 'meowstic', - 'aegislash-shield': 'aegislash', - 'pumpkaboo-average': 'pumpkaboo', - 'gourgeist-average': 'gourgeist', - 'oricorio-baile': 'oricorio', - 'lycanroc-midday': 'lycanroc', - 'wishiwashi-solo': 'wishiwashi', - 'type-null': 'type: null', - 'minior-red-meteor': 'minior', - 'mimikyu-disguised': 'mimikyu', - 'tapu-koko': 'tapu koko', - 'tapu-lele': 'tapu lele', - 'tapu-bulu': 'tapu bulu', - 'tapu-fini': 'tapu fini' - }; - - type Answer = { - url: string; - name: string; - index: number; - }; - - let pokemon: Answer[] = []; - let pkmnAmount = 0; - let score = 0; - let question = 0; - let questionAmount = 10; - let answer: Answer | null = null; - let selected: Answer | null = null; - let options: Answer[] = []; - let isPlaying = false; - let isDone = false; - let isChecked = false; - - let image: string; - - $: classic = pkmnAmount <= 151; - $: if (answer) { - image = getImage(classic, answer); - } - - function getImage(classic: boolean, answer: Answer) { - let url = 'https://raw.githubusercontent.com/tiffachoo/pokesprites/master/pokemon/'; - let imageUrl = `${url}${classic ? 'redblue' : 'sunmoon'}/`; - let number = answer.url.match(/\/(\d+)/)![1]; - return `${imageUrl}${number}.png`; - } - - onMount(() => { - let pokeList = localStorage.getItem('pokeList'); - - if (pokeList) { - pokemonData = JSON.parse(pokeList); - } else { - getData().then((res) => { - pokemonData = res.results; - localStorage.setItem('pokeList', JSON.stringify(res.results)); - }); - } - }); - - function prettifyName(name: string) { - return prettyNames[name as keyof typeof prettyNames] || name; - } - - function getData() { - return fetch(url) - .then((res) => res.json()) - .catch((err) => console.log('errrr')); - } - function startGame(val: number) { - question = 0; - score = 0; - isPlaying = true; - pokemon = [...pokemonData]; - - pkmnAmount = val || pkmnTotal; - - getNextQuestion(); - } - function getNextQuestion() { - question += 1; - resetAnswer(); - - if (question <= questionAmount) { - let removed: Answer; - for (let i = 1; i <= optionAmount; i++) { - removed = pokemon.splice(getRandomPokemon(i), 1)[0]; - if (i === 1) { - answer = removed; - } else { - options.push(removed); - } - } - - let pos = Math.floor(Math.random() * optionAmount); - options.splice(pos, 0, answer as Answer); - } else { - isPlaying = false; - isDone = true; - resetAnswer(); - } - } - function selectAnswer(ans: Answer) { - if (!isChecked) { - selected = ans; - } - } - function checkAnswer() { - isChecked = true; - - if (selected && answer && selected.name === answer.name) { - score += 10; - } - } - function getRandomPokemon(index: number) { - const diff = (question - 1) * 4 + index; - return Math.floor(Math.random() * (pkmnAmount + 1 - diff)); - } - function resetAnswer() { - options = []; - selected = null; - answer = null; - isChecked = false; - } - function restartGame() { - isDone = false; - } -</script> - -<div id="app"> - <div class:poke-classic={classic} class="container"> - <transition name="animate-section"> - {#if !isPlaying && !isDone} - <Trainer on:start-game={(e) => startGame(e.detail)} /> - {/if} - </transition> - - <transition name="animate-section"> - {#if isPlaying} - <section class="poke-section"> - <h1 class="poke-title">Who's that pokemon?</h1> - <div class="poke-question-wrapper"> - <span class="poke-question"> - <span class="poke-question-number"> - {question} - </span> - <span class="poke-question-amount"> - / {questionAmount} - </span> - </span> - <span class="poke-score"> - {score} - <small>pts</small> - </span> - <div - class="poke-image" - class:poke-image-success={isChecked && - selected && - answer && - selected.name === answer.name} - class:poke-image-error={isChecked && - selected && - answer && - selected.name !== answer.name} - > - <img src={image} alt="No cheating" class="poke-image-img" /> - </div> - <transition-group - tag="div" - name="animate-options" - class:poke-options-answers={isChecked} - class="poke-options" - > - {#each options as pokemon, index} - <button - data-index={index} - class:selected={selected && selected.index === index} - class:success={isChecked && answer && pokemon.name === answer.name} - class:error={isChecked && - selected && - answer && - selected.index === index && - selected.name !== answer.name} - class="poke-options-button" - on:click={() => selectAnswer({ ...pokemon, index })} - >{prettifyName(pokemon.name)}</button - > - {/each} - </transition-group> - <footer class="poke-buttons"> - <button - disabled={isChecked || (selected && Object.keys(selected).length < 1)} - class="button" - on:click={checkAnswer}>Submit</button - > - <button disabled={!isChecked} class="button" on:click={getNextQuestion}>Next</button> - </footer> - </div> - </section> - {/if} - </transition> - - <transition name="animate-section"> - {#if isDone} - <Final {score} on:restart-game={restartGame}></Final> - {/if} - </transition> - </div> -</div> diff --git a/src/routes/who/Final.svelte b/src/routes/who/Final.svelte deleted file mode 100644 index f11c7681a88b99113d75ee513df0d6f2d7b52828..0000000000000000000000000000000000000000 --- a/src/routes/who/Final.svelte +++ /dev/null @@ -1,16 +0,0 @@ -<script lang="ts"> - import { createEventDispatcher } from 'svelte'; - - export let score: number; - - const dispatch = createEventDispatcher<{ 'restart-game': void }>(); -</script> - -<section class="poke-final"> - <h2>Final score</h2> - <span class="poke-final-score"> - <span class="poke-final-score-number">{score}</span> - pts - </span> - <button class="button" on:click={() => dispatch('restart-game')}>Play again</button> -</section> diff --git a/src/routes/who/Trainer.svelte b/src/routes/who/Trainer.svelte deleted file mode 100644 index f15186420b5407372d3f74c157515adc8330d69f..0000000000000000000000000000000000000000 --- a/src/routes/who/Trainer.svelte +++ /dev/null @@ -1,52 +0,0 @@ -<script lang="ts"> - import { createEventDispatcher } from 'svelte'; - - const dispatch = createEventDispatcher<{ - 'start-game': number; - }>(); - - let trainerHovered: string | null = null; - - function trainerHover(val: string | MouseEvent | FocusEvent) { - trainerHovered = val as string; - } -</script> - -<section class="poke-section"> - <h2>What type of trainer are you?</h2> - <div class="poke-intro-trainer"> - <div class="poke-ball"></div> - <img - class:active={trainerHovered === 'classic'} - class="poke-trainer-img poke-trainer-img-classic" - src="https://raw.githubusercontent.com/tiffachoo/pokesprites/master/trainers/red-rb.png" - alt="Trainer red" - /> - <img - class:active={trainerHovered === 'master'} - class="poke-trainer-img poke-trainer-img-master" - src="https://raw.githubusercontent.com/tiffachoo/pokesprites/master/trainers/red-sm.png" - alt="Trainer red again" - /> - </div> - <button - class="button spacer" - on:click={() => dispatch('start-game', 151)} - on:mouseover={() => trainerHover('classic')} - on:focus={() => trainerHover('classic')} - on:mouseout={trainerHover} - on:blur={trainerHover} - > - Classic - </button> - <button - class="button" - on:click={() => dispatch('start-game', 0)} - on:mouseover={() => trainerHover('master')} - on:focus={() => trainerHover('master')} - on:mouseout={trainerHover} - on:blur={trainerHover} - > - Master - </button> -</section> diff --git a/src/routes/who/style.css b/src/routes/who/style.css deleted file mode 100644 index f5855316cffe00d7dd8affcbf50e1187c570e9f6..0000000000000000000000000000000000000000 --- a/src/routes/who/style.css +++ /dev/null @@ -1,458 +0,0 @@ -/* @import 'https://fonts.googleapis.com/css?family=VT323'; */ -@import 'https://fonts.googleapis.com/css?family=Londrina+Solid|Nunito:400,300'; -* { - box-sizing: border-box; -} - -body { - background-color: #f65a52; - font-family: 'Londrina Solid', monospace; - font-size: 16px; - line-height: 1.875em; - color: #333; -} - -.container { - width: 100%; - max-width: 400px; - position: relative; - margin: 50px auto; -} - -h2 { - font-size: 1.25rem; - white-space: nowrap; -} - -.spacer { - margin-bottom: 0.5rem; -} - -.button { - padding: 0.5em 1.5em; - border-radius: 1rem; - border: solid 1px transparent; - font-family: 'Londrina Solid', monospace; - font-size: 1.5rem; - background-color: #f32c22; - color: #333; - cursor: pointer; - transition: 0.35s; -} -.button:focus { - outline: none; - border: 1px dotted #f87f79; -} -.button:not([disabled]):hover { - background-color: #333; - color: #f65a52; -} - -.poke-section { - display: flex; - flex-direction: column; - align-items: center; - position: relative; - max-width: 500px; - margin: auto; -} - -.poke-intro-trainer { - position: relative; - margin-bottom: 1rem; - height: 200px; - width: 200px; -} -.poke-intro-trainer .poke-trainer-img { - position: absolute; - left: 50%; - bottom: 0; - height: 200px; - opacity: 0; - transition: 0.4s cubic-bezier(0.22, 0.75, 0.53, 0.99); -} -@media (max-width: 479px) { - .poke-intro-trainer .poke-trainer-img { - display: none; - } -} -.poke-intro-trainer .poke-trainer-img.active { - transform: translateX(-50%); - opacity: 1; -} -.poke-intro-trainer .poke-trainer-img-classic { - bottom: 5px; - height: 180px; - -ms-interpolation-mode: nearest-neighbor; - image-rendering: -moz-crisp-edges; - image-rendering: pixelated; - transform: translateX(-80%); -} -.poke-intro-trainer .poke-trainer-img-master { - transform: translateX(-20%); -} - -.poke-ball { - position: absolute; - top: 50%; - left: 50%; - height: 150px; - width: 150px; - border-radius: 50%; - background-color: #f32c22; - transform: translate(-50%, -50%); - overflow: hidden; -} -.poke-ball::before, -.poke-ball::after { - content: ''; - position: absolute; -} -.poke-ball::before { - z-index: 2; - top: 50%; - left: 50%; - height: 40px; - width: 40px; - border-radius: 50%; - border: solid 6px #f65a52; - background-color: #fa9f9b; - transform: translate(-50%, -50%); -} -.poke-ball::after { - z-index: 1; - top: 50%; - height: 50%; - width: 100%; - border-top: solid 6px #f65a52; - background-color: #fa9f9b; -} - -.poke-title { - position: absolute; - top: -2rem; -} - -.poke-question { - position: absolute; - right: calc(100% + 0.5rem); - display: flex; - flex-direction: column; - align-items: flex-end; -} -.poke-question-wrapper { - position: relative; - width: 250px; -} -.poke-question-number { - font-size: 8rem; - line-height: 0.4; - color: #fa9f9b; -} - -.poke-score { - position: absolute; - top: 6rem; - right: calc(100% + 0.5rem); - padding-top: 1rem; - font-size: 1.25rem; - white-space: nowrap; - color: #333; -} -.poke-score::before { - content: ''; - position: absolute; - top: 0; - right: 0; - width: 40px; - height: 6px; - background-color: #333; -} - -.poke-image { - position: relative; - z-index: 2; - display: flex; - justify-content: center; - align-items: center; - width: 250px; - height: 250px; - border-radius: 1rem; - border: solid 6px #333; - background-color: #fff; - overflow: hidden; -} -.poke-image::before, -.poke-image::after { - content: ''; - position: absolute; - z-index: -1; - border-radius: 50%; -} -.poke-image::before { - width: 100px; - height: 100px; - background-color: #c5d5ee; - opacity: 1; - transition: 0.65s ease-in-out; -} -.poke-image::after { - width: 100px; - height: 100px; - border: solid 12px #c5d5ee; - transform: scale(0); - transition: 0.4s ease-in-out; -} -.poke-image-img { - width: auto; - height: 150px; -} -.poke-image-success::before, -.poke-image-error::before { - transform: scale(4); - opacity: 0.5; -} -.poke-image-success::after, -.poke-image-error::after { - transform: scale(1); -} -.poke-image-success::before { - background-color: #7bd55a; -} -.poke-image-success::after { - border-color: #7bd55a; -} -.poke-image-error::before { - background-color: #ff8b62; -} -.poke-image-error::after { - border-color: #ff8b62; - width: 10px; - border-radius: 1rem; - transform: rotate(45deg); -} - -.poke-options { - position: relative; - display: flex; - flex-direction: column; - align-items: center; - z-index: 3; - top: -30px; - padding: 0 20px; - margin: 0 auto; - width: 170px; - border-radius: 1rem; - background-color: #333; -} -.poke-options:not(.poke-options-answers) .poke-options-button:not(.selected):hover { - background-color: #a9c1e6; - transform: translateY(-3px); -} -.poke-options:not(.poke-options-answers) .poke-options-button:not(.selected):active::before { - transform: translate(-50%, -50%) scale(1); -} -.poke-options.poke-options-answers .poke-options-button { - cursor: default; -} -.poke-options.poke-options-answers .poke-options-button:not(.error):not(.success) { - color: #94acbd; -} -.poke-options-button { - position: relative; - width: 100%; - padding: 0.5em; - min-width: 200px; - max-height: 48px; - border: solid 6px #333; - border-radius: 1rem; - background-color: #c5d5ee; - font-family: 'Londrina Solid', monospace; - font-size: 1.125rem; - transition: 0.45s; - cursor: pointer; - overflow: hidden; -} -.poke-options-button:focus { - outline: none; -} -.poke-options-button::before { - content: ''; - position: absolute; - z-index: -1; - left: 50%; - top: 50%; - height: 200px; - width: 200px; - border-radius: 50%; - background-color: #94acbd; - transform: translate(-50%, -50%) scale(0); - transition: 0.2s ease-in-out; -} -.poke-options-button:not(:last-child) { - margin-bottom: 3px; -} -.poke-options-button.selected { - background-color: #94acbd; -} -.poke-options-button.error { - background-color: #ff8b62; -} -.poke-options-button.success { - background-color: #7bd55a; -} - -.poke-buttons { - text-align: center; -} -@media (min-width: 480px) { - .poke-buttons { - position: absolute; - top: 20px; - left: 100%; - } - .poke-buttons .button { - padding-left: calc(1em + 10px); - border-top-left-radius: 0; - border-bottom-left-radius: 0; - transform: translateX(-10px); - } -} -.poke-buttons .button { - padding: 1em; - width: 110px; - height: 100px; - color: #fff; -} -.poke-buttons .button[disabled] { - color: #fa9f9b; - opacity: 0.7; - cursor: default; -} -.poke-buttons .button:not([disabled]):hover { - transform: translateX(0); -} -.poke-buttons .button:not(:last-child) { - margin-bottom: 6px; -} - -.poke-final { - text-align: center; -} -.poke-final-score { - display: block; - position: relative; - margin-bottom: 1rem; -} -.poke-final-score::before { - content: ''; - position: absolute; - z-index: -1; - top: 50%; - left: 50%; - height: 100px; - width: 100px; - border-radius: 50%; - border: solid 12px #f32c22; - transform: translate(-50%, -50%); - opacity: 0.3; - -webkit-animation: grow 2s infinite ease-in-out; - animation: grow 2s infinite ease-in-out; -} -.poke-final-score-number { - font-size: 8rem; - line-height: 0.4; - color: #fa9f9b; -} - -.poke-classic .poke-image-img { - -ms-interpolation-mode: nearest-neighbor; - image-rendering: -moz-crisp-edges; - image-rendering: pixelated; -} - -.animate-section-enter-active, -.animate-section-leave-active { - transition: 0.4s ease-in-out; -} -.animate-section-enter, -.animate-section-leave-to { - opacity: 0; -} -.animate-section-enter .poke-final-score-number { - transform: translateY(-30px); -} -.animate-section-leave-active { - transform: translateX(-30%); -} -.animate-section-enter-active { - transition-delay: 0.1s; - position: absolute; - top: 0; - left: 50%; - transform: translateX(-50%); -} - -.animate-options-enter-active { - transition: 0.4s ease-in-out; -} -.animate-options-enter-active:nth-child(4) { - transition-delay: 0s; -} -.animate-options-enter-active:nth-child(5) { - transition-delay: 0.2s; -} -.animate-options-enter-active:nth-child(6) { - transition-delay: 0.4s; -} -.animate-options-enter-active:nth-child(7) { - transition-delay: 0.6s; -} -.animate-options-enter-active:nth-child(8) { - transition-delay: 0.8s; -} -.animate-options-enter { - transform: rotateX(-45deg); - transform-origin: top center; - opacity: 0; -} -.animate-options-leave-active { - position: absolute; - z-index: -1; - transition: 0.8s ease-in-out; -} -.animate-options-leave-active[data-index='0'] { - top: 0; -} -.animate-options-leave-active[data-index='1'] { - top: 51px; -} -.animate-options-leave-active[data-index='2'] { - top: 102px; -} -.animate-options-leave-active[data-index='3'] { - top: 153px; -} -.animate-options-leave-to { - opacity: 0; -} - -@-webkit-keyframes grow { - 0%, - 100% { - transform: translate(-50%, -50%) scale(1); - } - 50% { - transform: translate(-50%, -50%) scale(0.6); - } -} - -@keyframes grow { - 0%, - 100% { - transform: translate(-50%, -50%) scale(1); - } - 50% { - transform: translate(-50%, -50%) scale(0.6); - } -}