kripke: add share buttons
This commit is contained in:
@@ -17,6 +17,8 @@ export const buttonVariants = tv({
|
||||
"border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm",
|
||||
foreground:
|
||||
"bg-foreground text-background hover:bg-foreground/80 shadow-sm",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { type Formula, isomorphic, validWorlds } from "@cannorin/kripke";
|
||||
import LuRotateCw from "lucide-svelte/icons/rotate-cw";
|
||||
import LuX from "lucide-svelte/icons/x";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import FrameInput from "./frame-input.svelte";
|
||||
import Game, { type GameStatus, type Move } from "./game.svelte";
|
||||
import Rules from "./rules.svelte";
|
||||
import Share from "./share.svelte";
|
||||
|
||||
import { daily } from "./store";
|
||||
import { getDailyFrame, getTimeUntilNextGame } from "./system";
|
||||
@@ -82,12 +84,20 @@ onMount(() => {
|
||||
<FrameInput class="pb-6" disabled width={250} height={250} frame={frame} />
|
||||
</div>
|
||||
|
||||
<Dialog.Footer class="gap-x-1 gap-y-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => (dialogOpen = false)}>
|
||||
<LuX class="w-4 h-4 mt-[2px]" /> Close
|
||||
</Button>
|
||||
<Dialog.Footer>
|
||||
<div class="flex flex-col md:flex-row gap-2 w-full justify-end">
|
||||
<Share date={$daily.date} moves={$daily.moves} status={status} />
|
||||
<Button
|
||||
href="/kripke/random"
|
||||
data-sveltekit-reload>
|
||||
<LuRotateCw class="w-4 h-4 mt-[2px]" /> Play Random Challenge
|
||||
</Button>
|
||||
<Button
|
||||
variant="foreground"
|
||||
onclick={() => (dialogOpen = false)}>
|
||||
<LuX class="w-4 h-4 mt-[2px]" /> Close
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
@@ -200,10 +200,8 @@ const colors: Record<number, string> = {
|
||||
</li>
|
||||
{/if}
|
||||
{#if status === "win"}
|
||||
<li class="flex flex-col items-center gap-2 rounded bg-primary text-background p-5 animate-fade-in">
|
||||
<li class="flex flex-col items-center gap-2 rounded bg-green-700 text-background p-5 animate-fade-in">
|
||||
<p class="text-xl font-bold">YOU WIN!</p>
|
||||
|
||||
<p class="text-sm">Play <a class="underline font-medium" href="/kripke/random">random challenge</a>?</p>
|
||||
</li>
|
||||
{:else if status === "lose"}
|
||||
{#await getAnswer() then answerId}
|
||||
@@ -216,7 +214,6 @@ const colors: Record<number, string> = {
|
||||
<span class="text-xs text-muted self-start px-2 py-1">id: {answerId}</span>
|
||||
<FrameInput class="pb-6" disabled width={250} height={250} frame={getFrame(answerId)} />
|
||||
</div>
|
||||
<p class="text-sm self-end">Play <a class="underline font-medium" href="/kripke/random">random challenge</a>?</p>
|
||||
</li>
|
||||
{/await}
|
||||
{/if}
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { type Formula, isomorphic, validWorlds } from "@cannorin/kripke";
|
||||
import LuRotateCw from "lucide-svelte/icons/rotate-cw";
|
||||
import LuX from "lucide-svelte/icons/x";
|
||||
|
||||
import FrameInput from "../frame-input.svelte";
|
||||
import Game, { type GameStatus, type Move } from "../game.svelte";
|
||||
import Rules from "../rules.svelte";
|
||||
import Share from "../share.svelte";
|
||||
import { getRandomFrame } from "../system";
|
||||
|
||||
let { data } = $props();
|
||||
@@ -78,12 +80,20 @@ $effect(() => {
|
||||
<FrameInput class="pb-6" disabled width={250} height={250} frame={frame} />
|
||||
</div>
|
||||
|
||||
<Dialog.Footer class="gap-x-1 gap-y-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => (dialogOpen = false)}>
|
||||
<LuX class="w-4 h-4 mt-[2px]" /> Close
|
||||
</Button>
|
||||
<Dialog.Footer>
|
||||
<div class="flex flex-col md:flex-row gap-2 w-full justify-end">
|
||||
<Share seed={seed} moves={moves} status={status} />
|
||||
<Button
|
||||
href="/kripke/random"
|
||||
data-sveltekit-reload>
|
||||
<LuRotateCw class="w-4 h-4 mt-[2px]" /> Play New Game
|
||||
</Button>
|
||||
<Button
|
||||
variant="foreground"
|
||||
onclick={() => (dialogOpen = false)}>
|
||||
<LuX class="w-4 h-4 mt-[2px]" /> Close
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
71
apps/web/src/routes/kripke/share.svelte
Normal file
71
apps/web/src/routes/kripke/share.svelte
Normal file
@@ -0,0 +1,71 @@
|
||||
<script lang="ts">
|
||||
import SiBluesky from "@icons-pack/svelte-simple-icons/icons/SiBluesky";
|
||||
import SiMastodon from "@icons-pack/svelte-simple-icons/icons/SiMastodon";
|
||||
import SiMisskey from "@icons-pack/svelte-simple-icons/icons/SiMisskey";
|
||||
import SiX from "@icons-pack/svelte-simple-icons/icons/SiX";
|
||||
import type { GameStatus, Move } from "./game.svelte";
|
||||
|
||||
let {
|
||||
date,
|
||||
moves,
|
||||
status,
|
||||
seed,
|
||||
}: { moves: Move[]; status: GameStatus } & (
|
||||
| { date: string; seed?: undefined }
|
||||
| { seed: number; date?: undefined }
|
||||
) = $props();
|
||||
|
||||
const numberEmojis = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣"];
|
||||
|
||||
let shareText = $derived.by(() => {
|
||||
const history = moves
|
||||
.map((move) => {
|
||||
switch (move.type) {
|
||||
case "guess": {
|
||||
return move.correct ? "✅" : "❎";
|
||||
}
|
||||
case "check": {
|
||||
return numberEmojis[move.valid];
|
||||
}
|
||||
}
|
||||
})
|
||||
.join("");
|
||||
|
||||
const text = `#KRIPKE_game (${date ?? ""}${seed ? `seed ${seed}` : ""})
|
||||
|
||||
${history} ${status === "win" ? moves.length : "X"}/10
|
||||
|
||||
https://www.cannorin.net/kripke${seed ? `/random?seed=${seed}` : ""}`;
|
||||
|
||||
return encodeURIComponent(text);
|
||||
});
|
||||
</script>
|
||||
|
||||
<nav class="h-9 flex items-center justify-between gap-4 mr-auto">
|
||||
<ul class="flex items-center gap-4">
|
||||
<li>
|
||||
<a href="https://x.com/intent/tweet?text={shareText}" target="_blank" rel="noopener noreferrer">
|
||||
<SiX size={20} />
|
||||
<span class="sr-only"> Share to X (Twitter)</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://mastoshare.net/share?text={shareText}" target="_blank" rel="noopener noreferrer">
|
||||
<SiMastodon size={20} />
|
||||
<span class="sr-only"> Share to Mastodon</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://misskeyshare.link/share.html?text={shareText}" target="_blank" rel="noopener noreferrer">
|
||||
<SiMisskey size={20} />
|
||||
<span class="sr-only"> Share to Misskey</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://bsky.app/intent/compose?text={shareText}" target="_blank" rel="noopener noreferrer">
|
||||
<SiBluesky size={20} />
|
||||
<span class="sr-only"> Share to Bluesky</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
Reference in New Issue
Block a user