feat: risks and sticky notes

This commit is contained in:
2025-07-08 12:56:52 +02:00
parent 4d8cc643a3
commit 8c1508f3ae
45 changed files with 613 additions and 34 deletions

View File

@ -7,6 +7,7 @@
x?: number; x?: number;
y?: number; y?: number;
zIndex?: number; zIndex?: number;
canvasOffset?: { x: number; y: number };
onMove?: (id: number, x: number, y: number) => void; onMove?: (id: number, x: number, y: number) => void;
onClick?: (id: number) => void; onClick?: (id: number) => void;
onDrop?: (id: number, x: number, y: number) => void; onDrop?: (id: number, x: number, y: number) => void;
@ -20,6 +21,7 @@
x = $bindable(0), x = $bindable(0),
y = $bindable(0), y = $bindable(0),
zIndex = $bindable(0), zIndex = $bindable(0),
canvasOffset = { x: 0, y: 0 },
onMove, onMove,
onClick, onClick,
onDrop onDrop
@ -59,8 +61,8 @@
hasMoved = true; hasMoved = true;
} }
const newX = event.clientX - dragOffset.x; const newX = event.clientX - dragOffset.x - canvasOffset.x;
const newY = event.clientY - dragOffset.y; const newY = event.clientY - dragOffset.y - canvasOffset.y;
x = newX; x = newX;
y = newY; y = newY;

223
src/lib/StickyNote.svelte Normal file
View File

