Created
January 15, 2023 23:09
-
-
Save daveknights/ed7d035999e706a9a20ac64a5f7aca1e to your computer and use it in GitHub Desktop.
An online game with increasing levels of difficulty.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@800&family=Spectral:wght@800&display=swap'); | |
:root { | |
--brick-pattern: repeating-linear-gradient(90deg, #fad79e, #fad79e 2px, #cca065 2px, #cca065 38px, #965c00 38px, #965c00 40px); | |
--brick-border-bottom: solid 1px #965c00; | |
--brick-border-top: solid 2px #fad79e; | |
--engraved-colour: #aa9668; | |
--game-bg: linear-gradient(#2487dc, #90c6ef 600px, #dfc07c 600px, #e3bc81); | |
--gold-grad: linear-gradient(yellow, gold 80%, sandybrown); | |
--red-grad: linear-gradient(pink, red 80%, darkred); | |
--sand: #dfc07c; | |
--selected: #00cc99; | |
--sky-shadow: #0467bb; | |
} | |
* { | |
border: 0; | |
box-sizing: border-box; | |
margin: 0; | |
padding: 0; | |
} | |
h1 { | |
color: gold; | |
font-family: 'Spectral', serif; | |
font-family: 'Orbitron', sans-serif; | |
font-size: 4rem; | |
margin: 0 0 40px 2px; | |
position: absolute; | |
text-align: center; | |
text-shadow: 1px 2px 1px black, -2px -1px 1px white; | |
top: 30px; | |
} | |
h2 { | |
color: black; | |
font-family: 'Orbitron', sans-serif; | |
letter-spacing: 1px; | |
margin-bottom: 2rem; | |
text-align: center; | |
width: 100%; | |
} | |
.game-bg { | |
background: var(--game-bg); | |
height: 800px; | |
margin: 50px auto 0; | |
padding: 30px; | |
position: relative; | |
width: 600px; | |
} | |
.game-bg, | |
.pyramid-container, | |
.bonus-container, | |
.block-stack { | |
align-items: center; | |
display: flex; | |
flex-direction: column; | |
justify-content: flex-end; | |
} | |
.level-container { | |
margin-bottom: 20px; | |
} | |
.row { | |
display: flex; | |
gap: 20px; | |
justify-content: center; | |
margin-bottom: 20px; | |
} | |
.level, | |
.bonus-level { | |
align-items: center; | |
background: var(--sand); | |
border-bottom: var(--brick-border-bottom); | |
border-top: var(--brick-border-top); | |
box-shadow: 0 2px 5px var(--sky-shadow); | |
display: flex; | |
height: 90px; | |
justify-content: center; | |
} | |
.level { | |
width: 90px; | |
} | |
.playable, | |
.unlocked { | |
cursor: pointer; | |
} | |
.playable, | |
.bonus-level p { | |
color: var(--engraved-colour); | |
font-size: 3rem; | |
font-weight: bold; | |
height: 90px; | |
line-height: 90px; | |
position: relative; | |
text-align: center; | |
text-shadow: 0px 1px 0px rgba(255,255,255,0.7), 0px -1px 0px rgba(0,0,0,0.7); | |
width: 100%; | |
} | |
.locked { | |
border-radius: 0 0 1px 1px; | |
height: 12px; | |
margin-top: 8px; | |
outline: solid 14px var(--engraved-colour); | |
position: relative; | |
width: 6px; | |
} | |
.locked::before { | |
border-radius: 50%; | |
content: ''; | |
display: block; | |
height: 15px; | |
left: -4px; | |
outline: solid 4px var(--engraved-colour); | |
position: absolute; | |
top: -22px; | |
width: 14px; | |
} | |
.playable::before, | |
.playable::after, | |
.star::before, | |
.star::after { | |
border-left: 15px solid transparent; | |
border-right: 15px solid transparent; | |
bottom: 8px; | |
content: ''; | |
display: block; | |
right: 5px; | |
position: absolute; | |
} | |
.playable::before { | |
border-bottom: 15px solid rgba(0,0,0,0.7); | |
right: 6px; | |
} | |
.playable::after { | |
border-bottom: 15px solid var(--engraved-colour); | |
} | |
.star::before { | |
border-bottom: 15px solid #a1772d; | |
right:7px; | |
} | |
.star::after { | |
border-bottom: 15px solid gold; | |
} | |
.bonus-level { | |
position: relative; | |
width: 420px; | |
} | |
.bonus-level .locked { | |
display: inline-block; | |
margin-bottom: 6px; | |
margin-right: 40px; | |
} | |
.button-nav, | |
.difficulty { | |
margin-bottom: auto; | |
margin-top: 100px; | |
display: flex; | |
justify-content: space-between; | |
width: 100%; | |
} | |
.difficulty button, | |
.button-nav button { | |
background: var(--gold-grad); | |
border: solid 5px white; | |
border-radius: 10px; | |
box-shadow: 0 0 5px black, inset 0 0 1px 1px black; | |
cursor: pointer; | |
font-weight: bold; | |
font-size: 1.5rem; | |
height: 60px; | |
width: 150px; | |
} | |
button span { | |
display: inline-block; | |
} | |
.return-span { | |
margin-right: 9px; | |
} | |
.next-span { | |
margin-left: 12px; | |
} | |
.difficulty { | |
flex-wrap: wrap; | |
margin-top: 40px; | |
width: 420px; | |
} | |
.difficulty button { | |
color: grey; | |
} | |
.difficulty button:hover { | |
color: black; | |
} | |
.difficulty button.selected { | |
border-color: var(--selected); | |
color: black; | |
cursor: auto; | |
} | |
.pyramid-container { | |
padding-bottom: 130px; | |
} | |
.bonus-container { | |
padding-bottom: 150px; | |
} | |
.block-line { | |
background: var(--brick-pattern); | |
border-bottom: var(--brick-border-bottom); | |
border-top: var(--brick-border-top); | |
cursor: pointer; | |
height: 30px; | |
width: 300px; | |
} | |
.capstone { | |
align-items: center; | |
display: flex; | |
flex-direction: column; | |
height: 60px; | |
width: 80px; | |
} | |
.capstone::before, | |
.capstone::after { | |
background: var(--brick-pattern); | |
border-bottom: var(--brick-border-bottom); | |
border-top: var(--brick-border-top); | |
content: ''; | |
height: 30px; | |
} | |
.capstone::before { | |
width: 40px; | |
} | |
.capstone::after { | |
width: 80px; | |
} | |
.message { | |
color: white; | |
bottom: 80px; | |
font-family: 'Spectral', serif; | |
font-family: 'Orbitron', sans-serif; | |
font-size: 3.5rem; | |
position: absolute; | |
text-align: center; | |
-webkit-text-stroke: 2px; | |
-webkit-text-stroke-color: black; | |
width: 420px; | |
} | |
.message::before { | |
content: ''; | |
display: block; | |
height: 440px; | |
width: 420px; | |
} | |
progress[value]::-webkit-progress-bar { | |
background-color: #eee; | |
border-radius: 2px; | |
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25) inset; | |
} | |
progress.time-indicator { | |
-webkit-appearance: none; | |
appearance: none; | |
border: solid 1px #555; | |
color: gold; | |
height: 20px; | |
width: 100%; | |
} | |
progress.time-indicator.final-second { | |
color: red; | |
} | |
progress.time-indicator::-webkit-progress-value { | |
background: var(--gold-grad); | |
} | |
progress.time-indicator::-moz-progress-bar { | |
background: var(--gold-grad); | |
} | |
progress.time-indicator.final-second::-webkit-progress-value { | |
background: var(--red-grad); | |
} | |
progress.time-indicator.final-second::-moz-progress-bar { | |
background: var(--gold-grad); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Fragment, useEffect, useState, useCallback } from 'react'; | |
import Levels from './Levels'; | |
import Pyramid from './Pyramid'; | |
import BonusLevel from './BonusLevel'; | |
import TimeIndicator from './TimeIndicator'; | |
import Navigation from './Navigation'; | |
import DisplayMessage from './DisplayMessage'; | |
import './App.css'; | |
const initialBlockWidths = ['320', '280', '240', '200', '160', '120']; | |
const initialBonusParts = [...Array(11).keys()]; | |
const levels = { | |
1: {widths: [...initialBlockWidths]}, | |
2: {widths: [...initialBlockWidths, '360']}, | |
3: {widths: [...initialBlockWidths, '360', '400']}, | |
4: {widths: [...initialBlockWidths, '360', '400', '440']}, | |
5: {widths: [...initialBlockWidths, '360', '400', '440', '480']}, | |
6: {widths: [...initialBlockWidths, '360', '400', '440', '480', '520']}, | |
}; | |
const initialLevelData = {}; | |
// Populate initialLevelData | |
for (const level in levels) { | |
parseInt(level) === 1 ? initialLevelData[level] = {locked: false, star: false} : | |
initialLevelData[level] = {locked: true, star: false}; | |
} | |
initialLevelData['bonus'] = {locked: true, star: false} | |
let timeClass = ''; | |
let gameTimer; | |
let gameTime = 0; | |
const App = () => { | |
const [difficulty, setDifficulty] = useState('normal'); | |
const [level, setLevel] = useState(1); | |
const [completedLevels, setCompletedLevels] = useState([]); | |
const [blockWidths, setBlockWidths] = useState(initialBlockWidths); | |
const [bonusParts, setBonusParts] = useState(initialBonusParts); | |
const [timeIndicatorValue, setTimeIndicatorValue] = useState(0); | |
const [levelData, setLevelData] = useState(initialLevelData); | |
const [playing, setPlaying] = useState(false); | |
const [isSolved, setIsSolved] = useState(false); | |
const [message, setMessage] = useState(''); | |
const stopTimer = () => { | |
clearInterval(gameTimer); | |
gameTimer = null; | |
setMessage(`Time's Up!`); | |
}; | |
const upDateTimeIndicator = useCallback((chosenLevel, widthArray = null) => { | |
const levelDifficulty = difficulty === 'normal' ? 1 : 1.5; | |
const bonusDifficulty = difficulty === 'normal' ? 1.5 : 2; | |
const partsLength = chosenLevel === 'bonus' ? bonusParts.length * bonusDifficulty : widthArray.length * levelDifficulty; | |
gameTime += 0.1; | |
setTimeIndicatorValue((gameTime / partsLength) * 100); | |
if (!timeClass && gameTime > partsLength - 1) { | |
timeClass = ' final-second'; | |
} | |
gameTime >= partsLength && stopTimer(); | |
}, [bonusParts.length, difficulty]); | |
const startTimer = useCallback((chosenLevel, widthArray) => { | |
!gameTimer && (gameTimer = setInterval(() => upDateTimeIndicator(chosenLevel, widthArray), 100)); | |
}, [upDateTimeIndicator]); | |
const showLevels = () => { | |
setPlaying(false); | |
setIsSolved(false); | |
stopTimer(); | |
setMessage(''); | |
}; | |
const handleSelectLevel = e => { | |
if (e.target.classList.contains('playable') || e.target.classList.contains('unlocked')) { | |
setPlaying(true); | |
if (e.target.textContent === 'Bonus Level') { | |
startLevel('bonus') | |
} else if (e.target.parentNode.classList.contains('level')) { | |
startLevel(parseInt(e.target.textContent)); | |
} | |
} | |
}; | |
const handleSetDifficulty = e => setDifficulty(e.target.dataset.difficulty); | |
const shuffle = widths => widths | |
.map(value => ({ value, sort: Math.random() })) | |
.sort((a, b) => a.sort - b.sort) | |
.map(({ value }) => value).slice(0, widths.length); | |
const startLevel = useCallback((chosenLevel, action = null) => { | |
let widthArray = null; | |
setMessage(''); | |
if (action === 'next level' && (chosenLevel !== 'bonus' && chosenLevel !== 6)) { | |
setLevel(level => level + 1); | |
setBlockWidths(shuffle(levels[level + 1].widths)); | |
chosenLevel = chosenLevel + 1; | |
widthArray = levels[level + 1].widths; | |
} else if ((action === 'next level' && level === 6) || chosenLevel === 'bonus') { | |
chosenLevel = 'bonus'; | |
setLevel('bonus'); | |
setBonusParts(bP => shuffle(bP)); | |
} else { | |
setLevel(chosenLevel); | |
setBlockWidths(shuffle(levels[chosenLevel].widths)); | |
widthArray = levels[chosenLevel].widths | |
} | |
timeClass = ''; | |
startTimer(chosenLevel, widthArray); | |
}, [startTimer, level]); | |
useEffect (() => { | |
let sorted; | |
const updateLevelData = () => { | |
gameTimer && setMessage('Well Done!'); | |
clearInterval(gameTimer); | |
if (gameTimer && !completedLevels.includes(level)) { | |
const updatedLevelData = {...levelData}; | |
setCompletedLevels(current => [...current, level]); | |
updatedLevelData[level].locked === true && (updatedLevelData[level].locked = false); | |
updatedLevelData[level].star === false && (updatedLevelData[level].star = true); | |
if (level < 6 && updatedLevelData[level + 1].locked === true) { | |
updatedLevelData[level + 1].locked = false; | |
} | |
if (completedLevels.length + 1 === 6) { | |
updatedLevelData['bonus'].locked = false; | |
} | |
setLevelData(updatedLevelData); | |
} | |
}; | |
const checkBonusSorting = () => { | |
for (const part of bonusParts) { | |
if (bonusParts[part] > bonusParts[part + 1]) { | |
sorted = false; | |
break; | |
} else { | |
sorted = true; | |
} | |
} | |
sorted && updateLevelData(); | |
gameTimer && setIsSolved(sorted); | |
isSolved && (gameTimer = null); | |
}; | |
const checkSorting = () => { | |
for (const width in blockWidths) { | |
if (parseInt(width) > 0 && parseInt(blockWidths[width]) !== parseInt(blockWidths[parseInt(width) - 1]) + 40) { | |
sorted = false; | |
break; | |
} else { | |
sorted = true; | |
} | |
} | |
sorted && updateLevelData(); | |
gameTimer && setIsSolved(sorted); | |
isSolved && (gameTimer = null); | |
} | |
playing && (level !== 'bonus' ? checkSorting() : checkBonusSorting()); | |
}, [completedLevels, isSolved, levelData, bonusParts, level, blockWidths, playing]); | |
const handleNavBtnClick = e => { | |
gameTime = 0; | |
setIsSolved(false); | |
switch (e.target.dataset.action) { | |
case 'to levels': | |
showLevels(); | |
break; | |
case 'try again': | |
startLevel(level); | |
break; | |
case 'next level': | |
startLevel(level, e.target.dataset.action); | |
break; | |
default: | |
break; | |
} | |
}; | |
return ( | |
<Fragment> | |
<h1>PYR▲MIDS</h1> | |
{!playing && <Levels | |
levelData={levelData} | |
handleSelectLevelFn={handleSelectLevel} | |
difficulty={difficulty} | |
handleSetDifficultyFn={handleSetDifficulty} />} | |
{playing && (!gameTimer || isSolved) && <Navigation | |
showNext={completedLevels.includes(level) && level !== 'bonus' } | |
handleNavBtnClickFn={handleNavBtnClick} />} | |
{(playing && level !== 'bonus') && <Pyramid | |
blockWidths={blockWidths} | |
setBlockWidths={setBlockWidths} | |
isSolved={isSolved} />} | |
{(playing && level === 'bonus') && <BonusLevel | |
parts={bonusParts} | |
setBonusParts={setBonusParts} />} | |
{message && <DisplayMessage message={message} />} | |
{playing && <TimeIndicator | |
timeClass={timeClass} | |
value={timeIndicatorValue} />} | |
</Fragment> | |
); | |
} | |
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
const blockLine = props => <div className="block-line" style={{width: props.width + 'px'}}></div>; | |
export default blockLine; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.strip { | |
cursor: pointer; | |
display: flex; | |
height: 32px; | |
justify-content: center; | |
} | |
.part-0 { | |
overflow: hidden; | |
position: relative; | |
width: 160px; | |
} | |
.part-0::before { | |
background: #e3bc81; | |
border-radius: 50%; | |
content: ''; | |
display: block; | |
height: 50px; | |
margin-top: 10px; | |
position: absolute; | |
width: 114px; | |
} | |
.part-0::after { | |
border-top: 32px solid #cca065; | |
border-left: 5px solid transparent; | |
border-right: 5px solid transparent; | |
border-radius: 50% 50% 0 0; | |
content: ''; | |
position: absolute; | |
width: 32px; | |
} | |
.part-1 { | |
border-left: 25px solid transparent; | |
border-right: 25px solid transparent; | |
width: 168px; | |
} | |
.part-1::before { | |
border-top: 15px solid #cca065; | |
border-left: 5px solid transparent; | |
border-right: 5px solid transparent; | |
border-radius: 0 0 15px 15px; | |
content: ''; | |
position: absolute; | |
width: 22px; | |
z-index: 1; | |
} | |
.part-1::after, | |
.part-2::after, | |
.part-3::after { | |
background: #e3bc81; | |
content: ''; | |
height: 32px; | |
position: absolute; | |
width: 114px; | |
} | |
.part-1, | |
.part-3 { | |
border-bottom: 32px solid #cca065; | |
} | |
.part-2, | |
.part-4 { | |
border-bottom: 32px solid #965c00; | |
} | |
.part-2 { | |
border-left: 19px solid transparent; | |
border-right: 19px solid transparent; | |
width: 208px; | |
} | |
.part-3 { | |
border-left: 20px solid transparent; | |
border-right: 20px solid transparent; | |
width: 248px; | |
} | |
.part-4 { | |
border-left: 15px solid transparent; | |
border-right: 15px solid transparent; | |
width: 276px; | |
} | |
.part-4::after, | |
.part-5::after { | |
border-top: 32px solid #e3bc81; | |
border-left: 10px solid transparent; | |
border-right: 10px solid transparent; | |
content: ''; | |
position: absolute; | |
width: 94px; | |
} | |
.part-5 { | |
background: #cca065; | |
border-radius: 2px 2px 20px 20px; | |
height: 32px; | |
width: 276px; | |
} | |
.part-5::after { | |
border-radius: 0 0 50% 50%; | |
width: 74px; | |
} | |
.part-6 { | |
border-top: 32px solid #965c00; | |
border-left: 28px solid transparent; | |
border-right: 28px solid transparent; | |
position: relative; | |
width: 250px; | |
} | |
.part-6::after { | |
border-bottom: 32px solid #e3bc81; | |
border-left: 7px solid transparent; | |
border-right: 7px solid transparent; | |
content: ''; | |
position: absolute; | |
top: -32px; | |
width: 42px; | |
} | |
.part-7 { | |
border-top: 32px solid #cca065; | |
border-left: 32px solid transparent; | |
border-right: 32px solid transparent; | |
position: relative; | |
width: 194px; | |
} | |
.part-7::after { | |
background: #e3bc81; | |
border-radius: 80px 80px 0 0; | |
content: ''; | |
height: 32px; | |
position: absolute; | |
top: -32px; | |
width: 180px; | |
} | |
.part-8 { | |
background: #cca065; | |
border-radius: 50px 50px 0 0; | |
width: 224px; | |
} | |
.part-9 { | |
background: #e3bc81; | |
border-radius: 25px 25px 0 0; | |
position: relative; | |
width: 268px; | |
} | |
.part-9::before, | |
.part-9::after { | |
background: #e3bc81; | |
bottom: 0; | |
border-radius: 50px 50px 0 0; | |
border-top: solid 1px #965c00; | |
content: ''; | |
height: 10px; | |
position: absolute; | |
width: 100px; | |
} | |
.part-9::before { | |
left: -16px; | |
} | |
.part-9::after { | |
right: -16px; | |
} | |
.part-10 { | |
background: #cca065; | |
display: flex; | |
position: relative; | |
width: 300px; | |
} | |
.part-10::before, | |
.part-10::after { | |
background: linear-gradient(90deg, #e3bc81, #e3bc81 22px, #965c00 22px, #965c00 26px, | |
#e3bc81 26px, #e3bc81 48px, #965c00 48px, #965c00 52px, | |
#e3bc81 52px, #e3bc81 74px, #965c00 74px, #965c00 78px, | |
#e3bc81 78px, #e3bc81); | |
border-bottom: solid 1px #965c00; | |
content: ''; | |
height: 32px; | |
width: 100px; | |
} | |
.part-10::after { | |
margin-left: auto; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import './BonusLevel.css'; | |
import { ReactSortable } from "react-sortablejs"; | |
const bonusLevel = props => ( | |
<div className="bonus-container"> | |
<ReactSortable className="block-stack" list={props.parts} setList={props.setBonusParts}> | |
{props.parts.map(part => <div key={part} className={`strip part-${part}`}></div>)} | |
</ReactSortable> | |
</div> | |
); | |
export default bonusLevel; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const capstone = () => <div className="capstone"></div>; | |
export default capstone; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
const displayMessage = props => <strong className="message">{props.message}</strong>; | |
export default displayMessage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
const levels = props => { | |
const data = props.levelData; | |
return ( | |
<div className="level-container" onClick={props.handleSelectLevelFn}> | |
<div className="row"> | |
<div className="level"> | |
{data['1'].locked ? <div className="locked"></div> : <p className={`playable${data['1'].star ? ' star' : ''}`}>{Object.keys(data)[0]}</p>} | |
</div> | |
</div> | |
<div className="row"> | |
<div className="level"> | |
{data['2'].locked ? <div className="locked"></div> : <p className={`playable${data['2'].star ? ' star' : ''}`}>{Object.keys(data)[1]}</p>} | |
</div> | |
<div className="level"> | |
{data['3'].locked ? <div className="locked"></div> : <p className={`playable${data['3'].star ? ' star' : ''}`}>{Object.keys(data)[2]}</p>} | |
</div> | |
</div> | |
<div className="row"> | |
<div className="level"> | |
{data['4'].locked ? <div className="locked"></div> : <p className={`playable${data['4'].star ? ' star' : ''}`}>{Object.keys(data)[3]}</p>} | |
</div> | |
<div className="level"> | |
{data['5'].locked ? <div className="locked"></div> : <p className={`playable${data['5'].star ? ' star' : ''}`}>{Object.keys(data)[4]}</p>} | |
</div> | |
<div className="level"> | |
{data['6'].locked ? <div className="locked"></div> : <p className={`playable${data['6'].star ? ' star' : ''}`}>{Object.keys(data)[5]}</p>} | |
</div> | |
</div> | |
<div className={`bonus-level${!data['bonus'].locked ? ' unlocked' : ''}`}> | |
<p className={`${!data['bonus'].locked ? 'playable' : ''}${data['bonus'].star ? ' star' : ''}`}>{data['bonus'].locked && <span className="locked"></span>}Bonus Level</p> | |
</div> | |
<div className="difficulty" onClick={props.handleSetDifficultyFn}> | |
<h2>Difficulty:</h2> | |
<button type="button" {...(props.difficulty === 'easy' && {className: "selected"})} data-difficulty="easy">Easy</button> | |
<button type="button" {...(props.difficulty === 'normal' && {className: "selected"})} data-difficulty="normal">Normal</button> | |
</div> | |
</div> | |
); | |
}; | |
export default levels; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import { ReactSortable } from "react-sortablejs"; | |
import BlockLine from "./BlockLine"; | |
import Capstone from "./Capstone"; | |
const pyramid = props => ( | |
<div className="pyramid-container"> | |
{props.isSolved && <Capstone />} | |
<ReactSortable className="block-stack" list={props.blockWidths} setList={props.setBlockWidths}> | |
{props.blockWidths.map(width => <BlockLine key={width} width={width} />)} | |
</ReactSortable> | |
</div> | |
); | |
export default pyramid; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
const timeIndicator = props => <progress className={`time-indicator${props.timeClass}`} max="100" value={props.value}></progress>; | |
export default timeIndicator; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment