<div class="print-hide">

  <h1>
    Netrunner Proxy Generator
  </h1>

  <p>
    Print Netrunner Proxy cards! Enter your card names into the sidebar, one card per line, and click print. <br />
    Credits got to <a href="https://netrunnerdb.com/" title="NetrunnerDB">netrunnerdb.com</a> and <a href="http://cardgamedb.com/" title="Card Game DB">cardgamedb.com</a> for providing the API and images.
  </p>

  
  <p>
    <a class="btn" href="javascript:window.print();" title="Print">Print Cards</a>
  </p>
  
  <textarea id="UserInput" name="UserInput" placeholder="Card Names" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus>
Exchange of Information
Desperado
The Toolbox
Grimoire
</textarea>
  
</div>

<div id="Cards"></div>
// card
$card-width:            6.35cm;
$card-height:           8.80cm;
$card-scale:            1;

// ui
$font-family-default:   -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";

$font-family-mono:      "Courier New", monospace;
$color-corp:            #0A539D;
$color-runner:          #B93A2F;


*,
*:before,
*:after {
  box-sizing: border-box;
}

html, 
html * {
  font-family: $font-family-default;
  font-size: 18px;
  font-weight: 400;
  line-height: 1.5;
  color: #222;
}

body {
  margin: 0;
  background-color: white;
}

textarea { // sidebar
  position: fixed;
  top: 0;
  left: 0;
  width: 18rem;
  height: 100vh;
  padding: 3rem 1rem 6rem 1rem;
  border: 0 none;
  box-shadow: inset 0.4rem 0 0 darken($color-runner, 6%);
  background-color: $color-runner;
  color: white;
  font-family: $font-family-mono;
  font-size: 14px;
  outline: none;
  resize: none;
  overflow-y: auto;
}

h1 {
  margin: 3rem 0 1rem 0;
  font-size: 1.75rem;
}

P {
  margin: 1rem 0
}

strong, b {
  font-weight: 600;
}

a {
  color: $color-corp;
  font-weight: 400;
  text-decoration: underline;
}

.btn {
  display: inline-block;
  padding: 0.8em 1.4em;
  border: 0 solid transparent;
  border-radius: 2px;
  box-shadow: 0 1px 4px rgba(black, 0.2);
  // background: hsl(0, 0%, 92%) linear-gradient(to top, hsl(0, 15%, 90%) 0%, hsl(0, 10%, 96%) 100%);
  background-color: $color-corp;
  color: white;
  line-height: 1;
  text-decoration: none !important;
  cursor: pointer;
  
  background-position: center;
  transition: background 0.125s;
  
  &:hover {
    background: $color-runner radial-gradient(circle, transparent 1%, $color-runner 1%) center/15000%;
  }
  
  &:active {
    background-color: $color-corp;
    background-size: 100%;
    transition: background 0s;
  }
}

img.card {
  width: $card-width * $card-scale;
  height: $card-height * $card-scale;
  vertical-align: top;
  border: 0.075cm solid black;
  background-color: black;
}

#Cards,
#Cards > * {
  line-height: 0;
}

.text-muted {
  color: hsl(0, 0%, 62%);
}

[data-loading] {
  display: inline-block;
  font-size: 1rem;
  line-height: 1;
  padding: 1em;
}

@media print {
  
  .print-hide {
    display: none;
  }
}


