Skip to content
Merged
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
43 changes: 43 additions & 0 deletions Javascript/flip a coin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Flip a Coin Simulator

A web-based coin flip simulator inspired by Google's coin flip feature. Flip a virtual coin with smooth 3D animations and get random heads or tails results.

## Features

- 🎲 **Random coin flips** - Fair 50/50 chance for heads or tails
- 🎨 **Smooth 3D animations** - Beautiful spinning coin effect with CSS 3D transforms
- ⌨️ **Keyboard support** - Press Space or Enter to flip
- ♿ **Accessibility** - Supports reduced motion preferences and proper ARIA labels
- 📱 **Responsive design** - Works on desktop and mobile devices
- 🎯 **Simple interface** - Clean, modern UI with dark theme

## How to Use

1. Open `index.html` in any modern web browser
2. Click the "Flip" button or press Space/Enter to flip the coin
3. Watch the coin spin and see the result (Heads or Tails)

## Files

- `index.html` - Main HTML structure
- `style.css` - Styling and animations
- `script.js` - Flip logic and interactions

## Browser Compatibility

Works in all modern browsers that support:
- CSS 3D Transforms
- Web Animations API (with fallback to CSS transitions)
- ES6 JavaScript

## Technical Details

- Uses CSS `transform-style: preserve-3d` for 3D coin effect
- Implements both Web Animations API and CSS transition fallback
- Respects `prefers-reduced-motion` media query for accessibility
- Fair random selection using `Math.random()`

## License

Free to use and modify.

37 changes: 37 additions & 0 deletions Javascript/flip a coin/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!doctype html>
<html lang="en">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flip a coin</title>
<meta name="description" content="Flip a coin simulator like Google">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>

<body>
<main class="app" role="main">
<h1 class="title">Flip a coin</h1>

<section class="stage" aria-label="Coin flip area">
<div id="coin" class="coin" aria-hidden="true">
<div class="face heads" data-label="Heads">Heads</div>
<div class="face tails" data-label="Tails">Tails</div>
</div>
</section>

<div class="controls">
<button id="flipBtn" class="btn" type="button" aria-label="Flip the coin">Flip</button>
</div>

<p id="result" class="result" aria-live="polite" aria-atomic="true">Tap Flip to start</p>
</main>

<script src="script.js"></script>

</body>

</html>
115 changes: 115 additions & 0 deletions Javascript/flip a coin/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
(() => {
const coin = document.getElementById('coin');
const flipBtn = document.getElementById('flipBtn');
const resultEl = document.getElementById('result');

let isFlipping = false;
let currentAngle = 0; // 0 => heads shown; 180 => tails shown (mod 360)

function getTargetAngle(face) {
return face === 'heads' ? 0 : 180;
}

function chooseRandomFace() {
return Math.random() < 0.5 ? 'heads' : 'tails';
}

function setImmediateFace(face) {
currentAngle = getTargetAngle(face);
coin.style.transform = `rotateY(${currentAngle}deg)`;
coin.classList.toggle('show-heads', face === 'heads');
coin.classList.toggle('show-tails', face === 'tails');
coin.classList.remove('spinning');
}

function announce(face) {
resultEl.textContent = face === 'heads' ? 'Heads' : 'Tails';
}

function flip(targetFace) {
if (isFlipping) return;
isFlipping = true;
flipBtn.disabled = true;

const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const target = targetFace || chooseRandomFace();

if (prefersReduced) {
setImmediateFace(target);
announce(target);
isFlipping = false;
flipBtn.disabled = false;
return;
}

const targetAngle = getTargetAngle(target);
// Add multiple full spins for flair
const fullSpins = 6; // even number so facing side logic is preserved cleanly
const start = currentAngle;
const end = start + fullSpins * 180 + (targetAngle - (start % 360));

const duration = 900; // ms
const supportsWAAPI = typeof coin.animate === 'function';

// During the spin, allow both faces to be visible to avoid popping
coin.classList.remove('show-heads', 'show-tails');
coin.classList.add('spinning');

if (supportsWAAPI) {
coin.animate(
[
{ transform: `rotateY(${start}deg)` },
{ transform: `rotateY(${end}deg)` }
],
{
duration,
easing: 'cubic-bezier(.22,.61,.36,1)',
fill: 'forwards'
}
).addEventListener('finish', () => {
currentAngle = ((end % 360) + 360) % 360;
// Snap to exact final state to avoid subpixel drift
coin.style.transform = `rotateY(${currentAngle}deg)`;
coin.classList.toggle('show-heads', target === 'heads');
coin.classList.toggle('show-tails', target === 'tails');
coin.classList.remove('spinning');
announce(target);
isFlipping = false;
flipBtn.disabled = false;
});
} else {
// Fallback: use CSS transition defined on .coin
// Set starting transform explicitly to ensure transition runs from the right place
coin.style.transform = `rotateY(${start}deg)`;
// Next frame, set to end to trigger transition
requestAnimationFrame(() => {
coin.style.transform = `rotateY(${end}deg)`;
});
window.setTimeout(() => {
currentAngle = ((end % 360) + 360) % 360;
coin.style.transform = `rotateY(${currentAngle}deg)`;
coin.classList.toggle('show-heads', target === 'heads');
coin.classList.toggle('show-tails', target === 'tails');
coin.classList.remove('spinning');
announce(target);
isFlipping = false;
flipBtn.disabled = false;
}, duration);
}
}

flipBtn.addEventListener('click', () => flip());

// Keyboard: Space/Enter to flip
document.addEventListener('keydown', (e) => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
flip();
}
});

