[
  {
    "path": "LICENSE",
    "content": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <https://unlicense.org>\n"
  },
  {
    "path": "README.md",
    "content": "# Virtual Bookshelf\nThis is a simple visualisation of books on a bookshelf using some CSS transforms to give the effect of picking out the book when you hover over it.\n\nI use it on my [personal site](https://petargyurov.com) to track what books I've read. It integrates nicely with static site generators, and well, just about anything since it's all vanilla JS, CSS and HTML.\n\n![Example](https://i.imgur.com/6u0CySS.png)\n\n**How do I add more books?**\n\nA book is defined as follows:\n```html\n<div class=\"book\">\n    <div class=\"side spine\">\n        <span class=\"spine-title\"> Book Title </span>\n        <span class=\"spine-author\"> PG </span>\n    </div>\n    <div class=\"side top\"></div>\n    <div class=\"side cover\"></div>\n</div>\n```\n\nSimply add this snippet for each book you want inside `<div class=\"bookshelf\">`.\n\n**Why use JS at all?**\n\nI originally aimed for a no-JS implementation but there was no way of adding randomness to the book height, colours and patterns without it. You can of course remove the JS and implement that stuff manually.\n\n**Is this free to use?**\n\nYep, do whatever you want with it.\n\nIf you found this project useful you can make use of the following badge to spread the word:  \n[![Using Virtual Bookshelf](https://img.shields.io/badge/Extended%20with-Virtual%20Bookshelf-blue?logo=github)](https://github.com/petargyurov/virtual-bookshelf)\n\n**Is it perfect?**\n\nNope. Doesn't handle long titles well. I'm sure there are other alignment issues. I wrote this in a day, don't expect much. Feel free to submit fixes/improvements.\n"
  },
  {
    "path": "bookshelf.css",
    "content": ":root {\n    --spine-pyramid: linear-gradient(\n      315deg,\n      transparent 75%,\n      rgba(255, 255, 255, 0.1) 0\n      ),\n      linear-gradient(\n        45deg,\n         transparent 75%,\n          rgba(255, 255, 255, 0.1) 0\n      ),\n      linear-gradient(\n        135deg,\n        rgba(255, 255, 255, 0.2) 166px,\n        transparent 0\n      ),\n      linear-gradient(\n        45deg,\n        rgba(0, 0, 0, 0.1) 75%,\n        transparent 0\n      );\n      background-size: 20px 20px;\n    --spine-stairs: repeating-linear-gradient(\n        63deg,\n        rgba(255, 255, 255, 0.1),\n        rgba(255, 255, 255, 0.1) 1px,\n        transparent 3px,\n        transparent 0\n      ),\n      linear-gradient(\n        127deg,\n        rgba(255, 255, 255, 0.1),\n        rgba(255, 255, 255, 0.1) 90px,\n        transparent 55%,\n        transparent 0\n      ),\n      linear-gradient(\n        transparent 51%,\n        rgba(0, 0, 0, 0.1) 170px\n      );\n      background-size: 70px 120px;\n    --spine-argyle: repeating-linear-gradient(\n        120deg,\n        rgba(255, 255, 255, 0.1),\n        rgba(255, 255, 255, 0.1) 1px,\n        transparent 1px,\n        transparent 60px\n      ),\n      repeating-linear-gradient(\n        60deg,\n        rgba(255, 255, 255, 0.1),\n        rgba(255, 255, 255, 0.1) 1px,\n        transparent 1px,\n        transparent 60px\n      ),\n      linear-gradient(\n        60deg,\n        rgba(0, 0, 0, 0.1) 25%,\n        transparent 25%,\n        transparent 75%,\n        rgba(0, 0, 0, 0.1) 75%,\n        rgba(0, 0, 0, 0.1)\n      ),\n      linear-gradient(\n        120deg,\n        rgba(0, 0, 0, 0.1) 25%,\n        transparent 25%,\n        transparent 75%,\n        rgba(0, 0, 0, 0.1) 75%,\n        rgba(0, 0, 0, 0.1)\n      );\n    background-size: 70px 120px;\n    --spine-tartan: repeating-linear-gradient(\n        transparent,\n        transparent 50px,\n        rgba(0, 0, 0, 0.4) 50px,\n        rgba(0, 0, 0, 0.4) 53px,\n        transparent 53px,\n        transparent 63px,\n        rgba(0, 0, 0, 0.4) 63px,\n        rgba(0, 0, 0, 0.4) 66px,\n        transparent 66px,\n        transparent 116px,\n        rgba(0, 0, 0, 0.5) 116px,\n        rgba(0, 0, 0, 0.5) 166px,\n        rgba(255, 255, 255, 0.2) 166px,\n        rgba(255, 255, 255, 0.2) 169px,\n        rgba(0, 0, 0, 0.5) 169px,\n        rgba(0, 0, 0, 0.5) 179px,\n        rgba(255, 255, 255, 0.2) 179px,\n        rgba(255, 255, 255, 0.2) 182px,\n        rgba(0, 0, 0, 0.5) 182px,\n        rgba(0, 0, 0, 0.5) 232px,\n        transparent 232px\n      ),\n      repeating-linear-gradient(\n        270deg,\n        transparent,\n        transparent 50px,\n        rgba(0, 0, 0, 0.4) 50px,\n        rgba(0, 0, 0, 0.4) 53px,\n        transparent 53px,\n        transparent 63px,\n        rgba(0, 0, 0, 0.4) 63px,\n        rgba(0, 0, 0, 0.4) 66px,\n        transparent 66px,\n        transparent 116px,\n        rgba(0, 0, 0, 0.5) 116px,\n        rgba(0, 0, 0, 0.5) 166px,\n        rgba(255, 255, 255, 0.2) 166px,\n        rgba(255, 255, 255, 0.2) 169px,\n        rgba(0, 0, 0, 0.5) 169px,\n        rgba(0, 0, 0, 0.5) 179px,\n        rgba(255, 255, 255, 0.2) 179px,\n        rgba(255, 255, 255, 0.2) 182px,\n        rgba(0, 0, 0, 0.5) 182px,\n        rgba(0, 0, 0, 0.5) 232px,\n        transparent 232px\n      ),\n      repeating-linear-gradient(\n        125deg,\n        transparent,\n        transparent 2px,\n        rgba(0, 0, 0, 0.2) 2px,\n        rgba(0, 0, 0, 0.2) 3px,\n        transparent 3px,\n        transparent 5px,\n        rgba(0, 0, 0, 0.2) 5px\n      );\n  }\n  \n  .bookshelf {\n    width: 100%;\n    margin-top: 32px;\n    display: flex;\n    flex-wrap: wrap;\n  }\n  \n  .book {\n    width: 50px;\n    height: 280px;\n    position: relative;\n    margin-left: 1px;\n    transform-style: preserve-3d;\n    transform: translateZ(0) rotateY(0);\n    transition: transform 1s;\n  }\n  \n  .side {\n    position: absolute;\n    border: 2px solid black;\n    border-radius: 3px;\n    font-weight: bold;\n    color: black;\n    text-align: center;\n    transform-origin: center left;\n  }\n  \n  .spine {\n    position: relative;\n    width: 50px;\n    height: 280px;\n    /* Patterns from: https://projects.verou.me/css3patterns/ */\n    background-image: var(--tartan);\n    transform: rotateY(0deg) translateZ(0px);\n  }\n  \n  .spine-title {\n    margin: 2px;\n    position: absolute;\n    top: 0px;\n    left: 0px;\n    font-size: 12px;\n    color: gold;\n    writing-mode: vertical-rl;\n    text-orientation: mixed;\n  }\n  \n  .spine-author {\n    position: absolute;\n    color: goldenrod;\n    bottom: 0px;\n    left: 20%; /* no idea why 20% centers it */\n  }\n  \n  .top {\n    width: 50px;\n    height: 190px;\n    top: -2px;  /* hmm, why -2 and not 0? */\n    background-image: linear-gradient(90deg, white 90%, gray 10%);\n    background-size: 5px 5px;\n    transform: rotateX(90deg) translateZ(95px) translateY(-95px);\n  }\n  \n  .cover {\n    width: 190px;\n    height: 280px;\n    top: 0px;\n    background-image: url(\"https://picsum.photos/190/280\");\n    background-size: contain;\n    background-repeat: round;\n    left: 50px;\n    transform: rotateY(90deg) translateZ(0);\n    transition: transform 1s;\n  }\n  \n  .book:hover {\n    z-index: 1;\n    transform: rotateX(-25deg) rotateY(-40deg) rotateZ(-15deg) translateY(50px)\n      translateX(-30px);\n  }\n  "
  },
  {
    "path": "bookshelf.js",
    "content": "import { getRootCssStyles} from './cssUtils.js';\n\nfunction getRandomInt(min, max) {\n  min = Math.ceil(min);\n  max = Math.floor(max);\n  return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nfunction randomChoice(array) {\n  return array[Math.floor(Math.random() * array.length)];\n}\n\nlet spines = Object.values(document.getElementsByClassName(\"spine\"));\nlet covers = Object.values(document.getElementsByClassName(\"cover\"));\nlet tops = Object.values(document.getElementsByClassName(\"top\"));\n\nlet availablePatterns = getRootCssStyles();\n\nlet availableColors = [\n  \"maroon\",\n  \"darkgreen\",\n  \"darkolivegreen\",\n  \"brown\",\n  \"saddlebrown\",\n  \"sienna\",\n  \"midnightblue\",\n];\n\n// assign a random height, pattern and colour to each book\nspines.map(function (s, i) {\n  let randomHeight = getRandomInt(220, 290);\n  s.style.height = `${randomHeight}px`;\n  s.style.top = `${280 - randomHeight}px`;\n\n  let randomPattern = randomChoice(availablePatterns);\n  s.style.backgroundImage = `var(${randomPattern})`;\n\n  let randomColor = randomChoice(availableColors);\n  s.style.backgroundColor = randomColor;\n\n  covers[i].style.height = `${randomHeight}px`;\n  covers[i].style.top = `${280 - randomHeight}px`;\n\n  tops[i].style.top = `${280 - randomHeight}px`;\n});\n"
  },
  {
    "path": "cssUtils.js",
    "content": "export function getRootCssStyles(rootRule = \":root\") {\n    // Get all CSS rules for the document using Array methods\n    const cssRulesArray = [...document.styleSheets]\n        .map(styleSheet => {\n            try {\n                return [...styleSheet.cssRules]\n                    .map(rule => rule)\n            } catch (e) {\n                // console.log('Access to stylesheet %s is denied. Ignoring...', styleSheet.href);\n            }\n        })\n\n    var cssVars = [];\n    // Get custom styles from root css rule \n    Object.values(cssRulesArray).forEach(arrayElement => {\n        Object.values(arrayElement).forEach(ruleElement => {\n            if (ruleElement.selectorText === rootRule) {\n                Object.values(ruleElement.style).forEach(style => {\n                    if (style.startsWith('--spine-') && cssVars.indexOf(style) == -1) {\n                        cssVars.push(style);\n                    }\n                })\n            }\n        })\n    })\n\n    return cssVars;\n}\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <link rel=\"stylesheet\" href=\"bookshelf.css\" />\n  </head>\n\n  <body>\n    <div class=\"bookshelf\">\n      <div class=\"book\">\n        <div class=\"side spine\">\n          <span class=\"spine-title\"> Book Title </span>\n          <span class=\"spine-author\"> PG </span>\n        </div>\n        <div class=\"side top\"></div>\n        <div class=\"side cover\"></div>\n      </div>\n    </div>\n  </body>\n  <script type=\"module\" src=\"bookshelf.js\"></script>\n</html>\n"
  }
]