Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>๐งฌ DNA CASINO - Protein Synthesis Slot Machine</title> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Bebas+Neue&display=swap'); | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| background: #000; | |
| color: #fff; | |
| font-family: 'Orbitron', monospace; | |
| overflow-x: hidden; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: flex-start; | |
| min-height: 100vh; | |
| position: relative; | |
| padding-top: 10px; | |
| } | |
| /* Casino carpet pattern background */ | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: | |
| radial-gradient(circle at 20% 30%, #ff0066 0px, transparent 100px), | |
| radial-gradient(circle at 80% 70%, #00ffff 0px, transparent 100px), | |
| radial-gradient(circle at 50% 50%, #ffff00 0px, transparent 150px), | |
| radial-gradient(circle at 30% 80%, #ff00ff 0px, transparent 80px), | |
| radial-gradient(circle at 70% 20%, #00ff00 0px, transparent 90px); | |
| opacity: 0.1; | |
| z-index: 0; | |
| animation: casinoLights 10s ease-in-out infinite; | |
| } | |
| @keyframes casinoLights { | |
| 0%, 100% { filter: hue-rotate(0deg) brightness(1); } | |
| 50% { filter: hue-rotate(180deg) brightness(1.5); } | |
| } | |
| /* Static TV noise overlay */ | |
| body::after { | |
| content: ''; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-image: | |
| repeating-linear-gradient( | |
| 0deg, | |
| transparent 0px, | |
| rgba(255,255,255,0.08) 1px, | |
| transparent 1px, | |
| transparent 2px | |
| ), | |
| repeating-linear-gradient( | |
| 90deg, | |
| transparent 0px, | |
| rgba(0,0,0,0.05) 1px, | |
| transparent 1px, | |
| transparent 2px | |
| ); | |
| background-size: 2px 2px, 2px 2px; | |
| pointer-events: none; | |
| z-index: 1; | |
| opacity: 0.8; | |
| animation: staticNoise 0.1s steps(8) infinite; | |
| } | |
| @keyframes staticNoise { | |
| 0%, 100% { transform: translate(0, 0); } | |
| 50% { transform: translate(-1px, -1px); } | |
| } | |
| /* Main casino cabinet */ | |
| .machine-container { | |
| background: linear-gradient(145deg, #1a0000, #330000); | |
| border: 5px solid #ffd700; | |
| border-radius: 30px; | |
| padding: 20px; | |
| padding-right: 120px; | |
| box-shadow: | |
| 0 0 50px rgba(255, 215, 0, 0.5), | |
| 0 20px 40px rgba(0,0,0,0.8), | |
| inset 0 0 30px rgba(255, 215, 0, 0.2); | |
| width: 95vw; | |
| max-width: 1400px; | |
| position: relative; | |
| z-index: 2; | |
| } | |
| /* Blinking lights around the machine */ | |
| .machine-container::before { | |
| content: ''; | |
| position: absolute; | |
| top: -10px; | |
| left: -10px; | |
| right: -10px; | |
| bottom: -10px; | |
| border-radius: 35px; | |
| background: conic-gradient( | |
| from 0deg, | |
| #ff0000, #ff7700, #ffff00, #00ff00, | |
| #0000ff, #ff00ff, #ff0000 | |
| ); | |
| z-index: -1; | |
| animation: rotateGradient 3s linear infinite; | |
| filter: blur(10px); | |
| opacity: 0.7; | |
| } | |
| @keyframes rotateGradient { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| /* Neon title */ | |
| .title { | |
| text-align: center; | |
| font-family: 'Bebas Neue', cursive; | |
| font-size: 4rem; | |
| margin-bottom: 10px; | |
| position: relative; | |
| text-transform: uppercase; | |
| letter-spacing: 0.2em; | |
| } | |
| .title a { | |
| text-decoration: none; | |
| color: #fff; | |
| text-shadow: | |
| 0 0 10px #ff00ff, | |
| 0 0 20px #ff00ff, | |
| 0 0 30px #ff00ff, | |
| 0 0 40px #ff00ff, | |
| 0 0 70px #ff00ff, | |
| 0 0 80px #ff00ff, | |
| 0 0 100px #ff00ff, | |
| 0 0 150px #ff00ff; | |
| animation: neonFlicker 1.5s infinite alternate; | |
| } | |
| @keyframes neonFlicker { | |
| 0%, 100% { opacity: 1; } | |
| 33% { opacity: 0.8; } | |
| 66% { opacity: 0.9; } | |
| } | |
| .subtitle { | |
| text-align: center; | |
| font-size: 1.5rem; | |
| color: #ffd700; | |
| margin-bottom: 20px; | |
| text-shadow: 0 0 10px #ffd700; | |
| animation: pulse 2s ease-in-out infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| } | |
| /* Jackpot counter */ | |
| .jackpot-display { | |
| background: #000; | |
| border: 3px solid #ffd700; | |
| border-radius: 10px; | |
| padding: 10px 20px; | |
| margin: 10px auto; | |
| text-align: center; | |
| max-width: 400px; | |
| box-shadow: | |
| inset 0 0 20px rgba(255, 215, 0, 0.3), | |
| 0 0 20px rgba(255, 215, 0, 0.5); | |
| } | |
| .jackpot-label { | |
| font-size: 0.9rem; | |
| color: #ffd700; | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| } | |
| .jackpot-value { | |
| font-size: 2rem; | |
| color: #00ff00; | |
| font-weight: bold; | |
| text-shadow: 0 0 10px #00ff00; | |
| font-family: 'Orbitron', monospace; | |
| } | |
| /* Language selector */ | |
| .language-selector { | |
| display: flex; | |
| justify-content: center; | |
| margin: 20px 0; | |
| } | |
| .language-toggle { | |
| position: relative; | |
| display: inline-block; | |
| width: 120px; | |
| height: 40px; | |
| cursor: pointer; | |
| } | |
| .language-toggle input { | |
| opacity: 0; | |
| width: 0; | |
| height: 0; | |
| } | |
| .toggle-slider { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: linear-gradient(145deg, #444, #222); | |
| border: 2px solid #ffd700; | |
| border-radius: 40px; | |
| transition: all 0.4s; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 0 10px; | |
| box-shadow: | |
| 0 5px 15px rgba(0,0,0,0.5), | |
| inset 0 2px 5px rgba(255,255,255,0.1); | |
| } | |
| .lang-label { | |
| font-size: 0.9rem; | |
| font-weight: bold; | |
| color: #888; | |
| transition: all 0.3s; | |
| z-index: 2; | |
| } | |
| .lang-label[data-lang="en"] { | |
| margin-left: 5px; | |
| } | |
| .lang-label[data-lang="ko"] { | |
| margin-right: 5px; | |
| } | |
| .toggle-slider::after { | |
| content: ''; | |
| position: absolute; | |
| height: 32px; | |
| width: 50px; | |
| left: 4px; | |
| bottom: 4px; | |
| background: linear-gradient(145deg, #00ff00, #00cc00); | |
| border-radius: 32px; | |
| transition: all 0.4s; | |
| box-shadow: | |
| 0 2px 10px rgba(0,255,0,0.5), | |
| inset 0 -2px 5px rgba(0,0,0,0.3); | |
| } | |
| .language-toggle input:checked + .toggle-slider::after { | |
| transform: translateX(62px); | |
| background: linear-gradient(145deg, #ff4444, #cc0000); | |
| box-shadow: | |
| 0 2px 10px rgba(255,0,0,0.5), | |
| inset 0 -2px 5px rgba(0,0,0,0.3); | |
| } | |
| .language-toggle input:checked + .toggle-slider .lang-label[data-lang="ko"] { | |
| color: #fff; | |
| } | |
| .language-toggle input:not(:checked) + .toggle-slider .lang-label[data-lang="en"] { | |
| color: #fff; | |
| } | |
| .cell-type-selector { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 20px; | |
| margin-bottom: 20px; | |
| } | |
| .radio-group { | |
| display: flex; | |
| gap: 30px; | |
| } | |
| .chip-label { | |
| position: relative; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 100px; | |
| height: 100px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .chip-label input[type="radio"] { | |
| position: absolute; | |
| opacity: 0; | |
| } | |
| .chip { | |
| position: relative; | |
| width: 100%; | |
| height: 100%; | |
| border-radius: 50%; | |
| background: radial-gradient(circle at 30% 30%, #ff4444, #cc0000); | |
| box-shadow: | |
| 0 5px 15px rgba(0,0,0,0.5), | |
| inset 0 0 20px rgba(0,0,0,0.3), | |
| inset -3px -3px 10px rgba(255,255,255,0.2); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: bold; | |
| font-size: 1.1rem; | |
| color: #fff; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.5); | |
| border: 8px dashed #fff; | |
| animation: chipRotate 20s linear infinite; | |
| } | |
| @keyframes chipRotate { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .chip-label:hover .chip { | |
| transform: scale(1.1); | |
| box-shadow: | |
| 0 10px 30px rgba(255,0,0,0.5), | |
| inset 0 0 30px rgba(255,255,255,0.3); | |
| } | |
| .chip-label input[type="radio"]:checked + .chip { | |
| background: radial-gradient(circle at 30% 30%, #44ff44, #00cc00); | |
| box-shadow: | |
| 0 0 30px rgba(0,255,0,0.7), | |
| inset 0 0 20px rgba(0,0,0,0.3); | |
| animation: chipRotate 5s linear infinite, winPulse 1s ease-in-out infinite; | |
| } | |
| @keyframes winPulse { | |
| 0%, 100% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| } | |
| /* Slot machine reels */ | |
| .reels-container { | |
| background: #000; | |
| border: 5px solid #ffd700; | |
| border-radius: 15px; | |
| padding: 20px; | |
| max-width: 100%; | |
| position: relative; | |
| box-shadow: | |
| inset 0 0 50px rgba(0,0,0,0.8), | |
| 0 0 30px rgba(255, 215, 0, 0.5); | |
| overflow: visible; | |
| margin: 20px 0; | |
| } | |
| .reels-wrapper { | |
| display: flex; | |
| gap: 2px; | |
| min-width: fit-content; | |
| padding: 5px 0; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| background: linear-gradient(90deg, | |
| transparent 0%, | |
| rgba(255, 215, 0, 0.1) 50%, | |
| transparent 100%); | |
| } | |
| .reel { | |
| width: 20px; | |
| height: 50px; | |
| background: #ffffff; | |
| border: 2px solid #ffd700; | |
| border-radius: 3px; | |
| overflow: hidden; | |
| position: relative; | |
| box-shadow: | |
| inset 0 0 10px rgba(0,0,0,0.3), | |
| 0 0 5px rgba(255, 215, 0, 0.5); | |
| } | |
| .reel-strip { | |
| position: absolute; | |
| width: 100%; | |
| transition: transform 0.5s ease-out; | |
| } | |
| .nucleotide { | |
| height: 50px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.2rem; | |
| font-weight: bold; | |
| background: #ffffff; | |
| text-shadow: 0 0 5px currentColor; | |
| } | |
| .nucleotide.A { color: #00ff00; background: #001100; } | |
| .nucleotide.T { color: #ff0000; background: #110000; } | |
| .nucleotide.C { color: #00ffff; background: #001111; } | |
| .nucleotide.G { color: #ffff00; background: #111100; } | |
| /* Casino style lever */ | |
| .lever-container { | |
| position: absolute; | |
| right: -90px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| z-index: 3; | |
| width: 80px; | |
| height: 250px; | |
| } | |
| .lever { | |
| width: 100%; | |
| height: 100%; | |
| position: relative; | |
| cursor: pointer; | |
| } | |
| .lever-mount { | |
| position: absolute; | |
| top: 100px; | |
| left: -15px; | |
| width: 60px; | |
| height: 80px; | |
| background: linear-gradient(180deg, #ffd700, #b8860b); | |
| border-radius: 10px 0 0 10px; | |
| box-shadow: | |
| 0 5px 20px rgba(0,0,0,0.5), | |
| inset 0 2px 5px rgba(255,255,255,0.5); | |
| } | |
| .lever-arm { | |
| position: absolute; | |
| top: 30px; | |
| left: 10px; | |
| width: 15px; | |
| height: 100px; | |
| background: linear-gradient(180deg, #c0c0c0, #808080); | |
| border-radius: 8px; | |
| box-shadow: 0 3px 10px rgba(0,0,0,0.5); | |
| transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); | |
| } | |
| .lever-ball { | |
| position: absolute; | |
| top: -40px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: 80px; | |
| height: 80px; | |
| background: radial-gradient(circle at 35% 35%, #ff4444, #cc0000, #800000); | |
| border-radius: 50%; | |
| box-shadow: | |
| 0 10px 30px rgba(0,0,0,0.6), | |
| inset -10px -10px 20px rgba(0,0,0,0.5), | |
| inset 5px 5px 10px rgba(255,255,255,0.6); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .lever-ball::before { | |
| content: '777'; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| font-size: 1.5rem; | |
| font-weight: bold; | |
| color: #ffd700; | |
| text-shadow: 0 2px 4px rgba(0,0,0,0.8); | |
| } | |
| .lever.pulled .lever-arm { | |
| transform: translateY(100px); | |
| height: 20px; | |
| } | |
| /* Generate button */ | |
| .spin-button { | |
| background: linear-gradient(145deg, #ff0000, #cc0000); | |
| border: 3px solid #ffd700; | |
| padding: 25px 80px; | |
| font-size: 2rem; | |
| font-weight: bold; | |
| border-radius: 50px; | |
| cursor: pointer; | |
| text-transform: uppercase; | |
| letter-spacing: 3px; | |
| box-shadow: | |
| 0 10px 30px rgba(0,0,0,0.7), | |
| 0 0 30px rgba(255, 0, 0, 0.5), | |
| inset 0 2px 10px rgba(255,255,255,0.3); | |
| transition: all 0.3s ease; | |
| color: #fff; | |
| text-shadow: 0 3px 6px rgba(0,0,0,0.5); | |
| position: relative; | |
| overflow: hidden; | |
| font-family: 'Bebas Neue', cursive; | |
| } | |
| .spin-button::before { | |
| content: ''; | |
| position: absolute; | |
| top: -50%; | |
| left: -50%; | |
| width: 200%; | |
| height: 200%; | |
| background: linear-gradient(45deg, | |
| transparent 30%, | |
| rgba(255,255,255,0.3) 50%, | |
| transparent 70%); | |
| animation: buttonShine 3s infinite; | |
| } | |
| @keyframes buttonShine { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .spin-button:hover { | |
| transform: translateY(-3px) scale(1.05); | |
| box-shadow: | |
| 0 15px 40px rgba(0,0,0,0.8), | |
| 0 0 50px rgba(255, 0, 0, 0.7); | |
| } | |
| .spin-button:active { | |
| transform: translateY(0) scale(0.98); | |
| } | |
| .spin-button:disabled { | |
| background: linear-gradient(145deg, #444, #222); | |
| cursor: not-allowed; | |
| box-shadow: none; | |
| animation: none; | |
| } | |
| /* Sequence display */ | |
| .sequence-display { | |
| background: #000; | |
| border: 3px solid #00ff00; | |
| border-radius: 15px; | |
| padding: 25px; | |
| font-family: 'Orbitron', monospace; | |
| font-size: 0.9rem; | |
| letter-spacing: 2px; | |
| width: 100%; | |
| max-width: 1200px; | |
| margin: 20px auto; | |
| box-shadow: | |
| 0 0 30px rgba(0, 255, 0, 0.5), | |
| inset 0 0 20px rgba(0, 255, 0, 0.1); | |
| position: relative; | |
| min-height: 100px; | |
| word-wrap: break-word; | |
| word-break: break-all; | |
| white-space: pre-wrap; | |
| overflow-wrap: break-word; | |
| } | |
| .sequence-display::before { | |
| content: '๐งฌ SYNTHETIC REGULATORY ELEMENT'; | |
| position: absolute; | |
| top: -15px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background: #000; | |
| padding: 0 20px; | |
| font-size: 0.8rem; | |
| color: #00ff00; | |
| letter-spacing: 3px; | |
| text-shadow: 0 0 10px #00ff00; | |
| } | |
| /* Protein analysis panel */ | |
| .protein-panel { | |
| background: linear-gradient(145deg, #1a0033, #000033); | |
| border: 3px solid #00ffff; | |
| border-radius: 15px; | |
| padding: 25px; | |
| margin: 20px auto; | |
| max-width: 1200px; | |
| box-shadow: | |
| 0 0 30px rgba(0, 255, 255, 0.5), | |
| inset 0 0 20px rgba(0, 255, 255, 0.1); | |
| display: none; | |
| animation: fadeIn 0.5s ease-in; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .protein-panel.active { | |
| display: block; | |
| } | |
| .protein-header { | |
| font-size: 1.5rem; | |
| color: #00ffff; | |
| margin-bottom: 15px; | |
| text-align: center; | |
| text-transform: uppercase; | |
| letter-spacing: 3px; | |
| text-shadow: 0 0 15px #00ffff; | |
| } | |
| .protein-sequence { | |
| background: #000; | |
| border: 1px solid #00ffff; | |
| border-radius: 10px; | |
| padding: 15px; | |
| margin-bottom: 20px; | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.85rem; | |
| word-wrap: break-word; | |
| word-break: break-all; | |
| color: #00ff00; | |
| max-height: 150px; | |
| overflow-y: auto; | |
| white-space: pre-wrap; | |
| line-height: 1.4; | |
| } | |
| .protein-analysis { | |
| background: rgba(0, 255, 255, 0.05); | |
| border: 1px solid rgba(0, 255, 255, 0.3); | |
| border-radius: 10px; | |
| padding: 20px; | |
| color: #fff; | |
| line-height: 1.8; | |
| font-size: 0.95rem; | |
| } | |
| .protein-analysis h3 { | |
| color: #00ffff; | |
| margin-bottom: 10px; | |
| text-shadow: 0 0 10px #00ffff; | |
| } | |
| .protein-stats { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 15px; | |
| margin-bottom: 20px; | |
| } | |
| .stat-card { | |
| background: rgba(255, 215, 0, 0.1); | |
| border: 1px solid #ffd700; | |
| border-radius: 10px; | |
| padding: 15px; | |
| text-align: center; | |
| } | |
| .stat-label { | |
| font-size: 0.8rem; | |
| color: #ffd700; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| .stat-value { | |
| font-size: 1.5rem; | |
| color: #fff; | |
| font-weight: bold; | |
| margin-top: 5px; | |
| } | |
| /* Info footer */ | |
| .info { | |
| text-align: center; | |
| margin-top: 20px; | |
| color: #ffd700; | |
| font-size: 1rem; | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| text-shadow: 0 0 10px #ffd700; | |
| } | |
| .lab-credit { | |
| text-align: center; | |
| margin-top: 15px; | |
| font-size: 1.3rem; | |
| display: flex; | |
| justify-content: center; | |
| gap: 40px; | |
| flex-wrap: wrap; | |
| } | |
| .lab-credit a { | |
| color: #00ff00; | |
| text-decoration: none; | |
| font-weight: bold; | |
| letter-spacing: 2px; | |
| padding: 10px 30px; | |
| border: 2px solid #00ff00; | |
| border-radius: 30px; | |
| display: inline-block; | |
| transition: all 0.3s ease; | |
| text-shadow: 0 0 10px #00ff00; | |
| } | |
| .lab-credit a:nth-child(2) { | |
| color: #ffff00; | |
| border-color: #ffff00; | |
| text-shadow: 0 0 10px #ffff00; | |
| } | |
| .lab-credit a:hover { | |
| background: #00ff00; | |
| color: #000; | |
| box-shadow: 0 0 30px #00ff00; | |
| transform: scale(1.1); | |
| } | |
| .lab-credit a:nth-child(2):hover { | |
| background: #ffff00; | |
| box-shadow: 0 0 30px #ffff00; | |
| } | |
| /* Winning animation */ | |
| .jackpot-win { | |
| animation: jackpotFlash 2s ease-out; | |
| } | |
| @keyframes jackpotFlash { | |
| 0%, 100% { background-color: transparent; } | |
| 10%, 30%, 50%, 70%, 90% { background-color: rgba(255, 215, 0, 0.3); } | |
| 20%, 40%, 60%, 80% { background-color: transparent; } | |
| } | |
| /* Loading animation */ | |
| @keyframes continuousSpin { | |
| from { transform: translateY(0); } | |
| to { transform: translateY(-200px); } | |
| } | |
| .reel-strip.loading { | |
| animation: continuousSpin 1s linear infinite; | |
| } | |
| /* Scrollbar styling */ | |
| ::-webkit-scrollbar { | |
| width: 10px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: rgba(0, 0, 0, 0.5); | |
| border-radius: 5px; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #00ffff; | |
| border-radius: 5px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #00cccc; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="machine-container"> | |
| <h1 class="title"><a href="https://github.com/pinellolab/DNA-Diffusion" target="_blank" rel="noopener noreferrer">๐ฐ DNA CASINO ๐งฌ</a></h1> | |
| <div class="subtitle">โ PROTEIN SYNTHESIS JACKPOT โ </div> | |
| <div class="jackpot-display"> | |
| <div class="jackpot-label">Current Sequence Length</div> | |
| <div class="jackpot-value" id="sequenceCounter">0 BP</div> | |
| </div> | |
| <div class="cell-type-selector"> | |
| <div class="radio-group"> | |
| <label class="chip-label"> | |
| <input type="radio" name="cellType" value="K562" checked> | |
| <div class="chip">K562</div> | |
| </label> | |
| <label class="chip-label"> | |
| <input type="radio" name="cellType" value="GM12878"> | |
| <div class="chip">GM12878</div> | |
| </label> | |
| <label class="chip-label"> | |
| <input type="radio" name="cellType" value="HepG2"> | |
| <div class="chip">HepG2</div> | |
| </label> | |
| </div> | |
| </div> | |
| <div class="language-selector"> | |
| <label class="language-toggle"> | |
| <input type="checkbox" id="languageToggle"> | |
| <span class="toggle-slider"> | |
| <span class="lang-label" data-lang="en">EN</span> | |
| <span class="lang-label" data-lang="ko">ํ๊ธ</span> | |
| </span> | |
| </label> | |
| </div> | |
| <div class="reels-container" id="reelsContainer"> | |
| <div class="reels-wrapper" id="reelsWrapper"></div> | |
| <div class="lever-container"> | |
| <div class="lever" id="lever"> | |
| <div class="lever-mount"></div> | |
| <div class="lever-arm"> | |
| <div class="lever-ball"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="controls"> | |
| <button class="spin-button" id="spinButton"><span>๐ฒ SPIN TO WIN ๐ฒ</span></button> | |
| <div class="sequence-display" id="sequenceDisplay"> | |
| ๐ฏ <span id="initialMessage">Pull the lever or press SPIN to generate your winning sequence!</span> ๐ฏ | |
| </div> | |
| <div class="protein-panel" id="proteinPanel"> | |
| <div class="protein-header" id="proteinHeader">๐ฌ Protein Analysis Report ๐ฌ</div> | |
| <div class="protein-stats" id="proteinStats"></div> | |
| <div class="protein-sequence" id="proteinSequence"></div> | |
| <div class="protein-analysis" id="proteinAnalysis"> | |
| <h3>Analyzing protein structure...</h3> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="info"> | |
| ๐ฐ 200BP REGULATORY ELEMENTS โข CELL-TYPE SPECIFIC โข SYNTHETIC BIOLOGY JACKPOT ๐ฐ | |
| </div> | |
| <div class="lab-credit"> | |
| <a href="https://pinellolab.org" target="_blank" rel="noopener noreferrer"> | |
| ๐งฌ Based by PINELLO LAB ๐งฌ | |
| </a> | |
| <a href="https://discord.gg/openfreeai" target="_blank" rel="noopener noreferrer"> | |
| โก Powered by VIDraft โก | |
| </a> | |
| </div> | |
| </div> | |
| <script> | |
| const NUCLEOTIDES = ['A', 'T', 'C', 'G']; | |
| const REEL_COUNT = 200; | |
| let TARGET_SEQUENCE = ''; | |
| let reels = []; | |
| let isSpinning = false; | |
| function generateRandomSequence() { | |
| let sequence = ''; | |
| for (let i = 0; i < REEL_COUNT; i++) { | |
| sequence += NUCLEOTIDES[Math.floor(Math.random() * 4)]; | |
| } | |
| return sequence; | |
| } | |
| function createReel(index) { | |
| const reel = document.createElement('div'); | |
| reel.className = 'reel'; | |
| const strip = document.createElement('div'); | |
| strip.className = 'reel-strip'; | |
| // Create multiple nucleotides for smooth spinning effect | |
| for (let i = 0; i < 10; i++) { | |
| NUCLEOTIDES.forEach(n => { | |
| const nucleotide = document.createElement('div'); | |
| nucleotide.className = `nucleotide ${n}`; | |
| nucleotide.textContent = n; | |
| strip.appendChild(nucleotide); | |
| }); | |
| } | |
| reel.appendChild(strip); | |
| return { element: reel, strip: strip, currentPosition: 0 }; | |
| } | |
| function initializeReels() { | |
| const wrapper = document.getElementById('reelsWrapper'); | |
| wrapper.innerHTML = ''; | |
| reels = []; | |
| for (let i = 0; i < REEL_COUNT; i++) { | |
| const reel = createReel(i); | |
| reels.push(reel); | |
| wrapper.appendChild(reel.element); | |
| // Set initial position to show a random nucleotide | |
| const randomIndex = Math.floor(Math.random() * 4); | |
| const initialOffset = -randomIndex * 50; | |
| reel.strip.style.transform = `translateY(${initialOffset}px)`; | |
| reel.currentPosition = randomIndex * 50; | |
| } | |
| } | |
| function updateSequenceCounter(length) { | |
| const counter = document.getElementById('sequenceCounter'); | |
| counter.textContent = `${length} BP`; | |
| counter.style.animation = 'pulse 0.5s ease-out'; | |
| } | |
| function startContinuousSpinning() { | |
| reels.forEach((reel, index) => { | |
| // Add continuous spinning animation | |
| reel.strip.style.transition = 'none'; | |
| reel.strip.classList.add('loading'); | |
| // Add slight delay variation for visual effect | |
| const delay = (index % 10) * 0.1; | |
| reel.strip.style.animationDelay = `${delay}s`; | |
| }); | |
| // Update counter animation | |
| let count = 0; | |
| const counterInterval = setInterval(() => { | |
| if (!isSpinning) { | |
| clearInterval(counterInterval); | |
| return; | |
| } | |
| count = (count + 7) % 201; | |
| updateSequenceCounter(count); | |
| }, 100); | |
| } | |
| function displayProteinAnalysis(metadata) { | |
| const panel = document.getElementById('proteinPanel'); | |
| const header = document.getElementById('proteinHeader'); | |
| const statsDiv = document.getElementById('proteinStats'); | |
| const sequenceDiv = document.getElementById('proteinSequence'); | |
| const analysisDiv = document.getElementById('proteinAnalysis'); | |
| // Show panel | |
| panel.classList.add('active'); | |
| // Check language | |
| const isKorean = document.getElementById('languageToggle').checked; | |
| // Update header | |
| header.textContent = isKorean ? '๐ฌ ๋จ๋ฐฑ์ง ๋ถ์ ๋ณด๊ณ ์ ๐ฌ' : '๐ฌ Protein Analysis Report ๐ฌ'; | |
| // Display stats | |
| statsDiv.innerHTML = ` | |
| <div class="stat-card"> | |
| <div class="stat-label">${isKorean ? '์ธํฌ ํ์ ' : 'Cell Type'}</div> | |
| <div class="stat-value">${metadata.cell_type || 'Unknown'}</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">${isKorean ? 'DNA ๊ธธ์ด' : 'DNA Length'}</div> | |
| <div class="stat-value">${TARGET_SEQUENCE.length} bp</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">${isKorean ? '๋จ๋ฐฑ์ง ๊ธธ์ด' : 'Protein Length'}</div> | |
| <div class="stat-value">${metadata.protein_length || 0} aa</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">${isKorean ? '์์ฑ ์๊ฐ' : 'Generation Time'}</div> | |
| <div class="stat-value">${(metadata.generation_time || 0).toFixed(1)}s</div> | |
| </div> | |
| `; | |
| // Display protein sequence | |
| if (metadata.protein_sequence) { | |
| sequenceDiv.innerHTML = `<strong>${isKorean ? '๋จ๋ฐฑ์ง ์ํ์ค:' : 'Protein Sequence:'}</strong><br>${metadata.protein_sequence}`; | |
| } | |
| // Display analysis | |
| if (metadata.protein_analysis) { | |
| analysisDiv.innerHTML = `<h3>๐งฌ ${isKorean ? '๊ตฌ์กฐ ๋ฐ ๊ธฐ๋ฅ ๋ถ์' : 'Structural & Functional Analysis'}</h3>${metadata.protein_analysis}`; | |
| } else { | |
| analysisDiv.innerHTML = `<h3>โณ ${isKorean ? '๋จ๋ฐฑ์ง ๋ถ์ ๋๊ธฐ ์ค...' : 'Protein analysis pending...'}</h3>`; | |
| } | |
| } | |
| function stopAndShowSequence(sequence, metadata = {}) { | |
| TARGET_SEQUENCE = sequence; | |
| updateSequenceCounter(sequence.length); | |
| reels.forEach((reel, index) => { | |
| // Remove continuous spinning | |
| reel.strip.classList.remove('loading'); | |
| // Calculate target position | |
| const targetNucleotide = TARGET_SEQUENCE[index]; | |
| const targetIndex = NUCLEOTIDES.indexOf(targetNucleotide); | |
| const finalPosition = targetIndex * 50; | |
| // Set up the final positioning animation | |
| setTimeout(() => { | |
| reel.strip.style.transition = `transform ${1000 + index * 5}ms cubic-bezier(0.17, 0.67, 0.12, 0.99)`; | |
| reel.strip.style.transform = `translateY(${-finalPosition}px)`; | |
| reel.currentPosition = finalPosition; | |
| }, index * 2); | |
| }); | |
| // Show the complete sequence after animation | |
| setTimeout(() => { | |
| const container = document.getElementById('reelsContainer'); | |
| const display = document.getElementById('sequenceDisplay'); | |
| const button = document.getElementById('spinButton'); | |
| const lever = document.getElementById('lever'); | |
| container.classList.remove('spinning'); | |
| container.classList.add('jackpot-win'); | |
| const isKorean = document.getElementById('languageToggle').checked; | |
| display.innerHTML = `<strong>๐ ${isKorean ? '์ญํ ์ํ์ค' : 'JACKPOT SEQUENCE'} ๐</strong><br><span style="color: #00ff00; font-family: 'Courier New', monospace; word-wrap: break-word; word-break: break-all; display: block; margin-top: 10px;">${TARGET_SEQUENCE}</span>`; | |
| // Display protein analysis if available | |
| if (metadata) { | |
| displayProteinAnalysis(metadata); | |
| } | |
| button.disabled = false; | |
| isSpinning = false; | |
| // Release the lever | |
| lever.classList.remove('pulled'); | |
| setTimeout(() => { | |
| container.classList.remove('jackpot-win'); | |
| }, 2000); | |
| }, 1500); | |
| } | |
| function startGeneration() { | |
| if (isSpinning) return; | |
| isSpinning = true; | |
| const button = document.getElementById('spinButton'); | |
| const display = document.getElementById('sequenceDisplay'); | |
| const container = document.getElementById('reelsContainer'); | |
| const lever = document.getElementById('lever'); | |
| const proteinPanel = document.getElementById('proteinPanel'); | |
| // Hide protein panel from previous spin | |
| proteinPanel.classList.remove('active'); | |
| // Pull the lever | |
| lever.classList.add('pulled'); | |
| // Check language | |
| const isKorean = document.getElementById('languageToggle').checked; | |
| button.disabled = true; | |
| display.innerHTML = isKorean ? | |
| '๐ฒ <strong>์ ์ ์ ๋ฃฐ๋ ์ ๋๋ฆฌ๋ ์ค...</strong> ๐ฒ' : | |
| '๐ฒ <strong>SPINNING THE GENETIC WHEEL OF FORTUNE...</strong> ๐ฒ'; | |
| container.classList.add('spinning'); | |
| // Start continuous spinning immediately | |
| startContinuousSpinning(); | |
| // Get selected cell type and language | |
| const cellType = document.querySelector('input[name="cellType"]:checked').value; | |
| const language = document.getElementById('languageToggle').checked ? 'ko' : 'en'; | |
| // Send request to parent window | |
| window.parent.postMessage({ | |
| type: 'generate_request', | |
| cellType: cellType, | |
| language: language | |
| }, '*'); | |
| } | |
| // Initialize | |
| initializeReels(); | |
| // Language toggle event listener | |
| document.getElementById('languageToggle').addEventListener('change', function() { | |
| const isKorean = this.checked; | |
| const initialMessage = document.getElementById('initialMessage'); | |
| if (initialMessage && !isSpinning) { | |
| initialMessage.textContent = isKorean ? | |
| '๋ ๋ฒ๋ฅผ ๋น๊ธฐ๊ฑฐ๋ SPIN์ ๋๋ฌ ๋น์ฒจ ์ํ์ค๋ฅผ ์์ฑํ์ธ์!' : | |
| 'Pull the lever or press SPIN to generate your winning sequence!'; | |
| } | |
| }); | |
| // Event listeners | |
| document.getElementById('spinButton').addEventListener('click', startGeneration); | |
| // Lever click functionality | |
| document.getElementById('lever').addEventListener('click', function() { | |
| if (!isSpinning) { | |
| startGeneration(); | |
| } | |
| }); | |
| // Listen for messages from parent window | |
| window.addEventListener('message', (event) => { | |
| if (event.data.type === 'sequence_generated') { | |
| // Stop spinning and show the actual sequence with metadata | |
| stopAndShowSequence(event.data.sequence, event.data.metadata); | |
| } else if (event.data.type === 'generation_error') { | |
| // Stop spinning and show error | |
| reels.forEach(reel => { | |
| reel.strip.classList.remove('loading'); | |
| }); | |
| const container = document.getElementById('reelsContainer'); | |
| const display = document.getElementById('sequenceDisplay'); | |
| const button = document.getElementById('spinButton'); | |
| const lever = document.getElementById('lever'); | |
| const isKorean = document.getElementById('languageToggle').checked; | |
| container.classList.remove('spinning'); | |
| display.innerHTML = `<strong style="color: #ff0000;">โ ${isKorean ? '์คํจ!' : 'BUST!'} โ</strong><br>` + event.data.error; | |
| button.disabled = false; | |
| isSpinning = false; | |
| lever.classList.remove('pulled'); | |
| updateSequenceCounter(0); | |
| } | |
| }); | |
| // Keyboard support | |
| document.addEventListener('keydown', (e) => { | |
| if (e.code === 'Space' && !isSpinning) { | |
| e.preventDefault(); | |
| startGeneration(); | |
| } | |
| }); | |
| // Add some casino ambiance | |
| setInterval(() => { | |
| if (!isSpinning && Math.random() > 0.7) { | |
| const chips = document.querySelectorAll('.chip'); | |
| const randomChip = chips[Math.floor(Math.random() * chips.length)]; | |
| randomChip.style.animation = 'none'; | |
| setTimeout(() => { | |
| randomChip.style.animation = 'chipRotate 20s linear infinite'; | |
| }, 10); | |
| } | |
| }, 5000); | |
| </script> | |
| </body> | |
| </html> |