diff --git a/CHANGELOG.md b/CHANGELOG.md index fab461ad..9be80477 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [1.6.3] - 2019-10-23 + +### Added +- MNNI: the MNML guide + - Delivers the message of the day + +### Fixed +- Fixed issue where dots / hots would not trigger when reapplied at a higher speed +- Changed layout of home page. UI elements for rerolling construct avatars moved to a separate tab. +- Added highlighting to first round of a game against the bots to guide new users +- Fixed UI issues related to scrolling +- Improved the invite link + ## [1.6.2] - 2019-10-20 ### Fixed - Combiner bug where it would preview items for different combinations diff --git a/VERSION b/VERSION index 308b6faa..f5d2a585 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.2 \ No newline at end of file +1.6.3 \ No newline at end of file diff --git a/WORKLOG.md b/WORKLOG.md index e5d75a31..174a7073 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -3,29 +3,20 @@ *PRODUCTION* -* rename vbox to shop -* give the shop and inventory distinct delineation -* proper victory / lose page instead of just face off (you are the winner or something) +* vbox phase skill list navigator (overlay maybe?) +* can't reset password without knowing password =\ * mobile styles * mobile info page - * fix info page for tablet layout -* can't reset password without knowing password =\ * Invert recharge - * serde serialize privatise - * chat - -* Convert spec 'Plus' -> '+' when it appears as combo text in combiner and in info text - ## SOON * equip from shop (buy and equip without putting in your inventory) for bases * move item from one construct to another -* bot game grind * ACP * essential * audio @@ -38,7 +29,7 @@ * remove names so games/instances are copy *$$$* - * chatwheel + * instead of red noise, red and black bar gradient * eth adapter * illusions * vaporwave @@ -65,6 +56,7 @@ ## LATER * constants +* bot game grind $$$ * Items diff --git a/acp/package.json b/acp/package.json index 50e3dad7..b0f4f97b 100644 --- a/acp/package.json +++ b/acp/package.json @@ -1,6 +1,6 @@ { "name": "mnml-client", - "version": "1.6.2", + "version": "1.6.3", "description": "", "main": "index.js", "scripts": { diff --git a/client/assets/assets/molecules/726.svg b/client/assets/assets/molecules/726.svg deleted file mode 100644 index 3d18b0c0..00000000 --- a/client/assets/assets/molecules/726.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/assets/mnni.svg b/client/assets/mnni.svg new file mode 100644 index 00000000..3b4b744b --- /dev/null +++ b/client/assets/mnni.svg @@ -0,0 +1,2557 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/client/assets/styles/colours.less b/client/assets/styles/colours.less index b6362c6b..79088b7c 100644 --- a/client/assets/styles/colours.less +++ b/client/assets/styles/colours.less @@ -131,6 +131,15 @@ svg { } } +@keyframes co-text { + from { + color: @black; + } + to { + color: @gray-exists; + } +} + button { &.blue { border-color: @blue; diff --git a/client/assets/styles/controls.less b/client/assets/styles/controls.less index c154e044..b465938d 100644 --- a/client/assets/styles/controls.less +++ b/client/assets/styles/controls.less @@ -50,31 +50,6 @@ aside { } } - // button.ready:enabled { - // &:hover { - // color: forestgreen; - // border-color: forestgreen; - // } - - // &:active, &:focus, &.enabled { - // background: forestgreen; - // color: black; - // border-color: forestgreen; - // } - // } - - button.ready:enabled { - color: forestgreen; - border-color: forestgreen; - - &:hover { - background: forestgreen; - color: black; - border-color: forestgreen; - } - } - - .timer-container { grid-area: timer; @@ -144,12 +119,6 @@ aside { } } -.play-ctrl { - .controls { - grid-template-rows: 1fr 1fr 1fr 3fr 1fr; - } -} - .abandon:not([disabled]) { &:hover { color: @red; diff --git a/client/assets/styles/game.less b/client/assets/styles/game.less index ef445028..f85ca3cf 100644 --- a/client/assets/styles/game.less +++ b/client/assets/styles/game.less @@ -235,6 +235,9 @@ width: 100%; height: 100%; position: absolute; + overflow: hidden; + max-height: 100%; + max-width: 100%; } .combat-anim svg { @@ -339,6 +342,9 @@ .skill-animation { opacity: 0; stroke-width: 5px; + overflow: hidden; + max-height: 100%; + max-width: 100%; // height: 5em; } diff --git a/client/assets/styles/instance.less b/client/assets/styles/instance.less index 05952fc7..461f8eb0 100644 --- a/client/assets/styles/instance.less +++ b/client/assets/styles/instance.less @@ -97,8 +97,7 @@ grid-template-columns: 1fr min-content 1fr; grid-template-areas: "vbox varrow inventory" - "vbox . carrow" - "vbox . combiner"; + "vbox varrow combiner"; } .vbox-inventory { @@ -112,15 +111,6 @@ justify-content: flex-end; } -.vbox-combiner-arrow { - color: @gray-hint; - grid-area: carrow; - display: block; - text-align: center; - font-size: 2em; - vertical-align: center; -} - .vbox-arrow, .vbox-arrow-mobile { display: flex; justify-content:center; @@ -413,9 +403,10 @@ text-align: center; overflow: hidden; display: grid; - grid-template-rows: 1fr 1.5fr; + grid-template-rows: 1fr 0.5fr 1.5fr; grid-template-areas: "opponent" + "text" "player"; h1 { @@ -428,14 +419,9 @@ margin-top: 1em; } - .opponent-name { - margin-bottom: 1em; - grid-area: oppname; - } - - .player-name { - margin-top: 1em; - grid-area: playername; + .winner { + color: @yellow; + font-weight: bold; } .team { @@ -445,6 +431,39 @@ } } +.faceoff-text { + grid-area: text; + font-size: 200%; + text-transform: uppercase; + letter-spacing: 1em; + font-weight: bold; + + color: @black; + animation: faceoff 4s ease-in-out 0s 2 alternate; + + &.winner { + animation: win 2s ease-in-out 0s 1; + } +} + +@keyframes faceoff { + from { + color: @black; + } + to { + color: @white; + } +} + +@keyframes win { + from { + color: @black; + } + to { + color: @yellow; + } +} + /* Mobile Nav*/ .instance-nav { display: none; } diff --git a/client/assets/styles/menu.less b/client/assets/styles/menu.less index 4d0dd7ca..0fbb7f90 100644 --- a/client/assets/styles/menu.less +++ b/client/assets/styles/menu.less @@ -35,6 +35,7 @@ .construct { flex: 1 1 33%; + overflow: hidden; display: flex; flex-flow: column; @@ -108,13 +109,40 @@ section { letter-spacing: 0.25em; text-transform: uppercase; display: grid; - grid-template-columns: repeat(4, 1fr); + // grid-template-columns: repeat(4, 1fr); + grid-template-columns: 1fr 1fr; grid-gap: 1em; flex-flow: row wrap; align-items: flex-end; button { border-radius: 0.25em; + // height: 3em; } + + &.play { + grid-template-columns: repeat(2, 1fr); + align-items: flex-start; + + &.rejoin { + grid-template-columns: 1fr; + } + + button.ready:enabled { + color: forestgreen; + border-color: forestgreen; + + &:hover { + background: forestgreen; + color: black; + border-color: forestgreen; + } + } + } + } + + .panes { + display: grid; + grid-template-columns: repeat(2, 1fr); } figure { @@ -188,7 +216,12 @@ section { .list { grid-template-columns: 1fr 1fr; + + &.play { + grid-template-columns: 1fr; + } } + } .menu .team { diff --git a/client/assets/styles/player.less b/client/assets/styles/player.less index 4eb448d9..616ba46f 100644 --- a/client/assets/styles/player.less +++ b/client/assets/styles/player.less @@ -2,6 +2,7 @@ .player-box { display: grid; + overflow: hidden; grid-template-areas: "msg" "img" @@ -45,6 +46,11 @@ grid-area: msg; color: @white; } + + &.winner { + color: @yellow; + font-weight: bold; + } } .chat { diff --git a/client/assets/styles/styles.less b/client/assets/styles/styles.less index 1cb6f712..e8188302 100644 --- a/client/assets/styles/styles.less +++ b/client/assets/styles/styles.less @@ -26,7 +26,7 @@ html body { /* stops inspector going skitz*/ overflow-x: hidden; - overflow-y: hidden; + // overflow-y: hidden; } // @media (min-width: 1921px) { @@ -281,7 +281,7 @@ ul { } li { - margin-bottom: 0.5em; + margin-bottom: 0; } .logo { @@ -299,6 +299,10 @@ li { background-position: center; } +.mnni { + background-image: url("./../mnni.svg"); +} + .avatar { grid-area: avatar; object-fit: contain; diff --git a/client/package.json b/client/package.json index 4bd0df9e..c2b00a0b 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "mnml-client", - "version": "1.6.2", + "version": "1.6.3", "description": "", "main": "index.js", "scripts": { @@ -15,9 +15,9 @@ "license": "UNLICENSED", "dependencies": { "anime": "^0.1.2", - "animejs": "^3.0.1", - "async": "^2.6.2", - "borc": "^2.0.3", + "animejs": "^3.1.0", + "async": "^2.6.3", + "borc": "^2.1.1", "docco": "^0.7.0", "hammerjs": "^2.0.8", "izitoast": "^1.4.0", @@ -26,15 +26,15 @@ "lodash": "^4.17.15", "logrocket": "^1.0.3", "node-sass": "^4.12.0", - "parcel": "^1.12.3", - "preact": "^8.4.2", + "parcel": "^1.12.4", + "preact": "^8.5.2", "preact-compat": "^3.19.0", - "preact-context": "^1.1.3", + "preact-context": "^1.1.4", "preact-redux": "^2.1.0", "query-string": "^6.8.3", "react-string-replace": "^0.4.4", - "react-stripe-elements": "^3.0.0", - "redux": "^4.0.0" + "react-stripe-elements": "^3.0.1", + "redux": "^4.0.4" }, "devDependencies": { "babel-core": "^6.26.3", @@ -42,11 +42,11 @@ "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "eslint": "^5.16.0", - "eslint-config-airbnb-base": "^13.1.0", - "eslint-plugin-import": "^2.14.0", - "eslint-plugin-react": "^7.11.1", - "jest": "^18.0.0", - "less": "^3.9.0" + "eslint-config-airbnb-base": "^13.2.0", + "eslint-plugin-import": "^2.18.2", + "eslint-plugin-react": "^7.16.0", + "jest": "^25.0.0", + "less": "^3.10.3" }, "alias": { "react": "preact-compat", diff --git a/client/src/components/account.top.jsx b/client/src/components/account.top.jsx index 351918b4..de983dd7 100644 --- a/client/src/components/account.top.jsx +++ b/client/src/components/account.top.jsx @@ -187,6 +187,7 @@ class AccountStatus extends Component { class="login-input" type="password" name="new" + autocomplete="new-password" value={passwordState.password} onInput={linkState(this, 'passwordState.password')} placeholder="new password" @@ -195,6 +196,7 @@ class AccountStatus extends Component { class="login-input" type="password" name="confirm" + autocomplete="new-password" value={passwordState.confirm} onInput={linkState(this, 'passwordState.confirm')} placeholder="confirm" diff --git a/client/src/components/controls.jsx b/client/src/components/controls.jsx index 8df3d524..e7b4e639 100644 --- a/client/src/components/controls.jsx +++ b/client/src/components/controls.jsx @@ -38,7 +38,7 @@ function Controls(args) { if (game) return ; if (instance) return ; - if (nav === 'play' || nav === 'shop' || !nav) return + if (nav === 'play' || nav === 'shop' || nav === 'reshape' || !nav) return if (nav === 'team' || nav === 'account') return return false; diff --git a/client/src/components/faceoff.jsx b/client/src/components/faceoff.jsx index 68de5481..251f10e6 100644 --- a/client/src/components/faceoff.jsx +++ b/client/src/components/faceoff.jsx @@ -58,7 +58,8 @@ function Faceoff(props) { const constructs = team.constructs.map((c, i) => ); - const classes = `team player ${team.ready ? 'ready' : ''}` + const winner = instance.winner === team.id; + const classes = `team player ${winner ? 'winner' : team.ready ? 'ready' : ''}` return (
{constructs} @@ -66,11 +67,13 @@ function Faceoff(props) { ); } + function OpponentTeam(team) { const constructs = team.constructs.map((c, i) => ); - const classes = `team opponent ${team.ready ? 'ready' : ''}` + const winner = instance.winner === team.id; + const classes = `team opponent ${winner ? 'winner' : team.ready ? 'ready' : ''}` return (
@@ -78,10 +81,30 @@ function Faceoff(props) {
); } + function faceoffText() { + if (!instance.winner) { + return ( +
+
{otherTeam.name}
+
vs
+
{playerTeam.name}
+
+ ); + } + const winner = instance.winner === playerTeam.id ? playerTeam : otherTeam; + return ( +
+
{winner.name}
+
wins
+
+ ) + + } return (
{OpponentTeam(otherTeam)} + {faceoffText()} {PlayerTeam(playerTeam)}
); diff --git a/client/src/components/header.jsx b/client/src/components/header.jsx index 9bb8d9e0..2ce0368f 100644 --- a/client/src/components/header.jsx +++ b/client/src/components/header.jsx @@ -40,7 +40,7 @@ const addState = connect( dispatch(actions.setItemEquip(null)); dispatch(actions.setItemUnequip([])); dispatch(actions.setVboxHighlight([])); - + dispatch(actions.setMtxActive(null)); return dispatch(actions.setNav(place)); } @@ -88,6 +88,11 @@ function Header(args) { class={`login-btn ${nav === 'shop' ? 'highlight' : ''}`}> Shop + - ); - - if (instances.length) { - return ( - - ); - } - - const inviteBtn = () => { - if (!invite) { - return ( - - ); - } - - function copyClick(e) { - const link = `${document.location.origin}#join=${invite}`; - const textArea = document.createElement('textarea', { id: '#clipboard' }); - textArea.value = link; - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - - try { - document.execCommand('copy'); - infoToast('Invite link copied to clipboard.'); - } catch (err) { - console.error('link copy error', err); - errorToast('Invite link copy error.'); - } - - document.body.removeChild(textArea); - return true; - } - - return ( - - ); - }; - return ( ); } -module.exports = addState(JoinButtons); +module.exports = JoinButtons; diff --git a/client/src/components/play.jsx b/client/src/components/play.jsx index e08223bc..dc7a1720 100644 --- a/client/src/components/play.jsx +++ b/client/src/components/play.jsx @@ -1,12 +1,8 @@ // const { connect } = require('preact-redux'); const preact = require('preact'); const { connect } = require('preact-redux'); -const { Elements } = require('react-stripe-elements'); - -const Header = require('./header'); -const Team = require('./team'); -const StripeBtns = require('./stripe.buttons'); +const { errorToast, infoToast } = require('../utils'); const actions = require('./../actions'); const VERSION = process.env.npm_package_version; @@ -16,17 +12,35 @@ const addState = connect( const { ws, account, - shop, + instances, + invite, } = state; - function mtxBuy(mtx) { - return ws.sendMtxBuy(mtx.variant); + function sendInstanceState(id) { + ws.sendInstanceState(id); + } + + function sendInstancePractice() { + ws.sendInstancePractice(); + } + + function sendInstanceQueue() { + ws.sendInstanceQueue(); + } + + function sendInstanceInvite() { + ws.sendInstanceInvite(); } return { account, - shop, - mtxBuy, + instances, + invite, + + sendInstanceState, + sendInstanceQueue, + sendInstancePractice, + sendInstanceInvite, }; }, @@ -51,32 +65,66 @@ const addState = connect( function Play(args) { const { account, - shop, - mtxBuy, + instances, + invite, + + sendInstanceState, + sendInstanceQueue, + sendInstancePractice, + sendInstanceInvite, - setMtxActive, setNav, } = args; - if (!shop) return false; + const inviteBtn = () => { + if (!invite) { + return ( +
+ +
Invite a Friend
+
+ + ); + } + + function copyClick(e) { + const link = `${document.location.origin}#join=${invite}`; + const textArea = document.createElement('textarea', { id: '#clipboard' }); + textArea.value = link; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + document.execCommand('copy'); + infoToast('Invite link copied to clipboard.'); + } catch (err) { + console.error('link copy error', err); + errorToast('Invite link copy error.'); + } + + document.body.removeChild(textArea); + return true; + } - const useMtx = (item, i) => { - const price = item === 'Rename' ? 5 : 1; return ( -
setMtxActive(item)} > -
{item}
- +
+ +
Invite Generated
); }; - const availableMtx = (item, i) => ( -
-
Enable {item.variant}
- -
- ); - const subscription = account.subscribed ? +
Matchmaking
+
+ {inviteBtn()} +
+ +
Practice MNML
+
+
+ +
Join the Community
+
+
+ ); + + return ( +
+
+ +
Resume playing
+
+
+ ); + } + return (
-

v{VERSION}

-

Use the buttons on the right to join an instance.

-

- Select PVP to play against other players.
- Select INVITE then click COPY LINK to generate an instance invitation for a friend.
- Click LEARN to practice the game without time controls. -

-

Join our Discord server to find opponents, message @ntr or @mashy for some credits to get started.

-

- If you enjoy the game please support its development by subscribing or purchasing credits.
- glhf -

+

v{VERSION}

+ {list()}

¤ {account.balance}

@@ -116,13 +199,18 @@ function Play(args) { role="link"> Get Credits -
-
- {shop.owned.map(useMtx)} +
+ Join our Discord server to find opponents and talk to the devs.
+ Message @ntr or @mashy for some credits to get started.
-
- {shop.available.map(availableMtx)} +
+
+ If you enjoy the game you can support the development: +
    +
  • Invite people to play pvp games and grow the community.
  • +
  • Subscribe or purchase credits.
  • +
diff --git a/client/src/components/player.box.jsx b/client/src/components/player.box.jsx index ec0527e5..29ee6532 100644 --- a/client/src/components/player.box.jsx +++ b/client/src/components/player.box.jsx @@ -67,9 +67,11 @@ function Scoreboard(args) { return ''; }; + const winner = player.score === 'Win'; + if (!isPlayer) { return ( -
+
{scoreText()}
{player.name}
@@ -80,7 +82,7 @@ function Scoreboard(args) { } return ( -
+
{chat || '\u00A0'}
{scoreText()}
{player.name}
diff --git a/client/src/components/reshape.jsx b/client/src/components/reshape.jsx new file mode 100644 index 00000000..d45bd4d4 --- /dev/null +++ b/client/src/components/reshape.jsx @@ -0,0 +1,129 @@ +// const { connect } = require('preact-redux'); +const preact = require('preact'); +const { connect } = require('preact-redux'); + +const actions = require('./../actions'); + +const addState = connect( + function receiveState(state) { + const { + ws, + account, + shop, + } = state; + + function mtxBuy(mtx) { + return ws.sendMtxBuy(mtx.variant); + } + + return { + account, + shop, + mtxBuy, + }; + }, + + function receiveDispatch(dispatch) { + function setMtxActive(mtx) { + dispatch(actions.setConstructRename(null)); + dispatch(actions.setMtxActive(mtx)); + return true; + } + + function setNav(place) { + return dispatch(actions.setNav(place)); + } + + return { + setMtxActive, + setNav, + }; + } +); + +function Reshape(args) { + const { + account, + shop, + mtxBuy, + + setMtxActive, + setNav, + } = args; + + if (!shop) return false; + + const useMtx = (item, i) => { + const price = item === 'Rename' ? 5 : 1; + return ( +
{ + e.stopPropagation(); + setMtxActive(item); + }}> +
{item}
+ +
+ ); + }; + + const availableMtx = (item, i) => ( +
+
Enable {item.variant}
+ +
+ ); + + const subscription = account.subscribed + ? + : ; + + return ( +
setMtxActive(null)}> +
+

Use credits to modify your construct names and appearance.

+
    + +
  • Purchase image sets to unlock different types of avatars.
  • +
  • You can reroll any avatar to a new avatar from owned sets.
  • +
  • Reroll avatars by clicking the owned set and then the construct you wish to reroll.
  • +
  • Press escape to clear any active mtx.
  • +
+ +

+ You can switch out your active constructs in the account settings.
+ Accounts start with 4 constructs by default.
+

+
+
+

¤ {account.balance}

+
+ {subscription} + +
+
+
+ {shop.owned.map(useMtx)} +
+
+ {shop.available.map(availableMtx)} +
+
+
+ ); +} + +module.exports = addState(Reshape); diff --git a/client/src/components/scoreboard.jsx b/client/src/components/scoreboard.jsx deleted file mode 100644 index ee2e9e1d..00000000 --- a/client/src/components/scoreboard.jsx +++ /dev/null @@ -1,52 +0,0 @@ -const preact = require('preact'); -const { connect } = require('preact-redux'); - -const addState = connect( - function receiveState(state) { - const { - ws, - instance - } = state; - - return { instance }; - }, -); - -function ScoreBoard(args) { - const { - instance, - } = args; - - const players = instance.players.map((p, i) => { - if (instance.phase === 'Finished') { - const winner = p.wins > instance.max_rounds / 2; - return - {p.name} - {p.wins} / {p.losses} - {winner ? 'winner' : ''} - - } - - const text = instance.phase === 'Finished' - ? p.wins > instance.rounds / 2 && 'Winner' - : ''; - - return - {p.name} - {p.wins} / {p.losses} - {p.ready ? 'ready' : ''} - - }); - - return ( - - - {players} - -
- ); -} - -module.exports = addState(ScoreBoard); diff --git a/client/src/components/shop.jsx b/client/src/components/shop.jsx index 5b36e0c4..08a79fad 100644 --- a/client/src/components/shop.jsx +++ b/client/src/components/shop.jsx @@ -41,7 +41,8 @@ function Shop(args) { Subscriptions grant extra benefits:
  • ¤150 per month
  • -
  • More community features in the future including account icons and chat wheel
  • +
  • Account img
  • +
  • Chat wheel

diff --git a/client/src/components/vbox.component.jsx b/client/src/components/vbox.component.jsx index 7e56bafb..266ae0b2 100644 --- a/client/src/components/vbox.component.jsx +++ b/client/src/components/vbox.component.jsx @@ -97,7 +97,7 @@ function Vbox(args) { const { combiner, navInstance, - // instance, + instance, itemInfo, player, reclaiming, @@ -161,11 +161,20 @@ function Vbox(args) { function availableBtn(v, group, index) { if (!v) return ; + const tutorial = instance.time_control === 'Practice' + && instance.rounds.length === 1 + && group === 0 + && combiner.length === 0 + && vboxSelected.length === 0 + && vbox.bits > 10 + && vbox.free[0].filter(c => c).length > 4 + ? 'combo-border' : null; const selected = vboxSelected[0] === group && vboxSelected[1] === index; // state not yet set in double click handler function onDblClick(e) { + clearVboxSelected(); sendVboxAccept(group, index); e.stopPropagation(); } @@ -194,7 +203,7 @@ function Vbox(args) { } return false; }) ? 'combo-border' : ''; - const classes = `${v.toLowerCase()} ${selected ? 'highlight' : ''} ${comboHighlight}`; + const classes = `${v.toLowerCase()} ${selected ? 'highlight' : ''} ${comboHighlight} ${tutorial}`; if (shapes[v]) { return ( @@ -267,6 +276,16 @@ function Vbox(args) { return ; } + const tutorial = instance.time_control === 'Practice' + && instance.rounds.length === 1 + && i === 0 + && combiner.length === 0 + && vboxSelected.length === 0 + && vbox.bits === 16 + && vbox.bound.length === 5 + && vbox.free[0].filter(c => c).length === 4 + ? 'combo-border' : null; + const combinerItems = combiner.map(j => vbox.bound[j]); const combinerCount = countBy(combinerItems, co => co); @@ -306,7 +325,7 @@ function Vbox(args) { const highlighted = combiner.indexOf(i) > -1; const border = buttons[removeTier(v)] ? buttons[removeTier(v)]() : ''; - const classes = `${highlighted ? 'highlight' : border} ${comboHighlight}`; + const classes = `${highlighted ? 'highlight' : border} ${comboHighlight} ${tutorial}`; if (shapes[v]) { return (