// Initialize with a fair random face so UI doesn’t feel static
setImmediateFace(chooseRandomFace());
})();


163 changes: 163 additions & 0 deletions Javascript/flip a coin/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
:root {
--bg: #0b0b10;
--card: #12121a;
--text: #e9eaf0;
--muted: #a8adbd;
--accent: #6750a4;
/* Googley purple */
--accent-2: #7c4dff;
--shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
}

* {
box-sizing: border-box;
}

html,
body {
height: 100%;
}

body {
margin: 0;
font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
background: radial-gradient(1200px 800px at 50% -10%, #1a1b27 0%, var(--bg) 55%);
color: var(--text);
}

.app {
min-height: 100vh;
display: grid;
grid-template-rows: auto 1fr auto auto;
place-items: center;
gap: 24px;
padding: 40px 16px;
}

.title {
margin: 0;
font-size: clamp(24px, 4vw, 36px);
font-weight: 800;
letter-spacing: -0.02em;
}

.stage {
width: min(520px, 92vw);
height: min(520px, 92vw);
display: grid;
place-items: center;
perspective: 1200px;
}

.coin {
width: 52%;
aspect-ratio: 1/1;
position: relative;
transform-style: preserve-3d;
transform: rotateY(0deg);
transition: transform 0.8s cubic-bezier(.22, .61, .36, 1);
filter: drop-shadow(0 12px 28px rgba(0, 0, 0, .45));
}

.face {
position: absolute;
inset: 0;
display: grid;
place-items: center;
border-radius: 50%;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.08em;
visibility: visible;
opacity: 0;
transition: opacity .18s ease;
}

.face.heads {
background: radial-gradient(120% 120% at 30% 30%, #ffd27a 0%, #f2b741 40%, #d6961f 70%, #ae6d00 100%);
color: #3a2610;
border: 6px solid rgba(255, 219, 120, 0.65);
transform: rotateY(0deg);
}

.face.tails {
background: radial-gradient(120% 120% at 30% 30%, #c8d7ff 0%, #9db8ff 40%, #7896ff 70%, #4a6bff 100%);
color: #0f1a4a;
border: 6px solid rgba(180, 198, 255, 0.7);
transform: rotateY(180deg);
}

/* Show correct face at rest */
.coin.show-heads .face.heads {
opacity: 1;
}

.coin.show-heads .face.tails {
opacity: 0;
}

.coin.show-tails .face.tails {
opacity: 1;
}

.coin.show-tails .face.heads {
opacity: 0;
}

/* During spin show both to avoid popping */
.coin.spinning .face {
opacity: 1;
}

/* Subtle rim */
.coin::before {
content: "";
position: absolute;
inset: -2%;
border-radius: 50%;
background: conic-gradient(from 0deg, #0000 0 20%, rgba(0, 0, 0, .35) 20% 30%, #0000 30% 70%, rgba(255, 255, 255, .25) 70% 80%, #0000 80% 100%);
filter: blur(1px);
z-index: -1;
}

.controls {
display: grid;
place-items: center;
gap: 12px;
}

.btn {
appearance: none;
border: 0;
padding: 14px 28px;
font-size: 16px;
font-weight: 700;
color: white;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
border-radius: 999px;
box-shadow: var(--shadow);
cursor: pointer;
}

.btn:disabled {
opacity: .6;
cursor: not-allowed;
}

.btn:focus-visible {
outline: 3px solid rgba(124, 77, 255, .6);
outline-offset: 3px;
}

.result {
margin: 0;
font-size: clamp(18px, 2.6vw, 22px);
color: var(--muted);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
.coin {
transition: none;
}
}