Repository: petargyurov/virtual-bookshelf Branch: main Commit: eedea4cc5d23 Files: 6 Total size: 10.4 KB Directory structure: gitextract_4f_gryi5/ ├── LICENSE ├── README.md ├── bookshelf.css ├── bookshelf.js ├── cssUtils.js └── index.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ================================================ FILE: README.md ================================================ # Virtual Bookshelf This 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. I 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. ![Example](https://i.imgur.com/6u0CySS.png) **How do I add more books?** A book is defined as follows: ```html
Book Title PG
``` Simply add this snippet for each book you want inside `
`. **Why use JS at all?** I 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. **Is this free to use?** Yep, do whatever you want with it. If you found this project useful you can make use of the following badge to spread the word: [![Using Virtual Bookshelf](https://img.shields.io/badge/Extended%20with-Virtual%20Bookshelf-blue?logo=github)](https://github.com/petargyurov/virtual-bookshelf) **Is it perfect?** Nope. 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. ================================================ FILE: bookshelf.css ================================================ :root { --spine-pyramid: linear-gradient( 315deg, transparent 75%, rgba(255, 255, 255, 0.1) 0 ), linear-gradient( 45deg, transparent 75%, rgba(255, 255, 255, 0.1) 0 ), linear-gradient( 135deg, rgba(255, 255, 255, 0.2) 166px, transparent 0 ), linear-gradient( 45deg, rgba(0, 0, 0, 0.1) 75%, transparent 0 ); background-size: 20px 20px; --spine-stairs: repeating-linear-gradient( 63deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.1) 1px, transparent 3px, transparent 0 ), linear-gradient( 127deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.1) 90px, transparent 55%, transparent 0 ), linear-gradient( transparent 51%, rgba(0, 0, 0, 0.1) 170px ); background-size: 70px 120px; --spine-argyle: repeating-linear-gradient( 120deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.1) 1px, transparent 1px, transparent 60px ), repeating-linear-gradient( 60deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.1) 1px, transparent 1px, transparent 60px ), linear-gradient( 60deg, rgba(0, 0, 0, 0.1) 25%, transparent 25%, transparent 75%, rgba(0, 0, 0, 0.1) 75%, rgba(0, 0, 0, 0.1) ), linear-gradient( 120deg, rgba(0, 0, 0, 0.1) 25%, transparent 25%, transparent 75%, rgba(0, 0, 0, 0.1) 75%, rgba(0, 0, 0, 0.1) ); background-size: 70px 120px; --spine-tartan: repeating-linear-gradient( transparent, transparent 50px, rgba(0, 0, 0, 0.4) 50px, rgba(0, 0, 0, 0.4) 53px, transparent 53px, transparent 63px, rgba(0, 0, 0, 0.4) 63px, rgba(0, 0, 0, 0.4) 66px, transparent 66px, transparent 116px, rgba(0, 0, 0, 0.5) 116px, rgba(0, 0, 0, 0.5) 166px, rgba(255, 255, 255, 0.2) 166px, rgba(255, 255, 255, 0.2) 169px, rgba(0, 0, 0, 0.5) 169px, rgba(0, 0, 0, 0.5) 179px, rgba(255, 255, 255, 0.2) 179px, rgba(255, 255, 255, 0.2) 182px, rgba(0, 0, 0, 0.5) 182px, rgba(0, 0, 0, 0.5) 232px, transparent 232px ), repeating-linear-gradient( 270deg, transparent, transparent 50px, rgba(0, 0, 0, 0.4) 50px, rgba(0, 0, 0, 0.4) 53px, transparent 53px, transparent 63px, rgba(0, 0, 0, 0.4) 63px, rgba(0, 0, 0, 0.4) 66px, transparent 66px, transparent 116px, rgba(0, 0, 0, 0.5) 116px, rgba(0, 0, 0, 0.5) 166px, rgba(255, 255, 255, 0.2) 166px, rgba(255, 255, 255, 0.2) 169px, rgba(0, 0, 0, 0.5) 169px, rgba(0, 0, 0, 0.5) 179px, rgba(255, 255, 255, 0.2) 179px, rgba(255, 255, 255, 0.2) 182px, rgba(0, 0, 0, 0.5) 182px, rgba(0, 0, 0, 0.5) 232px, transparent 232px ), repeating-linear-gradient( 125deg, transparent, transparent 2px, rgba(0, 0, 0, 0.2) 2px, rgba(0, 0, 0, 0.2) 3px, transparent 3px, transparent 5px, rgba(0, 0, 0, 0.2) 5px ); } .bookshelf { width: 100%; margin-top: 32px; display: flex; flex-wrap: wrap; } .book { width: 50px; height: 280px; position: relative; margin-left: 1px; transform-style: preserve-3d; transform: translateZ(0) rotateY(0); transition: transform 1s; } .side { position: absolute; border: 2px solid black; border-radius: 3px; font-weight: bold; color: black; text-align: center; transform-origin: center left; } .spine { position: relative; width: 50px; height: 280px; /* Patterns from: https://projects.verou.me/css3patterns/ */ background-image: var(--tartan); transform: rotateY(0deg) translateZ(0px); } .spine-title { margin: 2px; position: absolute; top: 0px; left: 0px; font-size: 12px; color: gold; writing-mode: vertical-rl; text-orientation: mixed; } .spine-author { position: absolute; color: goldenrod; bottom: 0px; left: 20%; /* no idea why 20% centers it */ } .top { width: 50px; height: 190px; top: -2px; /* hmm, why -2 and not 0? */ background-image: linear-gradient(90deg, white 90%, gray 10%); background-size: 5px 5px; transform: rotateX(90deg) translateZ(95px) translateY(-95px); } .cover { width: 190px; height: 280px; top: 0px; background-image: url("https://picsum.photos/190/280"); background-size: contain; background-repeat: round; left: 50px; transform: rotateY(90deg) translateZ(0); transition: transform 1s; } .book:hover { z-index: 1; transform: rotateX(-25deg) rotateY(-40deg) rotateZ(-15deg) translateY(50px) translateX(-30px); } ================================================ FILE: bookshelf.js ================================================ import { getRootCssStyles} from './cssUtils.js'; function getRandomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } function randomChoice(array) { return array[Math.floor(Math.random() * array.length)]; } let spines = Object.values(document.getElementsByClassName("spine")); let covers = Object.values(document.getElementsByClassName("cover")); let tops = Object.values(document.getElementsByClassName("top")); let availablePatterns = getRootCssStyles(); let availableColors = [ "maroon", "darkgreen", "darkolivegreen", "brown", "saddlebrown", "sienna", "midnightblue", ]; // assign a random height, pattern and colour to each book spines.map(function (s, i) { let randomHeight = getRandomInt(220, 290); s.style.height = `${randomHeight}px`; s.style.top = `${280 - randomHeight}px`; let randomPattern = randomChoice(availablePatterns); s.style.backgroundImage = `var(${randomPattern})`; let randomColor = randomChoice(availableColors); s.style.backgroundColor = randomColor; covers[i].style.height = `${randomHeight}px`; covers[i].style.top = `${280 - randomHeight}px`; tops[i].style.top = `${280 - randomHeight}px`; }); ================================================ FILE: cssUtils.js ================================================ export function getRootCssStyles(rootRule = ":root") { // Get all CSS rules for the document using Array methods const cssRulesArray = [...document.styleSheets] .map(styleSheet => { try { return [...styleSheet.cssRules] .map(rule => rule) } catch (e) { // console.log('Access to stylesheet %s is denied. Ignoring...', styleSheet.href); } }) var cssVars = []; // Get custom styles from root css rule Object.values(cssRulesArray).forEach(arrayElement => { Object.values(arrayElement).forEach(ruleElement => { if (ruleElement.selectorText === rootRule) { Object.values(ruleElement.style).forEach(style => { if (style.startsWith('--spine-') && cssVars.indexOf(style) == -1) { cssVars.push(style); } }) } }) }) return cssVars; } ================================================ FILE: index.html ================================================
Book Title PG