') 100% no-repeat;background-color:#fff;box-shadow:0 .2rem .2rem .1rem hsla(0,0%,40%,.5);cursor:pointer;margin-bottom:1rem;padding-right:3.2rem}select:focus{border-color:#4c4355;box-shadow:0 0 .2rem .2rem rgba(76,76,76,.3),0 .2rem .2rem .1rem hsla(0,0%,40%,.5)!important}textarea{min-height:6.4rem}label,legend{color:#332c39;display:block;font-weight:600}fieldset{border-width:0;padding:0}input[type=checkbox],input[type=radio]{cursor:pointer;display:inline;margin-bottom:0}input[type=checkbox]:disabled,input[type=radio]:disabled{cursor:default}input[type=checkbox]+span,input[type=radio]+span{cursor:pointer;font-weight:400;width:100%}input[type=checkbox]:disabled+span,input[type=radio]:disabled+span,span.toggleLabel+input[type=checkbox]:disabled{color:#616161;cursor:default!important}.label-inline{color:#19161c;display:inline-block}.label-inline+input{display:inline-block;margin-left:1.2rem;margin-right:1.2rem;width:unset}label>input[type=checkbox]{align-content:center;align-items:center;display:inline-flex}@media (min-width:680px){input[type=checkbox]+span:before{height:1.4rem;width:1.4rem}}span input[type=checkbox]:disabled+{cursor:default!important}input[type=checkbox]{opacity:0;position:absolute}input[type=checkbox]+span{cursor:pointer;line-height:2.3rem;padding-left:2.8rem;position:relative}input[type=checkbox]:active+span:before{background-color:#ccc}input[type=checkbox]+span:before{background-color:#f5f4f2;border:.1rem solid #665972;border-radius:.1rem;bottom:.3rem;display:inline-block;height:1.7rem;left:0;margin-right:.8rem;vertical-align:text-top;width:1.7rem}input[type=checkbox]+span:after,input[type=checkbox]+span:before{content:"";position:absolute;transition:all .05s;user-select:none}input[type=checkbox]+span:after{bottom:1.1rem;box-shadow:.2rem 0 0 #f5f4f2,.4rem 0 0 #f5f4f2,.4rem -.6rem 0 #f5f4f2,.4rem -.8rem 0 #f5f4f2;height:.3rem;left:.2rem;transform:rotate(90deg) scale(0);transform-origin:5% 85%;width:.3rem}input[type=checkbox]:checked+span:after{background-color:#8c3e34;box-shadow:.2rem 0 0 #8c3e34,.4rem 0 0 #8c3e34,.4rem -.2rem 0 #8c3e34,.4rem -.4rem 0 #8c3e34,.4rem -.6rem 0 #8c3e34,.4rem -.8rem 0 #8c3e34;content:"";transform:rotate(45deg) scale(1)}input[type=checkbox]:indeterminate+span:after{background-color:#8c3e34;bottom:1.1rem;box-shadow:unset;height:1.2rem;left:.4rem;transform:rotate(90deg) scale(1);width:.3rem}input[type=checkbox]:focus+span:before{border-color:#4c4355;border-width:.2rem;box-shadow:0 0 .2rem .2rem rgba(76,76,76,.3)!important}input[type=checkbox]:disabled+span:before{background:#8c8c8c;border:.1rem solid #8c8c8c;box-shadow:none}input[type=checkbox]:disabled+span{cursor:default!important}input[type=checkbox]:disabled:checked+span:before{cursor:default}input[type=checkbox]:disabled:checked+span:after{background:#ccc;box-shadow:.2rem 0 0 #ccc,.4rem 0 0 #ccc,.4rem -.2rem 0 #ccc,.4rem -.4rem 0 #ccc,.4rem -.6rem 0 #ccc,.4rem -.8rem 0 #ccc;cursor:default}input[type=checkbox]:disabled:indeterminate+span:after{background-color:#ccc}input[type=checkbox].toggle-switch{opacity:0;position:absolute}input[type=checkbox].toggle-switch+span:after,input[type=checkbox].toggle-switch+span:before{box-shadow:none;margin:0 .8rem 0 0;transform:unset;vertical-align:unset}input[type=checkbox].toggle-switch+span{align-items:baseline;display:inline-flex;height:2.3rem;margin:0;padding-left:4.8rem;vertical-align:text-bottom}input[type=checkbox].toggle-switch+span:before{background:gray;border:none;border-radius:1.6rem;content:"";display:inline-block;height:1.6rem;width:3.2rem}input[type=checkbox].toggle-switch+span:after{background-color:#f5f4f2;border-radius:50%;bottom:.5rem;content:"";display:inline-block;height:1.2rem;left:.2rem;transition:left .14s cubic-bezier(.32,0,.67,0);width:1.2rem}input[type=checkbox].toggle-switch:active+span:before,input[type=checkbox].toggle-switch:focus+span:before{border-color:#998ca5;box-shadow:0 0 .2rem .2rem rgba(140,126,154,.6)!important}input[type=checkbox].toggle-switch:disabled+span:after{background:#ccc!important}input[type=checkbox].toggle-switch:disabled+span:before{background:#8c8c8c!important;box-shadow:none!important}input[type=checkbox].toggle-switch:checked+span:after,input[type=checkbox].toggle-switch:checked+span:before{box-shadow:none;transform:unset}input[type=checkbox].toggle-switch:checked+span:after{background-color:#f5f4f2;display:inline-block;left:1.7rem}input[type=checkbox].toggle-switch:checked+span:before{background:#b3675c!important}input[type=checkbox].toggle-switch:checked:disabled+span:after{background:#ccc}input[type=checkbox].toggle-switch:checked:disabled+span:before{background:#ababab!important}.toggleLabel{color:#19161c;position:relative;top:.2rem}.radio-inline,.toggleLabelAfter,.toggleLabelBefore{margin-right:.8rem}.radio-inline{display:inline-block;min-width:9.2rem}input[type=radio]:checked,input[type=radio]:not(:checked){left:-9999px;position:absolute}input[type=radio]:checked+span:before{background-color:#f5f4f2;border:.1rem solid #665972;border-radius:100%;content:"";height:1.6rem;left:0;position:absolute;top:.15rem;width:1.6rem}input[type=radio]:active+span:before{background-color:#ababab;border:none;box-shadow:none}input[type=radio]:focus:checked+span:before{border:.1rem solid #4c4355;box-shadow:0 0 .2rem .2rem rgba(76,67,85,.6)}input[type=radio]:checked+span,input[type=radio]:not(:checked)+span{cursor:pointer;display:inline-block;line-height:2.2rem;padding-left:2.4rem;position:relative}input[type=radio]:checked:disabled+span:before{border:.1rem solid #8c8c8c;cursor:default}input[type=radio]:not(:checked)+span:before{background-color:#f5f4f2;border:.1rem solid #665972;border-radius:100%;content:"";height:1.6rem;left:0;position:absolute;top:.15rem;width:1.6rem}input[type=radio]:not(:checked):active+span:before{background-color:#ababab;border:none;box-shadow:none}input[type=radio]:not(:checked):focus+span:before{border:.1rem solid #4c4355;box-shadow:0 0 .2rem .2rem rgba(76,67,85,.6)}input[type=radio]:not(:checked):disabled+span:before{background-color:#8c8c8c;border:none;cursor:default}input[type=radio]:checked+span:after,input[type=radio]:not(:checked)+span:after{background:#8c3e34;border-radius:100%;content:"";height:.6rem;left:.5rem;position:absolute;top:.65rem;transform:scale(1);transition:transform .14s;width:.6rem}input[type=radio]:not(:checked)+span:after{transform:scale(0);transition:transform .5s cubic-bezier(.33,1,.68,1)}input[type=radio]:checked:disabled+span:before{background:0 0}input[type=radio]:checked:disabled+span:after{background:#8c8c8c;cursor:default}input[type=radio]:disabled+span{color:#616161;cursor:default}input[type=radio]:checked+span:after{opacity:1;transform:scale(1)}input[type=range]{-webkit-appearance:none;background:0 0;margin-top:1.6rem;width:100%}input[type=range]:focus{background-color:initial;outline:0}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;background:#fff;border:.1rem solid #8a8a8a;border-radius:45%;box-shadow:0 .2rem .2rem .1rem hsla(0,0%,40%,.5);cursor:pointer;height:2rem;margin-top:-.8rem;outline:0;width:2rem}input[type=range]:focus::-webkit-slider-thumb{border:.1rem solid #4c2c6c;box-shadow:0 0 .2rem .2rem rgba(76,67,85,.6),0 .2rem .2rem .1rem hsla(0,0%,40%,.5)!important}input[type=range]:disabled::-webkit-slider-thumb{background-color:#8c8c8c;border:.1rem solid #ccc;box-shadow:none;cursor:default}input[type=range]::-moz-range-thumb{background:#fff;border:.1rem solid #8a8a8a;border-radius:45%;box-shadow:0 .2rem .2rem .1rem hsla(0,0%,40%,.5);cursor:pointer;height:2rem;margin-top:-.8rem;outline:0;width:2rem}input[type=range]:focus::-moz-range-thumb{border:.1rem solid #4c2c6c;box-shadow:0 0 .2rem .2rem rgba(76,67,85,.6),0 .2rem .2rem .1rem hsla(0,0%,40%,.5)!important}input[type=range]:disabled::-moz-range-thumb{background-color:#8c8c8c;border:.1rem solid #ccc;box-shadow:none;cursor:default}input[type=range]::-webkit-slider-runnable-track{background-color:#8a8a8a;border-radius:0;cursor:pointer;height:.6rem;outline:0;width:100%}input[type=range]:disabled::-webkit-slider-runnable-track{background-color:#ccc;border:.1rem solid #8c8c8c;cursor:default}input[type=range]::-moz-range-track{background-color:#8a8a8a;border-radius:0;cursor:pointer;height:.6rem;outline:0;width:100%}input[type=range]:disabled::-moz-range-track{background-color:#ccc;border:.1rem solid #8c8c8c;cursor:default}.container,.container-fw{margin:0 auto;padding:0 .8rem;position:relative;width:100%}.container-fw{max-width:100vw}.container{max-width:80rem}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.colGap1{column-gap:1.6rem}.row.colGap2{column-gap:3.2rem}.row.colGap3{column-gap:4.8rem}.row.colGap4{column-gap:6.4rem}.row.row-no-padding,.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;min-width:0;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{align-self:center}@media (min-width:680px){.container,.container-fw{padding:0 2.4rem}.row{flex-direction:row;margin-left:-1.2rem;width:calc(100% + 2.4rem)}.row .column{margin-bottom:inherit;padding:0 1.2rem}}dl,ol,ul{list-style:none;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{margin:.4rem 0 .8rem 3em}dl ul>li,ol ul>li,ul ul>li{list-style:circle outside}ol,ul{list-style:none;margin:.4rem 0 .8rem 3em;padding-left:0}ol ul>li{list-style:disc outside}ol{list-style:decimal outside}ul{list-style:disc outside}fieldset{padding:0!important}input[type=range]{padding-bottom:1.2rem}li{margin-bottom:.6rem}blockquote,dl,figure,ol,p,pre,ul{margin-bottom:1.2rem;margin-top:0}table{margin:2.4rem 0}@media (min-width:680px){li{margin-bottom:.4rem}}table{border-spacing:0;max-width:100%}thead{background-color:#ababab;color:#212121}td,th{border-bottom:.1rem solid #8a8a8a;line-height:1;padding:.8rem;text-align:left}td:first-child,th:first-child{padding-left:1.6rem}td:last-child,th:last-child{padding-right:1.6rem}th{border-bottom:0;font-weight:400}@media (min-width:680px){table{width:auto}}b,strong{font-weight:700}p{margin-top:0}h1,h2,h3,h4,h5,h6{color:#414141;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-weight:600;letter-spacing:-.1rem;line-height:1.2;margin:0 0 2.4rem}h1{font-size:3.6rem;font-weight:600;margin:3.2rem 0}h2{font-size:2.8rem;letter-spacing:0;line-height:1.25;margin:2rem 0}h3{font-size:1.8rem;font-weight:800}h3,h4{color:#0a0a0a;line-height:1.3}h4{font-size:1.6rem;text-decoration:underline}img{max-width:100%}figure{margin:0}video{display:block;margin:0 0 2.4rem;max-width:100%;outline:0}.clearfix:after{clear:both;content:" ";display:table}.float-left{float:left}.float-right{float:right}.cur-ptr{cursor:pointer}.disp-ib{display:inline-block!important}.disp-none{display:none!important}@media (max-width:679px){.show-sm-desktop{display:none!important}}@media (max-width:1279px){.show-lg-desktop{display:none!important}}@media (min-width:0px){.hide-mobile{display:none!important}}@media (min-width:680px){.hide-sm-desktop,.show-mobile{display:none!important}}@media (min-width:1280px){.hide-lg-desktop{display:none!important}}.pos-abs{position:absolute}.pl0{padding-left:0}.pl1{padding-left:.8rem}.pl2{padding-left:1.6rem}.pr0{padding-right:0}.pr1{padding-right:.8rem}.pr2{padding-right:1.6rem}.pb0{padding-bottom:0}.pb1{padding-bottom:.8rem}.pt0{padding-top:0}.pt1{padding-top:.8rem}.pt2{padding-top:1.6rem}.p0{padding:0}.p1{padding:.8rem}.p2{padding:1rem}.ml0{margin-left:0}.ml1{margin-left:.8rem}.mr0{margin-right:0}.mr1{margin-right:.8rem}.mr2{margin-right:1.6rem}.mt0{margin-top:0}.mt1{margin-top:.8rem}.mt2{margin-top:1.6rem}.mt3{margin-top:2.4rem}.mb0{margin-bottom:0}.mb1{margin-bottom:.8rem!important}.mb2{margin-bottom:1.6rem}.mb3{margin-bottom:2.4rem!important}.m0{margin:0}.mw100{max-width:100%}.m1{margin:.8rem}.m-center{margin:0 auto}.gpu-scrl{transform:translateZ(0)}.text-center{text-align:center}.text-right{text-align:right}.no-wrap{white-space:nowrap}.fsLarge{font-size:3rem}.fw-400{font-weight:400}.fw-500{font-weight:500}.fw-600{font-weight:600}.colorBlackLt{color:#4c4355}.ptrEvNone{pointer-events:none}.rootNavContainer{display:flex;flex-direction:column;margin-bottom:1.6rem}#root-nav{background:#ccc;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1.3rem;padding:1.2rem .4rem}#root-nav a{color:#8c3e34}#root-nav a:hover{color:#3e31a2}#root-nav ol{list-style:none;margin:0;padding-left:2.4rem}#root-nav ol li{display:inline-block;margin:0 .6rem 0 0;text-transform:capitalize}#root-nav ol li:first-child{margin-left:-2.4rem;padding-right:3.2rem;text-transform:none}#root-nav ol li:first-child:after,#root-nav ol li:last-child:after{content:""}#root-nav ol li:after{content:" /"}@media (min-width:680px){#root-nav{font-size:1.4rem}#root-nav ol li:first-child{padding-right:4rem}}a{color:#3e31a2;cursor:pointer;text-decoration:underline}a:focus,a:hover{color:#19161c}a:visited{color:#8c3e34}a:visited:focus,a:visited:hover{color:#3e31a2}body,html{height:100%}body{background-color:#ccc}.div__noscript{background-color:#8d47b3;color:#d0dc71;font-size:2.2rem}#container,#fakeContainer{background-color:#ccc;height:auto}.bg-c64-ltgreen{background-color:#acea88}.bg-c64-ltyellow{background-color:#d0dc71}.bg-c64-grey1{background-color:#ccc}.bg-c64-grey2{background-color:#ababab}.bg-c64-grey3{background-color:#8a8a8a}.bg-c64-ltblue{background-color:#7abfc7}.bg-c64-ltred{background-color:#bb776d}.bg-c64-green{background-color:#68a941}.bg-c64-ltpurple{background-color:#7c70da}.bg-c64-ltbrown{background-color:#905f25}.bg-c64-magenta{background-color:#8d47b3}.bg-c64-dkred{background-color:#8c3e34}.bg-c64-dkbrown{background-color:#574200}.bg-c64-dkpurple{background-color:#3e31a2}.colour-c64-ltgreen{color:#acea88}.colour-c64-ltyellow{color:#d0dc71}.colour-c64-grey1{color:#ccc}.colour-c64-grey2{color:#ababab}.colour-c64-grey3{color:#8a8a8a}.colour-c64-grey4{color:gray}.colour-c64-grey5{color:#676767}.colour-c64-grey6{color:#545454}.colour-c64-ltblue{color:#7abfc7}.colour-c64-ltred{color:#bb776d}.colour-c64-green{color:#68a941}.colour-c64-ltpurple{color:#7c70da}.colour-c64-ltbrown{color:#905f25}.colour-c64-magenta{color:#8d47b3}.colour-c64-dkred{color:#8c3e34}.colour-c64-dkbrown{color:#574200}.colour-c64-dkpurple{color:#3e31a2}
derekh.ca ( ) home wrote a solitaire clone 31 March 2020
Wrote A Solitaire Clone Play solitaire
Not too much to explain. Felt like playing around with an SVG animation library and Solitaire seemed like a good use. Used Surplus to render the UI, but bulk of the work leveraged svg.js .
Animation management turned out to be a complete pain. Took a few attempts before I could decouple the animation system from the core game state. Initially just treated the animation as a side effect of state changes, but this approach fizzled out for a few reasons. I couldn’t treat animations as pure side effects because of dependencies between related animations. I can’t animate a card moving from pile A to pile C, when it first needs to move from pile B to pile A.
SVG.js caused a few problems as well. The final transition coordinates were terrible due to floating precision. I would try to animate something from (0,0) to (50,5), but the final position would be ( 49.999937,5.0000023). If it was 1 card this probably wouldn’t be perceptible, but this would happen to a whole stack of cards and the pile would look wonky.
The only way I could fix it was to implement an end-of-animation system where I statically positioned the cards using Surplus.
Probably the most surprising thing was how awful SVG animation performance could be. The SVG card deck I used had some pretty complex designs and trying to animate several of these cards at once was not possible at 60fps. It might be an issue with linux graphical performance, but I eventually added support for highish-res PNG cards just so the animations could look acceptable.
I think it’s a general problem with SVG. Noticed a similar issue with Tetris on mobile.