@ -0,0 +1,223 @@
<script lang="ts">
interface Props {
id: number;
text: string;
x: number;
y: number;
zIndex: number;
color: string;
canvasOffset?: { x: number; y: number };
onMove?: (id: number, x: number, y: number) => void;
onUpdate?: (id: number, text: string) => void;
onDelete?: (id: number) => void;
}
let {
id,
text = $bindable(''),
x = $bindable(0),
y = $bindable(0),
zIndex = $bindable(0),
color,
canvasOffset = { x: 0, y: 0 },
onMove,
onUpdate,
onDelete
}: Props = $props();
let isDragging = $state(false);
let dragOffset = $state({ x: 0, y: 0 });
let dragStartPos = $state({ x: 0, y: 0 });
let hasMoved = $state(false);
let noteElement: HTMLDivElement;
let textareaElement: HTMLTextAreaElement;
let isEditing = $state(false);
function handleMouseDown(event: MouseEvent) {
if (event.button !== 0) return;
isDragging = true;
hasMoved = false;
const rect = noteElement.getBoundingClientRect();
dragOffset = {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
dragStartPos = {
x: event.clientX,
y: event.clientY
};
event.preventDefault();
}
function handleMouseMove(event: MouseEvent) {
if (!isDragging) return;
const deltaX = Math.abs(event.clientX - dragStartPos.x);
const deltaY = Math.abs(event.clientY - dragStartPos.y);
if (deltaX > 5 || deltaY > 5) {
hasMoved = true;
}
const newX = event.clientX - dragOffset.x - canvasOffset.x;
const newY = event.clientY - dragOffset.y - canvasOffset.y;
x = newX;
y = newY;
onMove?.(id, newX, newY);
}
function handleMouseUp() {
isDragging = false;
}
function handleClick() {
if (!hasMoved) {
isEditing = true;
setTimeout(() => textareaElement?.focus(), 0);
}
}
function handleTextBlur() {
isEditing = false;
onUpdate?.(id, text);
}
function handleKeyDown(event: KeyboardEvent) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
isEditing = false;
onUpdate?.(id, text);
}
if (event.key === 'Escape') {
isEditing = false;
}
}
function handleDelete(event: MouseEvent) {
event.stopPropagation();
onDelete?.(id);
}
$effect(() => {
if (isDragging) {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}
});
</script>
<div
bind:this={noteElement}
class="sticky-note"
class:dragging={isDragging}
class:editing={isEditing}
style="left: {x}px; top: {y}px; z-index: {zIndex}; background-color: {color};"
onmousedown={handleMouseDown}
onclick={handleClick}
role="button"
tabindex="0"
>
<button class="delete-btn" onclick={handleDelete}>×</button>
{#if isEditing}
<textarea
bind:this={textareaElement}
bind:value={text}
onblur={handleTextBlur}
onkeydown={handleKeyDown}
class="note-textarea"
placeholder="Enter your note..."
></textarea>
{:else}
<div class="note-text">{text || 'Click to edit'}</div>
{/if}
</div>
<style>
.sticky-note {
position: absolute;
width: 200px;
min-height: 120px;
padding: 15px;
border-radius: 8px;
cursor: pointer;
user-select: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
line-height: 1.4;
transition: transform 0.1s ease;
}
.sticky-note:hover {
transform: translateY(-2px);
}
.sticky-note.dragging {
cursor: grabbing;
z-index: 1000 !important;
transform: rotate(2deg);
}
.sticky-note.dragging:hover {
transform: rotate(2deg);
}
.delete-btn {
position: absolute;
top: 5px;
right: 5px;
width: 20px;
height: 20px;
border: none;
background: rgba(0, 0, 0, 0.1);
border-radius: 50%;
cursor: pointer;
font-size: 14px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
color: rgba(0, 0, 0, 0.6);
transition: all 0.2s ease;
}
.delete-btn:hover {
background: rgba(255, 0, 0, 0.2);
color: #ff0000;
}
.note-textarea {
width: 100%;
min-height: 80px;
border: none;
background: transparent;
resize: none;
font-family: inherit;
font-size: inherit;
line-height: inherit;
outline: none;
padding: 0;
}
.note-text {
min-height: 80px;
word-wrap: break-word;
white-space: pre-wrap;
color: rgba(0, 0, 0, 0.8);
}
.note-text:empty::before {
content: 'Click to edit';
color: rgba(0, 0, 0, 0.4);
font-style: italic;
}
</style>

View File

@ -1,5 +1,7 @@
<script lang="ts"> <script lang="ts">
import Card from '$lib/Card.svelte'; import Card from '$lib/Card.svelte';
import StickyNote from '$lib/StickyNote.svelte';
import { onMount } from 'svelte';
interface CardData { interface CardData {
id: number; id: number;
@ -9,25 +11,128 @@
y: number; y: number;
zIndex: number; zIndex: number;
inDeck: boolean; inDeck: boolean;
isRiskCategory?: boolean;
} }
const cardImages = Array.from({ length: 14 }, (_, i) => `front${i + 1}.png`); interface DeckConfig {
const backImage = 'back.png'; name: string;
backImage: string;
cards: string[];
riskCategories?: string[];
}
interface StickyNote {
id: number;
text: string;
x: number;
y: number;
zIndex: number;
color: string;
}
const deckConfigs: DeckConfig[] = [
{
name: 'Default',
backImage: 'back.png',
cards: Array.from({ length: 14 }, (_, i) => `front${i + 1}.png`)
},
{
name: 'Risk Cards',
backImage: 'risks/risiko_back.png',
cards: [
'risks/altsysteme_und_altlasten1.png',
'risks/altsysteme_und_altlasten2.png',
'risks/altsysteme_und_altlasten3.png',
'risks/altsysteme_und_altlasten4.png',
'risks/altsysteme_und_altlasten5.png',
'risks/betrieb_und_deployment1.png',
'risks/betrieb_und_deployment2.png',
'risks/betrieb_und_deployment3.png',
'risks/betrieb_und_deployment4.png',
'risks/betrieb_und_deployment5.png',
'risks/fremdsysteme_und_plattformen1.png',
'risks/fremdsysteme_und_plattformen2.png',
'risks/fremdsysteme_und_plattformen3.png',
'risks/fremdsysteme_und_plattformen4.png',
'risks/fremdsysteme_und_plattformen5.png',
'risks/kompetenz_und_erfahrung1.png',
'risks/kompetenz_und_erfahrung2.png',
'risks/kompetenz_und_erfahrung3.png',
'risks/kompetenz_und_erfahrung4.png',
'risks/kompetenz_und_erfahrung5.png',
'risks/orga_und_prozesse1.png',
'risks/orga_und_prozesse2.png',
'risks/orga_und_prozesse3.png',
'risks/orga_und_prozesse4.png',
'risks/orga_und_prozesse5.png',
'risks/softwareloesung1.png',
'risks/softwareloesung2.png',
'risks/softwareloesung3.png',
'risks/softwareloesung4.png',
'risks/softwareloesung5.png',
'risks/weiche_faktoren1.png',
'risks/weiche_faktoren2.png',
'risks/weiche_faktoren3.png',
'risks/weiche_faktoren4.png',
'risks/weiche_faktoren5.png',
'risks/zielsetzung1.png',
'risks/zielsetzung2.png',
'risks/zielsetzung3.png',
'risks/zielsetzung4.png',
'risks/zielsetzung5.png'
],
riskCategories: [
'altsysteme_und_altlasten',
'betrieb_und_deployment',
'fremdsysteme_und_plattformen',
'kompetenz_und_erfahrung',
'orga_und_prozesse',
'softwareloesung',
'weiche_faktoren',
'zielsetzung'
]
}
];
let selectedDeck = $state(0);
let shuffleMode = $state('normal');
let currentDeckConfig = $derived(deckConfigs[selectedDeck]);
let backImage = $derived(currentDeckConfig.backImage);
let deckPosition = $state({ x: 50, y: 300 }); let deckPosition = $state({ x: 50, y: 300 });
let canvasOffset = $state({ x: 0, y: 0 });
let isDragging = $state(false);
let dragStart = $state({ x: 0, y: 0 });
let dragStartOffset = $state({ x: 0, y: 0 });
let cards = $state<CardData[]>( let cards = $state<CardData[]>([]);
cardImages.map((image, index) => ({ let maxZIndex = $state(0);
let stickyNotes = $state<StickyNote[]>([]);
let nextNoteId = $state(1);
let noteColors = ['#ffeb3b', '#4caf50', '#2196f3', '#ff9800', '#e91e63', '#9c27b0'];
function initializeDeck() {
const config = deckConfigs[selectedDeck];
cards = config.cards.map((image, index) => ({
id: index + 1, id: index + 1,
frontImage: image, frontImage: image,
flipped: true, flipped: true,
x: deckPosition.x + index * 2, x: deckPosition.x + index * 2,
y: deckPosition.y + index * 2, y: deckPosition.y + index * 2,
zIndex: index, zIndex: index,
inDeck: true inDeck: true,
})) isRiskCategory: selectedDeck === 1 && image.includes('1.png')
); }));
let maxZIndex = $state(cards.length); maxZIndex = cards.length;
}
onMount(() => {
initializeDeck();
});
function handleDeckChange() {
initializeDeck();
}
function handleCardMove(id: number, x: number, y: number) { function handleCardMove(id: number, x: number, y: number) {
const card = cards.find(c => c.id === id); const card = cards.find(c => c.id === id);
@ -41,12 +146,16 @@
function handleCardDrop(id: number, x: number, y: number) { function handleCardDrop(id: number, x: number, y: number) {
const card = cards.find(c => c.id === id); const card = cards.find(c => c.id === id);
if (card) { if (card) {
// Check if card is dropped on deck area // Calculate card center position (card is 220x300px)
const cardCenterX = x + 110;
const cardCenterY = y + 150;
// Check if card center is dropped on deck area
if ( if (
x >= deckPosition.x && cardCenterX >= deckPosition.x &&
x <= deckPosition.x + 220 && cardCenterX <= deckPosition.x + 220 &&
y >= deckPosition.y && cardCenterY >= deckPosition.y &&
y <= deckPosition.y + 300 && cardCenterY <= deckPosition.y + 300 &&
!card.inDeck !card.inDeck
) { ) {
// Move card to top of deck // Move card to top of deck
@ -76,6 +185,20 @@
const deckCards = cards.filter(card => card.inDeck); const deckCards = cards.filter(card => card.inDeck);
if (deckCards.length === 0) return; if (deckCards.length === 0) return;
if (selectedDeck === 1) {
if (shuffleMode === 'shuffle-only-risks') {
shuffleOnlyRisks();
} else if (shuffleMode === 'shuffle-per-category') {
shufflePerCategory();
} else {
normalShuffle(deckCards);
}
} else {
normalShuffle(deckCards);
}
}
function normalShuffle(deckCards: CardData[]) {
for (let i = deckCards.length - 1; i > 0; i--) { for (let i = deckCards.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); const j = Math.floor(Math.random() * (i + 1));
[deckCards[i], deckCards[j]] = [deckCards[j], deckCards[i]]; [deckCards[i], deckCards[j]] = [deckCards[j], deckCards[i]];
@ -89,6 +212,70 @@
}); });
} }
function shuffleOnlyRisks() {
const deckCards = cards.filter(card => card.inDeck);
const riskCategories = deckCards.filter(card => card.isRiskCategory);
const riskCards = deckCards.filter(card => !card.isRiskCategory);
for (let i = riskCards.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[riskCards[i], riskCards[j]] = [riskCards[j], riskCards[i]];
}
const shuffled = [...riskCategories, ...riskCards];
shuffled.reverse();
const numCategories = riskCategories.length;
const totalCards = shuffled.length;
shuffled.forEach((card, index) => {
if (index >= totalCards - numCategories) {
const categoryIndex = index - (totalCards - numCategories);
card.x = 50 + 330 + (categoryIndex % 6) * 240;
card.y = 50 + Math.floor(categoryIndex / 6) * 320;
card.zIndex = index;
card.flipped = false;
card.inDeck = false;
} else {
card.x = deckPosition.x + index * 2;
card.y = deckPosition.y + index * 2;
card.zIndex = index;
card.flipped = true;
card.inDeck = true;
}
});
}
function shufflePerCategory() {
const deckCards = cards.filter(card => card.inDeck);
const categories = currentDeckConfig.riskCategories || [];
const shuffledDeck: CardData[] = [];
for (const category of categories) {
const categoryCard = deckCards.find(card => card.frontImage.includes(`${category}1.png`));
const categoryRisks = deckCards.filter(card =>
card.frontImage.includes(category) && !card.frontImage.includes('1.png')
);
for (let i = categoryRisks.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[categoryRisks[i], categoryRisks[j]] = [categoryRisks[j], categoryRisks[i]];
}
if (categoryCard) shuffledDeck.push(categoryCard);
shuffledDeck.push(...categoryRisks);
}
shuffledDeck.reverse();
shuffledDeck.forEach((card, index) => {
card.x = deckPosition.x + index * 2;
card.y = deckPosition.y + index * 2;
card.zIndex = index;
card.flipped = true;
});
}
function collectToDeck() { function collectToDeck() {
cards.forEach((card, index) => { cards.forEach((card, index) => {
card.x = deckPosition.x + index * 2; card.x = deckPosition.x + index * 2;
@ -101,7 +288,7 @@
function dealCards() { function dealCards() {
cards.forEach((card, index) => { cards.forEach((card, index) => {
card.x = 50 + (index % 6) * 240; card.x = 50 + 330 + (index % 6) * 240;
card.y = 50 + Math.floor(index / 6) * 320; card.y = 50 + Math.floor(index / 6) * 320;
card.zIndex = index; card.zIndex = index;
card.flipped = false; card.flipped = false;
@ -114,21 +301,132 @@
card.flipped = !card.flipped; card.flipped = !card.flipped;
}); });
} }
function handleCanvasWheel(event: WheelEvent) {
event.preventDefault();
const scrollSpeed = 1;
canvasOffset.x -= event.deltaX * scrollSpeed;
canvasOffset.y -= event.deltaY * scrollSpeed;
canvasOffset.x = Math.max(-2000, Math.min(2000, canvasOffset.x));
canvasOffset.y = Math.max(-2000, Math.min(2000, canvasOffset.y));
}
function handleCanvasMouseDown(event: MouseEvent) {
if (event.button === 1 || event.button === 2) {
event.preventDefault();
isDragging = true;
dragStart.x = event.clientX;
dragStart.y = event.clientY;
dragStartOffset.x = canvasOffset.x;
dragStartOffset.y = canvasOffset.y;
}
}
function handleCanvasMouseMove(event: MouseEvent) {
if (isDragging) {
event.preventDefault();
const deltaX = event.clientX - dragStart.x;
const deltaY = event.clientY - dragStart.y;
canvasOffset.x = Math.max(-2000, Math.min(2000, dragStartOffset.x + deltaX));
canvasOffset.y = Math.max(-2000, Math.min(2000, dragStartOffset.y + deltaY));
}
}
function handleCanvasMouseUp(event: MouseEvent) {
if (event.button === 1 || event.button === 2) {
isDragging = false;
}
}
function handleCanvasContextMenu(event: MouseEvent) {
event.preventDefault();
}
function addStickyNote() {
const randomColor = noteColors[Math.floor(Math.random() * noteColors.length)];
const newNote: StickyNote = {
id: nextNoteId++,
text: 'New note',
x: 400 - canvasOffset.x,
y: 200 - canvasOffset.y,
zIndex: ++maxZIndex,
color: randomColor
};
stickyNotes.push(newNote);
}
function updateStickyNote(id: number, text: string) {
const note = stickyNotes.find(n => n.id === id);
if (note) {
note.text = text;
}
}
function moveStickyNote(id: number, x: number, y: number) {
const note = stickyNotes.find(n => n.id === id);
if (note) {
note.x = x;
note.y = y;
note.zIndex = ++maxZIndex;
}
}
function deleteStickyNote(id: number) {
const index = stickyNotes.findIndex(n => n.id === id);
if (index >= 0) {
stickyNotes.splice(index, 1);
}
}
</script> </script>
<div class="game-container"> <div class="game-container">
<div class="controls"> <div class="controls">
<div class="control-group">
<label for="deck-select">Deck:</label>
<select id="deck-select" bind:value={selectedDeck} onchange={handleDeckChange}>
{#each deckConfigs as config, index}
<option value={index}>{config.name}</option>
{/each}
</select>
</div>
{#if selectedDeck === 1}
<div class="control-group">
<label for="shuffle-mode">Shuffle:</label>
<select id="shuffle-mode" bind:value={shuffleMode}>
<option value="normal">Normal</option>
<option value="shuffle-only-risks">Only Risks</option>
<option value="shuffle-per-category">Per Category</option>
</select>
</div>
{/if}
<button onclick={collectToDeck}>Collect to Deck</button> <button onclick={collectToDeck}>Collect to Deck</button>
<button onclick={shuffleDeck}>Shuffle Deck</button> <button onclick={shuffleDeck}>Shuffle Deck</button>
<button onclick={dealCards}>Deal Cards</button> <button onclick={dealCards}>Deal Cards</button>
<button onclick={flipAllCards}>Flip All</button> <button onclick={flipAllCards}>Flip All</button>
<button onclick={addStickyNote}>Add Note</button>
</div> </div>
<div class="game-area"> <div
class="game-area"
onwheel={handleCanvasWheel}
onmousedown={handleCanvasMouseDown}
onmousemove={handleCanvasMouseMove}
onmouseup={handleCanvasMouseUp}
oncontextmenu={handleCanvasContextMenu}
>
<div
class="canvas-content"
style="transform: translate({canvasOffset.x}px, {canvasOffset.y}px);"
>
{#each cards as card (card.id)} {#each cards as card (card.id)}
<Card <Card
{...card} {...card}
backImage={backImage} backImage={card.isRiskCategory ? 'risks/risikocat_back.png' : backImage}
canvasOffset={canvasOffset}
bind:flipped={card.flipped} bind:flipped={card.flipped}
bind:x={card.x} bind:x={card.x}
bind:y={card.y} bind:y={card.y}
@ -139,6 +437,20 @@
/> />
{/each} {/each}
{#each stickyNotes as note (note.id)}
<StickyNote
{...note}
canvasOffset={canvasOffset}
bind:text={note.text}
bind:x={note.x}
bind:y={note.y}
bind:zIndex={note.zIndex}
onMove={moveStickyNote}
onUpdate={updateStickyNote}
onDelete={deleteStickyNote}
/>
{/each}
<div <div
class="deck-area" class="deck-area"
style="left: {deckPosition.x}px; top: {deckPosition.y}px;" style="left: {deckPosition.x}px; top: {deckPosition.y}px;"
@ -147,6 +459,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<style> <style>
.game-container { .game-container {
@ -164,6 +477,33 @@
display: flex; display: flex;
gap: 10px; gap: 10px;
z-index: 2000; z-index: 2000;
align-items: center;
flex-wrap: wrap;
}
.control-group {
display: flex;
align-items: center;
gap: 5px;
background: rgba(255, 255, 255, 0.9);
padding: 8px 12px;
border-radius: 27px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.control-group label {
font-weight: 600;
font-size: 14px;
color: #333;
}
.control-group select {
padding: 4px 8px;
border: 1px solid #ccc;
border-radius: 4px;
background: white;
font-size: 14px;
cursor: pointer;
} }
.controls button { .controls button {
@ -187,6 +527,20 @@
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden;
cursor: grab;
}
.game-area:active {
cursor: grabbing;
}
.canvas-content {
position: relative;
width: 500vw;
height: 500vh;
min-width: 500vw;
min-height: 500vh;
} }
.deck-area { .deck-area {

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB