This commit is contained in:
2026-03-18 15:33:24 +01:00
parent 5e7a6808ab
commit 867c51062b
39 changed files with 6499 additions and 161 deletions

View File

@@ -0,0 +1,545 @@
<script>
import { onMount } from 'svelte';
// A fake card for display purposes
const exampleCard = {
name: "Harald Bluetooth",
image_link: "https://upload.wikimedia.org/wikipedia/commons/thumb/c/ce/1200_Harald_Bl%C3%A5tand_anagoria.jpg/330px-1200_Harald_Bl%C3%A5tand_anagoria.jpg",
card_rarity: "rare",
card_type: "person",
wikidata_instance: "Q5",
text: "Harald \"Bluetooth\" Gormsson was a king of Denmark and Norway.",
attack: 351,
defense: 222,
cost: 5,
created_at: new Date().toISOString(),
reported: false,
};
const annotations = [
{ number: 1, label: "Name", description: "The name of the Wikipedia article this card was generated from." },
{ number: 2, label: "Type", description: "The category of the subject — Person, Location, Artwork, etc." },
{ number: 3, label: "Rarity badge", description: "Rarity is determined by the article's WikiRank quality score. From lowest to highest: Common, Uncommon, Rare, Super Rare, Epic, and Legendary." },
{ number: 4, label: "Wikipedia link", description: "Opens the Wikipedia article this card was generated from." },
{ number: 5, label: "Cost bubbles", description: "How much energy it costs to play this card. Derived from the card's attack and defense stats." },
{ number: 6, label: "Article text", description: "The opening paragraph of the Wikipedia article." },
{ number: 7, label: "Attack", description: "Determines how much damage this card deals when it attacks. Based on how many Wikipedia language editions the article appears in." },
{ number: 8, label: "Defense", description: "How much damage this card can absorb before being destroyed. Based on the article's monthly page views." },
];
// Annotation positions as percentage of card width/height
const markerPositions = [
{ number: 1, x: 15, y: 3 }, // name — top center
{ number: 2, x: 75, y: 3 }, // type badge — top right
{ number: 3, x: 14, y: 20 }, // rarity badge — top left of image
{ number: 4, x: 85, y: 20 }, // wiki link — top right of image
{ number: 5, x: 15, y: 53 }, // cost bubbles — bottom left of image
{ number: 6, x: 50, y: 73 }, // text — middle
{ number: 7, x: 15, y: 88 }, // attack — bottom left
{ number: 8, x: 85, y: 88 }, // defense — bottom right
];
</script>
<main>
<div class="content">
<h1 class="page-title">How to Play</h1>
<section class="section">
<h2 class="section-title">Understanding Your Cards</h2>
<div class="card-explainer">
<div class="card-annotated">
<div class="card-display">
<!-- Inline card rendering matching Card.svelte visuals -->
<div class="demo-card">
<div class="demo-inner" style="--bg: #f0e0c8; --header: #b87830">
<div class="demo-header">
<span class="demo-name">{exampleCard.name}</span>
<span class="demo-type-badge">Person</span>
</div>
<div class="demo-image-wrap">
<img src={exampleCard.image_link} alt={exampleCard.name} class="demo-image" />
<div class="demo-rarity" style="background: #2a5a9b; color: #fff">R</div>
<a href="https://en.wikipedia.org/wiki/Harald_Bluetooth" target="_blank" rel="noopener" class="demo-wiki">
<svg viewBox="0 0 50 50" width="14" height="14"><circle cx="25" cy="25" r="24" fill="white" stroke="#888" stroke-width="1"/><text x="25" y="33" text-anchor="middle" font-family="serif" font-size="28" font-weight="bold" fill="#000">W</text></svg>
</a>
<div class="demo-cost-bubbles">
{#each { length: exampleCard.cost } as _}
<div class="demo-cost-bubble"></div>
{/each}
</div>
</div>
<div class="demo-divider"></div>
<div class="demo-text">{exampleCard.text}</div>
<div class="demo-footer" style="background: #e8d8b8">
<span class="demo-stat">ATK <strong>{exampleCard.attack}</strong></span>
<span class="demo-date">{new Date(exampleCard.created_at).toLocaleDateString()}</span>
<span class="demo-stat">DEF <strong>{exampleCard.defense}</strong></span>
</div>
</div>
</div>
</div>
<!-- Annotation markers -->
<div class="markers">
{#each markerPositions as pos}
<div class="marker" style="left: {pos.x}%; top: {pos.y}%">
<div class="marker-bubble">{pos.number}</div>
</div>
{/each}
</div>
</div>
<ol class="annotation-list">
{#each annotations as a}
<li>
<span class="annotation-number">{a.number}</span>
<div class="annotation-content">
<span class="annotation-label">{a.label}</span>
<span class="annotation-desc">{a.description}</span>
</div>
</li>
{/each}
</ol>
</div>
</section>
<section class="section">
<h2 class="section-title">Taking a Turn</h2>
<div class="rules-grid">
<div class="rule-card">
<div class="rule-icon"></div>
<h3 class="rule-title">Energy</h3>
<p class="rule-body">You start your first turn with 1 energy (or 2 if you're the second player). Each subsequent turn you gain one more, up to a maximum of 6. Energy does not carry over between turns.</p>
</div>
<div class="rule-card">
<div class="rule-icon"></div>
<h3 class="rule-title">Drawing</h3>
<p class="rule-body">At the start of your turn you draw cards until you have 5 in your hand. You cannot draw more than 5 cards.</p>
</div>
<div class="rule-card">
<div class="rule-icon"></div>
<h3 class="rule-title">Playing Cards</h3>
<p class="rule-body">Select a card from your hand, then click an empty slot on your side of the board. The card must have a cost less or equal to your current energy. You can have up to 5 cards in play at once.</p>
</div>
<div class="rule-card">
<div class="rule-icon">🗡</div>
<h3 class="rule-title">Sacrificing</h3>
<p class="rule-body">Click the dagger icon to enter sacrifice mode, then click one of your cards to remove it from play and recover its energy cost. Use this to afford expensive cards.</p>
</div>
</div>
</section>
<section class="section">
<h2 class="section-title">Combat</h2>
<p class="body-text">When you end your turn, all your cards attack simultaneously. Each card attacks the card directly opposite it:</p>
<div class="rules-grid">
<div class="rule-card">
<div class="rule-icon"></div>
<h3 class="rule-title">Opposed Attack</h3>
<p class="rule-body">If there is an enemy card in the opposing slot, your card deals its ATK as damage to that card's DEF. If DEF reaches zero, the card is destroyed.</p>
</div>
<div class="rule-card">
<div class="rule-icon"></div>
<h3 class="rule-title">Direct Attack</h3>
<p class="rule-body">If the opposing slot is empty, your card attacks the opponent's life total directly, dealing damage equal to its ATK.</p>
</div>
<!-- <div class="rule-card">
<div class="rule-icon">🛡</div>
<h3 class="rule-title">No Overflow</h3>
<p class="rule-body">Excess damage does not carry through. A card that destroys an opposing card does not deal the remaining damage to the opponent's life total.</p>
</div> -->
</div>
</section>
<section class="section">
<h2 class="section-title">Winning & Losing</h2>
<div class="rules-grid">
<div class="rule-card">
<div class="rule-icon">💀</div>
<h3 class="rule-title">Life Total</h3>
<p class="rule-body">Each player starts with 500 life. Reduce your opponent's life to zero to win.</p>
</div>
<div class="rule-card">
<div class="rule-icon">🃏</div>
<h3 class="rule-title">No Cards Left</h3>
<p class="rule-body">If you have cards in play and your opponent has no playable cards remaining in their deck, hand, or board, you win.</p>
</div>
<div class="rule-card">
<div class="rule-icon"></div>
<h3 class="rule-title">Timeout</h3>
<p class="rule-body">Each player has 2 minutes per turn. If your opponent's timer runs out, you win.</p>
</div>
<div class="rule-card">
<div class="rule-icon">🔌</div>
<h3 class="rule-title">Disconnect</h3>
<p class="rule-body">If your opponent disconnects and does not reconnect within 15 seconds, you win.</p>
</div>
</div>
</section>
</div>
</main>
<style>
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=Crimson+Text:ital,wght@0,400;0,600;1,400&display=swap');
main {
height: 100vh;
overflow-y: auto;
background: #0d0a04;
}
.content {
max-width: 1000px;
margin: 0 auto;
padding: 2rem;
}
.page-title {
font-family: 'Cinzel', serif;
font-size: 28px;
font-weight: 700;
color: #f0d080;
margin: 0 0 2rem;
letter-spacing: 0.08em;
}
.section {
margin-bottom: 3rem;
}
.section-title {
font-family: 'Cinzel', serif;
font-size: 14px;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: #f0d080AA;
margin: 0 0 1.25rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid #f0d08055;
}
.body-text ul {
margin-left: 20px;
}
.body-text {
font-family: 'Crimson Text', serif;
font-size: 17px;
color: rgba(240, 180, 80, 0.75);
line-height: 1.7;
margin: 0 0 1rem;
}
/* ── Card explainer ── */
.card-explainer {
display: flex;
gap: 3rem;
align-items: flex-start;
flex-wrap: wrap;
}
.card-annotated {
position: relative;
flex-shrink: 0;
}
.card-display {
position: relative;
}
.markers {
position: absolute;
inset: 0;
pointer-events: none;
}
.marker {
position: absolute;
transform: translate(-50%, -50%);
z-index: 10;
}
.marker-bubble {
width: 22px;
height: 22px;
border-radius: 50%;
background: #c8861a;
border: 2px solid #fff;
color: #fff;
font-family: 'Cinzel', serif;
font-size: 11px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0,0,0,0.6);
}
/* ── Annotation list ── */
.annotation-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.9rem;
flex: 1;
min-width: 260px;
}
.annotation-list li {
display: flex;
gap: 0.75rem;
align-items: flex-start;
}
.annotation-number {
width: 22px;
height: 22px;
border-radius: 50%;
background: #c8861a;
color: #fff;
font-family: 'Cinzel', serif;
font-size: 11px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-top: 2px;
}
.annotation-content {
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.annotation-label {
font-family: 'Cinzel', serif;
font-size: 12px;
font-weight: 700;
color: #f0d080;
letter-spacing: 0.04em;
}
.annotation-desc {
font-family: 'Crimson Text', serif;
font-size: 14px;
color: rgba(240, 180, 80, 0.6);
line-height: 1.5;
}
/* ── Rules grid ── */
.rules-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
}
.rule-card {
background: #1a1008;
border: 1px solid rgba(107, 76, 30, 0.3);
border-radius: 8px;
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.rule-icon {
color: #f0d080AA;
font-size: 20px;
line-height: 1;
}
.rule-title {
font-family: 'Cinzel', serif;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.06em;
color: #f0d080;
margin: 0;
}
.rule-body {
font-family: 'Crimson Text', serif;
font-size: 14px;
color: rgba(240, 180, 80, 0.6);
line-height: 1.55;
margin: 0;
}
/* ── Demo card ── */
.demo-card {
width: 300px;
border-radius: 12px;
padding: 7px;
background: #111;
border: 2px solid #111;
box-shadow: 0 4px 24px rgba(0,0,0,0.5);
font-family: 'Crimson Text', serif;
position: relative;
user-select: none;
}
.demo-inner {
border-radius: 8px;
overflow: hidden;
background: var(--bg);
border: 2px solid #000;
display: flex;
flex-direction: column;
}
.demo-header {
padding: 9px 12px 7px;
background: var(--header);
border-bottom: 2px solid #000;
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 8px;
}
.demo-name {
font-family: 'Cinzel', serif;
font-size: 13px;
font-weight: 700;
color: #fff;
text-shadow: 0 1px 3px rgba(0,0,0,0.6);
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.demo-type-badge {
font-family: 'Cinzel', serif;
font-size: 9px;
color: rgba(255,255,255,0.95);
text-transform: uppercase;
letter-spacing: 0.05em;
background: rgba(0,0,0,0.25);
padding: 1px 5px;
border-radius: 3px;
white-space: nowrap;
}
.demo-image-wrap {
position: relative;
width: 100%;
aspect-ratio: 4/3;
overflow: hidden;
border-bottom: 2px solid #000;
}
.demo-image {
width: 100%;
height: 100%;
object-fit: cover;
object-position: top;
display: block;
}
.demo-rarity {
position: absolute;
top: 7px;
left: 7px;
width: 26px;
height: 26px;
border-radius: 50%;
border: 2.5px solid #000;
font-family: 'Cinzel', serif;
font-size: 9px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
z-index: 3;
}
.demo-wiki {
position: absolute;
top: 7px;
right: 7px;
width: 26px;
height: 26px;
border-radius: 50%;
background: rgba(255,255,255,0.92);
border: 1.5px solid #000;
display: flex;
align-items: center;
justify-content: center;
z-index: 3;
}
.demo-cost-bubbles {
position: absolute;
bottom: 6px;
left: 8px;
display: flex;
gap: 3px;
flex-wrap: wrap;
max-width: calc(100% - 16px);
}
.demo-cost-bubble {
width: 16px;
height: 16px;
border-radius: 50%;
background: #6ea0ec;
border: 2.5px solid #000;
display: flex;
align-items: center;
justify-content: center;
color: #08152c;
font-size: 12px;
font-weight: 700;
font-family: 'Cinzel', serif;
line-height: 1;
}
.demo-divider {
height: 2px;
background: #000;
}
.demo-text {
padding: 10px 12px;
font-size: 13px;
line-height: 1.55;
color: #1a1208;
font-style: italic;
background: #f0e6cc;
border-bottom: 2px solid #000;
height: 110px;
overflow: hidden;
}
.demo-footer {
padding: 7px 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
.demo-stat {
font-family: 'Cinzel', serif;
font-size: 11px;
color: #2a2010;
letter-spacing: 0.03em;
}
.demo-stat strong {
color: #000;
font-size: 15px;
}
.demo-date {
font-size: 10px;
color: rgba(0,0,0,0.5);
font-style: italic;
font-family: 'Crimson Text', serif;
}
</style>