@media screen {
  
  body {
    margin: 0 1rem ($card-height / 2) 20rem;
    overflow-y: scroll;
    cursor: default;
  }
  
  #Cards {
    margin-top: 3rem;

    > * {
      display: inline-block;
      margin: 0;
      vertical-align: top;
      position: relative;
      z-index: 10;
      width: $card-width * $card-scale;
      height: $card-height * $card-scale;
      background: #f6f7f7 linear-gradient(-54deg, rgba(black, 0.06) 50%, rgba(black, 0.02) 50%);
      transform: scale(1);
      // filter: blur(0);
      transition: 
        transform 0.125s, 
        filter 0.1s ease-in-out;

      > img {
        border-radius: 0.1cm;
        background-image: none;
      }
      
      > img:after {
        content: 'card';
        padding: 1.621em 1rem;
        line-height: 1.5;
      }
      
      > img:after,
      > *:not(img) {
        
      }
      
      > .label {
        visibility: hidden;
        position: absolute;
        left: 2px;
        right: 2px;
        bottom: 2px;
        padding: 0.5em 1em;
        border-radius: 0 0 0.1cm 0.1cm;
        font-size: 12px;
        line-height: 1.2;
        background: rgba(black, 0.6);
        color: white;
        opacity: 0;
        transition: opacity 0.1s;
      }
    } 
    
    > p {
      padding: 1.621em 1rem;
      line-height: 1.5;
    }
    
    > a:hover {
      transform: scale(1.1);
      z-index: 20;
      
      > img {
        
      }
      
      > .label {
        visibility: visible;
        opacity: 1;
      }
    }
    
    
    &:hover > a:not(:hover) img {
      // filter: blur(0.5px) grayscale(0.5);
    }
  }
}
View Compiled
var _cardDB           = {};
var _userInputElem    = $('#UserInput');
var _cardListElem     = $('#Cards');
var _cardListHtml     = '';


// loadCards() load cardDB from local storage
function loadCards() {
  _cardDB = localStorage.getItem('cards');
  _cardDB = JSON.parse(_cardDB);
  
  if (!_cardDB) {
    fetchAllCards();
  } else {
    buildList();
  }
}

// saveCards() save cardDB in local storage
function saveCards() {
  localStorage.setItem('cards', JSON.stringify(_cardDB));
}

// reset alias
function reset() {
  return fetchAllCards();
}

// fetchAllCards() get cards from api
function fetchAllCards() {
  localStorage.removeItem('cards');
  _cardListElem.html('<span class="text-muted" data-loading>loading cards ...</span>');

  $.getJSON( "https://netrunnerdb.com/api/2.0/public/cards", function(response) {
    _cardDB = {};

    $.each(response.data, function(key, item) {
        var image = 'https://netrunnerdb.com/card_image/' + item.code + '.png';

        // console.log(item);

        if (typeof item.image_url != "undefined") {
          image = item.image_url;
        }
      
        /* TODO allow alt art for reprinted cards */

        _cardDB[item.title.toLowerCase().replace(/:/g, '').replace(/\s/g, '__')] = {
          code: item.code,
          image: image
        }
    });
    
    console.log('cards fetched from api');
    saveCards();
    buildList();
  });
}

// buildList() generate image list
function buildList() {
  var html = '';
  var input = _userInputElem.val().toLowerCase().split(/\n/);
  var unfound = 0;
  
  if (!_cardDB) {
    return false;
  }
  
  for (var i=0; i<input.length; i++) {
    var cardname = $.trim(input[i]).replace(/:/g, '').replace(new RegExp(' ', 'g'), '__');	
    
    if (cardname == '') {		       
      continue;		
    }
    
    if (cardname in _cardDB) {
      var card = _cardDB[cardname];
      var newCard = '';

      newCard += '<a href="https://netrunnerdb.com/en/card/' + card.code + '" title="" target="NetrunnerCard">';
      newCard += '<img class="card" src="' + card.image + '" alt="' + card.code + '" />';
      newCard += '<span class="label print-hide">' + card.code + ' ' + cardname.replace(/__/g, ' ') + '</span>'; 
      newCard += '</a>'; 

      // console.log(JSON.stringify(card) + ' ' + cardname);
      
      // append
      html += newCard;
      
      // prepend
      // html = newCard + html;
    } else {
      unfound++;
    }
  }
  
  if (unfound > 0) {
    html += '<p class="no-print text-muted">' + unfound + ' not found</p>';
  }
  
  if (_cardListHtml != html) {
    // save current html
    _cardListHtml = html;
    // show images html
    _cardListElem.html(_cardListHtml);
  } 
} 

// assignEvents() rebuild image list when textarea changes
function assignEvents() {
  $(document).on('input propertychange', _userInputElem, function() {
    buildList();
  });  
}

assignEvents();
loadCards();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js