Shadows and Global Alpha

shadowColor (transparent by default), shadowBlur, shadowOffsetX and shadowOffsetY cast a shadow under everything drawn next, and globalAlpha scales the opacity of later drawing. ctx.filter takes CSS filters such as blur() and grayscale(), but Safari 10 lacks it (not Baseline), so treat it as an enhancement.

A cast shadow, a half-transparent cover, and a grayscale filter for a sold-out bookHTML
<canvas id="c" style="width:600px; height:108px"></canvas><script src="hidpi.js"></script>
<script>
  const ctx = hidpi(c), r = devicePixelRatio;
  function cover(x, color, title) {
    ctx.fillStyle = color;  ctx.fillRect(x, 6, 80, 92);  ctx.font = 'bold 13px Georgia';
    ctx.fillStyle = '#fff3e0';  ctx.fillText(title, x + 8, 30);
  }
  ctx.save();
  ctx.shadowColor = 'rgb(0 0 0 / 0.45)';  ctx.shadowBlur = 8 * r;     // shadows ignore
  ctx.shadowOffsetX = ctx.shadowOffsetY = 5 * r;                    // the transform: scale
  cover(20, '#1f5f8b', 'Harbor');  ctx.restore();
  ctx.globalAlpha = 0.5;  cover(170, '#5b3f99', 'Deep Web');  ctx.globalAlpha = 1;
  cover(320, '#e09a10', 'Saffron');
  ctx.filter = 'grayscale(1) opacity(0.6)';  cover(470, '#e09a10', 'Saffron');
</script>
Browser output of Listing 2.10
Browser output of 10

Shadow offsets and blur ignore the transform, so under hidpi() multiply them by devicePixelRatio. Wrap shadows in save() and restore(), or every later shape grows one, as the title here does.

Drop shadows, globalAlpha and ctx.filter on four BookNest coversHTMLLive
<!doctype html>
<style>
  body { margin: 0; padding: 8px; font: 13px system-ui, sans-serif; background: #fafaf7; }
  canvas { width: 100%; max-width: 600px; height: 150px; display: block; background: #fff; border: 1px solid #ddd; }
</style>
<canvas id="c"></canvas>
<label>shadowBlur <input type="range" id="blur" min="0" max="20" value="8"></label>
<script>
  function hidpi(c, r = devicePixelRatio) {
    c.width = c.clientWidth * r;  c.height = c.clientHeight * r;
    const ctx = c.getContext('2d');  ctx.scale(r, r);  return ctx;
  }
  const ctx = hidpi(document.getElementById('c')), r = devicePixelRatio;

  function cover(x, color, title, label) {
    ctx.fillStyle = color;  ctx.fillRect(x, 12, 90, 110);
    ctx.font = 'bold 14px Georgia';  ctx.fillStyle = '#fff3e0';  ctx.fillText(title, x + 8, 36);
    ctx.save();  ctx.shadowColor = 'transparent';  ctx.globalAlpha = 1;  ctx.filter = 'none';
    ctx.font = '11px sans-serif';  ctx.fillStyle = '#263238';  ctx.fillText(label, x, 140);
    ctx.restore();
  }

  function draw(blur) {
    ctx.clearRect(0, 0, 600, 150);
    // Shadows ignore the transform, so multiply by devicePixelRatio under hidpi()
    ctx.save();
    ctx.shadowColor = 'rgb(0 0 0 / 0.45)';  ctx.shadowBlur = blur * r;
    ctx.shadowOffsetX = ctx.shadowOffsetY = 5 * r;
    cover(20, '#1f5f8b', 'Harbor', 'shadow');
    ctx.restore();                                   // or every later shape gets one

    ctx.globalAlpha = 0.5;  cover(170, '#5b3f99', 'Deep Web', 'globalAlpha 0.5');
    ctx.globalAlpha = 1;
    cover(320, '#e09a10', 'Saffron', 'plain');
    // ctx.filter takes CSS filters (not in Safari: treat as an enhancement)
    ctx.filter = 'grayscale(1) opacity(0.6)';  cover(470, '#e09a10', 'Saffron', 'filter: grayscale');
    ctx.filter = 'none';
  }
  const slider = document.getElementById('blur');
  slider.addEventListener('input', () => draw(+slider.value));
  draw(8);
</script>