/*
 * Page geometry is derived from real millimetres:
 * DIN A4 is 210x297mm, an MTG card is 63x88mm. Three cards span 189 of 210mm
 * and 264 of 297mm, and centring the grid puts the rest into the margins.
 * The variables are set from the binder config, so a 4x3 album is a data change.
 *
 * The cards are then drawn at --card-scale of that, which is the one place the
 * album stops being 1:1 — see the note on the variable for why.
 */
/*
 * --cover is declared rather than merely set, for two reasons: a registered
 * <color> can be interpolated, so recolouring the album crossfades instead of
 * jumping, and an invalid value falls back to this initial rather than making
 * the whole gradient invalid at computed-value time.
 */
@property --cover {
  syntax: "<color>";
  inherits: true;
  initial-value: #2e211a;
}

:root {
  /* The swatch is picked in the album and stored on the binder; this is only
     the colour the page is painted in before that arrives. */
  transition: --cover 240ms ease;

  --page-w: 210;
  --page-h: 297;
  --card-w: 63;
  --card-h: 88;
  --cols: 3;
  --rows: 3;

  /*
   * How large a card is drawn against the paper it sits on.
   *
   * Everything above is a real measurement and stays one; this is the single
   * deliberate departure. Packed to the true 189 of 210mm the sheet reads as
   * crowded on a screen, because none of what separates a card from the paper
   * in the hand survives the trip: no thickness to the sleeve, no shadow in the
   * gap, no light along the edge. Giving the paper back a few millimetres does
   * on screen what those do in life.
   *
   * The cards keep their proportions and their arrangement — only the scale
   * changes — so the album is still a binder, just photographed from slightly
   * further away.
   *
   * It buys height as well, which the rows were short of: three cards plus
   * their price tags used to come within a pixel of the bottom of the sheet on
   * a phone, with nothing clipping them if they crossed it.
   */
  --card-scale: 0.93;

  /*
   * The album around the page, in the same millimetres: the cover overhangs the
   * sheet on every side, and the spine holds the ring mechanism to its left.
   * Everything the cover draws is a fraction of these, so changing the binder
   * shape moves the rings and the page with it.
   *
   * The spine is the one dimension here that is a drawing decision rather than a
   * measurement, and it has been cut three times: 32, then 12, then 6, now 3.
   * At 32 it put 41mm of hide left of the paper against 9mm on the other three
   * sides, which read less like a binder than like a page pinned to one. At 3
   * the band is 12mm — still a third wider than the overhang elsewhere, so the
   * sheet reads as bound to an edge rather than centred on the cover, but only
   * just.
   *
   * The cover keeps its even 9mm overhang everywhere else, which is why this is
   * the variable that moves and not the page's own offset: sliding the sheet
   * alone would leave the difference hanging off the right.
   *
   * This is the floor. A ring loops 4.7mm past the paper's left edge — see
   * --hole-x — leaving 7.3mm of hide in front of the metal here. Two or three
   * millimetres further and the mechanism is sitting on the cover's edge; take
   * the spine to 0 and the page is centred, with 4.3mm left. Going tighter than
   * this means making the rings smaller (--d on .ring) or pushing --hole-x
   * higher so less of each ring clears the sheet — and both of those spend the
   * mechanism to buy the margin.
   *
   * Each cut also narrows the whole album, and since the album is fitted to
   * whatever space is left, the paper and the cards are drawn larger for it:
   * 260 to 231 across the three, and cards about 13% bigger on a phone.
   */
  --cover-pad: 9;
  --spine: 3;
  --album-w: calc(var(--page-w) + var(--spine) + var(--cover-pad) * 2);
  --album-h: calc(var(--page-h) + var(--cover-pad) * 2);
  /*
   * Where the holes are punched: down the page's left edge, at these heights.
   *
   * --hole-x carries the rings as well as the perforation — .ring is positioned
   * at it inside a box that shares the page's — so moving it moves the metal
   * and the holes together and the two cannot drift apart.
   *
   * It is also what sets the gap between the hide and the rings, because the
   * only part of a ring on show is the arc that clears the sheet's left edge.
   * At 4.5% of the page that leaves about 36mm of leather before the metal
   * starts and 4.7mm of a 28mm ring standing proud of the paper: a crescent of
   * the band rather than a loop with its opening visible.
   *
   * Its ceiling is the grid. The holes are 1.1cqw across and painted above the
   * cards, so --hole-x plus that radius has to stay inside the page margin,
   * which --card-scale sets. At 4.5% the holes reach 5.6% and the first column
   * begins at 5.75%. Drawing the cards larger again without walking this back
   * would punch a hole through the first card of every row.
   */
  --hole-x: 4.5%;
  --hole-1: 17%;
  --hole-2: 50%;
  --hole-3: 83%;
  /* What is seen through a punched hole: the ring's metal, then the cut edge. */
  --hole-fill: #a8afb6 0 58%, rgba(0, 0, 0, 0.6) 62% 96%, transparent 97%;

  /*
   * The hide the album is bound in. One colour is stored per binder and set on
   * the root from the binder config; the sheen and the shadowed far edge are
   * mixed from it where the cover is painted, so a new colour keeps the same
   * grain and lighting instead of needing three hand-picked stops.
   */
  --cover: #2e211a;

  --ink: #1b1a17;
  --paper: #fdfcf9;
  --felt: #23303a;
  --felt-2: #16212a;
  --slot-line: rgba(0, 0, 0, 0.22);
  --accent: #b4703a;

  /*
   * A pocket is a sheet of clear plastic welded onto the page: a bright seam
   * around the edge, a lit lip along the opening, and its own small shadow on
   * the paper. Kept in one place because the filter and drag states have to
   * redraw box-shadow and must not lose the sleeve when they do.
   */
  --pocket-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.5), inset 0 1.5px 0 rgba(255, 255, 255, 0.75),
    inset 0 -1px 0 rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.16),
    0 4px 9px rgba(0, 0, 0, 0.1);
}

* {
  box-sizing: border-box;
}

/*
 * The `hidden` attribute is only honoured by a UA rule, which any author rule
 * setting `display` silently beats — so `.filterbar { display: flex }` kept the
 * bar on screen no matter what the toggle did. Restate it with authority.
 */
[hidden] {
  display: none !important;
}

/*
 * The album is a fixed layout, not a document: the bar, the page and the pager
 * share exactly one screen and the page takes whatever is left. Nothing scrolls,
 * whether or not the filter bar is open.
 */
body {
  margin: 0;
  height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
  color: var(--ink);
  background: radial-gradient(ellipse at 50% 0%, var(--felt) 0%, var(--felt-2) 70%);
}

.binder {
  flex: 1;
  min-height: 0; /* let the page shrink instead of pushing the pager off screen */
  display: grid;
  place-items: center;
  padding: 0.75rem 1rem;
  container-type: size;
  perspective: 2000px;
}

/*
 * The closed object: it is the album that fills the leftover space now, and the
 * page is placed inside it, so the cover cannot crop or overflow the sheet.
 */
.album {
  position: relative;
  /* Both axes are stated outright rather than left to aspect-ratio, because the
     ratio here is arithmetic over the millimetre variables. */
  width: min(100cqw, calc(100cqh * var(--album-w) / var(--album-h)));
  height: min(100cqh, calc(100cqw * var(--album-h) / var(--album-w)));
  container-type: size;
  /* Perspective only reaches direct children, so the page gets its own here. */
  perspective: 2200px;
  border-radius: 1.4cqw 0.8cqw 0.8cqw 1.4cqw;
  /*
   * Grained leather: a broad sheen off the top-left over a dark hide.
   *
   * The two outer stops are mixed from --cover right here rather than kept as
   * variables of their own. A custom property is substituted where it is
   * declared, so a --cover-lit living on :root would go on quoting the root's
   * hide no matter which element the colour was actually set on — the sheen
   * would stay brown under a navy cover. Mixed at the point of use it cannot
   * come apart. oklab, so lightening does not wash the colour out the way the
   * same mix in sRGB does.
   */
  background:
    repeating-linear-gradient(
      74deg,
      rgba(255, 255, 255, 0.022) 0 2px,
      rgba(0, 0, 0, 0.03) 2px 4px
    ),
    radial-gradient(
      130% 110% at 10% -5%,
      color-mix(in oklab, var(--cover), #fff 14%) 0%,
      var(--cover) 55%,
      color-mix(in oklab, var(--cover), #000 40%) 100%
    );
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.1),
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    inset 0 -2px 6px rgba(0, 0, 0, 0.5),
    0 30px 70px rgba(0, 0, 0, 0.55);
}

/* The spine: a darker band under the rings, stitched along its outer edge. */
.album::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: calc((var(--cover-pad) + var(--spine)) / var(--album-w) * 100cqw);
  border-radius: inherit;
  background: linear-gradient(90deg, rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0.1) 70%, transparent);
  border-right: 1px dashed rgba(255, 255, 255, 0.12);
  pointer-events: none;
}

.page {
  position: absolute;
  top: calc(var(--cover-pad) / var(--album-h) * 100cqh);
  left: calc((var(--cover-pad) + var(--spine)) / var(--album-w) * 100cqw);
  width: calc(var(--page-w) / var(--album-w) * 100cqw);
  height: calc(var(--page-h) / var(--album-h) * 100cqh);
  container-type: size;
  /* Above the ring mechanism: the paper hides the half of each ring that has
     passed behind it, which is what leaves the metal looping out of the spine. */
  z-index: 2;
  background: var(--paper);
  border-radius: 2px 5px 5px 2px;
  /* Hinged on the ring mechanism, like the real thing. */
  transform-origin: left center;
  /* The sheets filed behind this one, then the page's shadow on the cover. */
  box-shadow:
    1px 1px 0 #f2efe8,
    2px 2px 0 #e9e5dc,
    3px 3px 0 #ded9cf,
    4px 4px 0 #d2ccc1,
    5px 6px 14px rgba(0, 0, 0, 0.45);
  transition:
    transform 220ms ease,
    opacity 220ms ease;
}

/*
 * What the mechanism does to the paper: three punched holes and the shading of
 * the sheet bending down into the spine.
 */
.page::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  z-index: 2;
  background:
    radial-gradient(circle 1.1cqw at var(--hole-x) var(--hole-1), var(--hole-fill)),
    radial-gradient(circle 1.1cqw at var(--hole-x) var(--hole-2), var(--hole-fill)),
    radial-gradient(circle 1.1cqw at var(--hole-x) var(--hole-3), var(--hole-fill)),
    linear-gradient(90deg, rgba(0, 0, 0, 0.22), rgba(0, 0, 0, 0.06) 3%, transparent 9%);
}

/*
 * The ring mechanism. The rings share the page's box so that they line up with
 * the holes punched in it, and sit *under* the page, so each one is cut off at
 * the sheet's edge exactly where the real metal would disappear behind it.
 */
.rings {
  position: absolute;
  top: calc(var(--cover-pad) / var(--album-h) * 100cqh);
  left: calc((var(--cover-pad) + var(--spine)) / var(--album-w) * 100cqw);
  width: calc(var(--page-w) / var(--album-w) * 100cqw);
  height: calc(var(--page-h) / var(--album-h) * 100cqh);
  z-index: 1;
  pointer-events: none;
}

.ring {
  --d: 9cqh;
  position: absolute;
  left: var(--hole-x);
  width: var(--d);
  height: var(--d);
  /* Centred on its hole, so the arc that clears the page edge is the part of
     the ring that loops out over the spine. */
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: linear-gradient(150deg, #fdfdfe 0%, #c3c9d0 30%, #6d747b 58%, #9aa1a8 75%, #eef1f4 100%);
  /* Hollow it out: a ring, not a disc. drop-shadow is applied after the mask,
     so the shadow follows the metal instead of being clipped away with it. */
  -webkit-mask: radial-gradient(circle, transparent 0 57%, #000 58%);
  mask: radial-gradient(circle, transparent 0 57%, #000 58%);
  filter: drop-shadow(1px 2px 2px rgba(0, 0, 0, 0.5));
}

.ring:nth-child(1) {
  top: var(--hole-1);
}
.ring:nth-child(2) {
  top: var(--hole-2);
}
.ring:nth-child(3) {
  top: var(--hole-3);
}

/*
 * The spread: the binder lying open on two pages, the way it is read in the hand.
 *
 * The spine moves to the middle and the album doubles in width around it. The
 * first sheet — the lower page number — goes to the left of the rings and is
 * mirrored: punched down its right edge, bending into the spine on that side,
 * hinged there for the turn. The second sheet sits right of the rings exactly as
 * the single page always has.
 *
 * Everything is still in millimetres, so the cards are drawn at the same
 * proportions as on one page; there are simply two pages' worth of them, and the
 * album is fitted to the space as before — which is why the switch is not offered
 * on a phone.
 */
.album.spread {
  /* Hide between the two sheets, under the rings. */
  --gutter: 12;
  --album-w: calc(var(--page-w) * 2 + var(--gutter) + var(--cover-pad) * 2);
}

.album.spread::before {
  left: calc((var(--cover-pad) + var(--page-w)) / var(--album-w) * 100cqw);
  width: calc(var(--gutter) / var(--album-w) * 100cqw);
  border-radius: 0;
  border-right: none;
  background: linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.45) 50%, transparent);
}

.album.spread .page-first {
  left: calc(var(--cover-pad) / var(--album-w) * 100cqw);
  border-radius: 5px 2px 2px 5px;
  transform-origin: right center;
  /* The sheets behind it stack off to the left now. */
  box-shadow:
    -1px 1px 0 #f2efe8,
    -2px 2px 0 #e9e5dc,
    -3px 3px 0 #ded9cf,
    -4px 4px 0 #d2ccc1,
    -5px 6px 14px rgba(0, 0, 0, 0.45);
}

.album.spread .page-first::before {
  background:
    radial-gradient(circle 1.1cqw at calc(100% - var(--hole-x)) var(--hole-1), var(--hole-fill)),
    radial-gradient(circle 1.1cqw at calc(100% - var(--hole-x)) var(--hole-2), var(--hole-fill)),
    radial-gradient(circle 1.1cqw at calc(100% - var(--hole-x)) var(--hole-3), var(--hole-fill)),
    linear-gradient(270deg, rgba(0, 0, 0, 0.22), rgba(0, 0, 0, 0.06) 3%, transparent 9%);
}

.album.spread .page-second {
  left: calc((var(--cover-pad) + var(--page-w) + var(--gutter)) / var(--album-w) * 100cqw);
}

/*
 * Open, the rings are no longer half-hidden behind one sheet: each runs from a
 * hole in the left page to the matching hole in the right, over the gutter, so
 * it is drawn above both pages. The box spans hole to hole — 0.045 is --hole-x
 * as a fraction, and must move with it.
 */
.album.spread .rings {
  left: calc((var(--cover-pad) + var(--page-w) * 0.955) / var(--album-w) * 100cqw);
  width: calc((var(--gutter) + var(--page-w) * 0.09) / var(--album-w) * 100cqw);
  z-index: 3;
}

.album.spread .ring {
  left: 0;
  width: 100%;
  transform: translateY(-50%);
  /* An ellipse, so the metal keeps its thickness on a ring that is not quite round. */
  -webkit-mask: radial-gradient(ellipse, transparent 0 57%, #000 58%);
  mask: radial-gradient(ellipse, transparent 0 57%, #000 58%);
}

/*
 * Card cells are sized as a percentage of the page — 63/210 and 88/297 — taken
 * down by --card-scale. Each row carries a price tag under the card, so it is a
 * card tall plus the tag.
 *
 * The height now has room to spare: three cards are 82.7% of the page and the
 * two gaps about 3.4%, leaving roughly 13.9% for three tags, which want 7.2%.
 * At full size those figures were 88.9%, 2.3% and 8.8% against the same 7.2% —
 * inside the sheet, but by a margin thin enough that a slightly taller tag
 * would have pushed the bottom row of cards off the paper.
 *
 * Across the width, three cards and two gaps come to 88.5% of the page, so each
 * side margin is about 5.75% — comfortably wider than the 2.4% between the
 * cards, which is the way round a real binder page reads.
 */
.grid {
  /* Proportional, with a floor so the tag never gets shorter than its own text. */
  --tag-h: clamp(13px, 2.4cqh, 26px);

  display: grid;
  grid-template-columns: repeat(
    var(--cols),
    calc(var(--card-w) / var(--page-w) * 100cqw * var(--card-scale))
  );
  grid-template-rows: repeat(
    var(--rows),
    calc(var(--card-h) / var(--page-h) * 100cqh * var(--card-scale) + var(--tag-h))
  );
  gap: 2.4cqw;
  place-content: center;
  height: 100%;
}

/* One pocket and its tag. The card keeps its true size; the tag takes the rest. */
.cell {
  display: grid;
  grid-template-rows: 1fr var(--tag-h);
  min-width: 0;
}

.price {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Scales with the page, but a 500px-wide page would otherwise put this at 7px. */
  font-size: clamp(9px, 1.7cqw, 15px);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  color: rgba(27, 26, 23, 0.82);
  overflow: hidden;
  white-space: nowrap;
}

/* A dimmed card's tag has to fade with it, or the price outlives the artwork. */
.slot.dimmed + .price {
  opacity: 0.2;
}

/*
 * A pocket. The plastic is a shade larger than the card it holds, so the sleeve
 * keeps a little padding and the artwork sits inside it rather than being the
 * pocket itself.
 */
.slot {
  aspect-ratio: var(--card-w) / var(--card-h);
  border-radius: 3.5% / 2.5%;
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.5), rgba(214, 222, 228, 0.35));
  box-shadow: var(--pocket-shadow);
  position: relative;
  overflow: hidden;
  display: grid;
  place-items: center;
  cursor: pointer;
  padding: 1.6%;
  border: none;
  font: inherit;
  color: inherit;
  transition:
    transform 180ms ease,
    box-shadow 180ms ease;
}

/*
 * The sheen on the plastic, plus the lit lip where the pocket opens. It rides
 * above the artwork, which is what keeps the card looking sleeved rather than
 * printed on the page.
 */
.slot::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  border-radius: inherit;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0) 4%),
    linear-gradient(
      104deg,
      transparent 28%,
      rgba(255, 255, 255, 0.1) 40%,
      rgba(255, 255, 255, 0.28) 47%,
      rgba(255, 255, 255, 0.04) 56%,
      transparent 72%
    );
  /* The lip is fixed; the sheen is drawn wider than the pocket and parked off to
     one side, so hovering only has to slide it across. */
  background-repeat: no-repeat;
  background-size:
    100% 100%,
    260% 100%;
  background-position:
    0 0,
    -15% 0;
  transition: background-position 520ms ease;
}

/* An empty pocket is still a pocket: clear plastic over bare paper. */
.slot.empty {
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.45), rgba(206, 214, 221, 0.4));
  color: var(--slot-line);
  font-size: 2cqw;
  transition:
    background 120ms ease,
    color 120ms ease;
}

.slot.empty:hover,
.slot.dropping {
  background: linear-gradient(160deg, rgba(224, 176, 128, 0.4), rgba(180, 112, 58, 0.22));
  color: var(--accent);
}

.slot.filled {
  cursor: grab;
}

/* The card being carried stays visible but recedes, so its origin is obvious. */
.slot.dragging {
  opacity: 0.35;
  cursor: grabbing;
}

/* Where it would land. A filled target means a swap, so both cards are marked. */
.slot.dropping {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* A filled target is washed in the accent. The wash moved to ::after because
   ::before now carries the sleeve's sheen; the two states never coincide. */
.slot.filled.dropping::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
  background: rgba(180, 112, 58, 0.3);
}

/*
 * Children are transparent to the pointer so that moving across the artwork does
 * not fire dragleave on the slot and make the drop highlight flicker.
 */
.slot > * {
  pointer-events: none;
}

/* The card inside the sleeve: its own rounded corners, dropped a hair into the
   pocket so the plastic reads as a layer in front of it. */
.slot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 3.5% / 2.5%;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}

/*
 * Hover: the whole sleeve is picked off the page — it rises, casts a longer
 * shadow and overlaps its neighbours, the card inside it brightens, and the
 * light travels across the plastic. The card is scaled inside the pocket rather
 * than the pocket being scaled around it, so it still reads as sleeved.
 */
.slot.filled:hover {
  transform: translateY(-1.2%) scale(1.04);
  z-index: 4;
  box-shadow:
    var(--pocket-shadow),
    0 10px 24px rgba(0, 0, 0, 0.4);
}

.slot.filled:hover img {
  transform: scale(1.03);
}

/*
 * The sheen slides from one corner of the pocket to the other, and brightens as
 * it goes. The light is put on the plastic rather than on the card: Scryfall's
 * image rules forbid brightening, desaturating or otherwise colour-shifting a
 * card image, and ::before is a gradient of our own, not theirs.
 */
.slot.filled:hover::before {
  filter: brightness(1.3);
  background-position:
    0 0,
    85% 0;
}

.slot img {
  transition:
    transform 160ms ease,
    filter 160ms ease;
}

/*
 * The collector's own tags, along the top of the card. The lookup badge owns the
 * top-right corner, so this strip stops short of it.
 */
.slot .tags {
  position: absolute;
  inset: 3% 9% auto 3%;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4cqw;
  overflow: hidden;
  max-height: 22%;
}

.slot .tag {
  padding: 0.15em 0.45em;
  border-radius: 999px;
  font-size: clamp(8px, 1.35cqw, 13px);
  font-weight: 600;
  line-height: 1.4;
  color: #fdfcf9;
  background: rgba(20, 18, 15, 0.72);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  white-space: nowrap;
  /* A long label yields to the card rather than spilling over its edge. */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

.slot .tag.more {
  background: rgba(20, 18, 15, 0.5);
}

/*
 * A proxy says so on its face. Across the bottom rather than in a corner: the
 * corners are spoken for by the tick and the lookup dot, and this is a statement
 * about the whole card rather than a mark on it.
 */
.slot .proxy-mark {
  position: absolute;
  inset: auto 0 0 0;
  padding: 0.35cqw 0;
  font: 700 1.05cqw/1 system-ui, sans-serif;
  letter-spacing: 0.22em;
  text-align: center;
  color: #f2ece2;
  background: rgba(20, 18, 15, 0.72);
  pointer-events: none;
}

/* The caption slides over it on hover, so it gets out of the way. */
.slot.filled:hover .proxy-mark {
  opacity: 0;
}

/* Cards whose Scryfall lookup did not resolve get a corner marker. */
.slot .badge {
  position: absolute;
  top: 4%;
  right: 4%;
  width: 1.6cqw;
  height: 1.6cqw;
  min-width: 8px;
  min-height: 8px;
  border-radius: 50%;
  background: #e0a030;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.35);
}

.slot .caption {
  position: absolute;
  inset: auto 0 0 0;
  padding: 0.6cqw 0.8cqw;
  /* Sized against the page, with a floor: on a phone the page is some 360px
     across, and 1.25cqw of that is 4px — a grey smear rather than a card name. */
  font-size: max(1.25cqw, 0.52rem);
  line-height: 1.25;
  text-align: left;
  color: #fff;
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
  opacity: 0;
  transition: opacity 140ms ease;
  pointer-events: none;
}

.slot.filled:hover .caption {
  opacity: 1;
}

/*
 * Filtering fades what does not match instead of moving anything.
 *
 * The wash is a layer of felt laid over the pocket, not a filter on the card:
 * greying the artwork out would desaturate a Scryfall image, which their rules
 * forbid. Washing it over instead pushes the card back without touching a
 * pixel of it, and it stays legible enough to read at a glance — which the old
 * 0.16 opacity did not manage anyway.
 */
.slot.dimmed {
  opacity: 0.5;
}

.slot.dimmed::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
  pointer-events: none;
  background: rgba(22, 33, 42, 0.72);
  transition: background 200ms ease;
}

.slot.dimmed:hover {
  opacity: 0.95;
}

.slot.dimmed:hover::after {
  background: rgba(22, 33, 42, 0.2);
}

.slot,
.slot.dimmed {
  transition:
    opacity 200ms ease,
    filter 200ms ease;
}

/* A match keeps its full colour and gets a quiet outline to pick it out. Gathered,
   every card on the page is a match, so the outline would say nothing. */
body.filtering:not(.gathered) .slot.filled:not(.dimmed) {
  box-shadow:
    0 0 0 2px var(--accent),
    0 4px 14px rgba(0, 0, 0, 0.3),
    var(--pocket-shadow);
}

/* A match outranks the plain hover rule, so it has to lift on its own. */
body.filtering:not(.gathered) .slot.filled:not(.dimmed):hover {
  box-shadow:
    0 0 0 2px var(--accent),
    0 10px 24px rgba(0, 0, 0, 0.4),
    var(--pocket-shadow);
}

/*
 * Picking cards out.
 *
 * The tick sits in the same corner as the lookup badge but is drawn as a proper
 * box, because it is a control rather than a marker: empty it invites a click,
 * filled it says the card is in the selection. It is in the layout for every
 * pocket while the mode is on, so ticking one shifts nothing.
 */
.slot .tick {
  position: absolute;
  top: 4%;
  left: 4%;
  width: 2.6cqw;
  height: 2.6cqw;
  min-width: 14px;
  min-height: 14px;
  border-radius: 4px;
  border: 2px solid rgba(255, 255, 255, 0.8);
  background: rgba(20, 18, 15, 0.45);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
  transition:
    background 140ms ease,
    border-color 140ms ease;
}

.slot.selected .tick {
  border-color: var(--accent);
  background: var(--accent)
    /* The check itself, so no glyph has to be loaded or sized to the box. */
    url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M3 8.5l3.2 3.2L13 5' fill='none' stroke='white' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/></svg>")
    center / 80% no-repeat;
}

/* Unpicked cards step back a little so the picked ones read as a set. */
body.selecting .slot.filled:not(.selected) {
  opacity: 0.72;
}

body.selecting .slot.filled:hover {
  opacity: 1;
}

.slot.selected {
  box-shadow:
    0 0 0 2px var(--accent),
    0 4px 14px rgba(0, 0, 0, 0.3),
    var(--pocket-shadow);
}

/* A picked card outranks the plain hover rule, so it lifts on its own. */
.slot.selected:hover {
  box-shadow:
    0 0 0 2px var(--accent),
    0 10px 24px rgba(0, 0, 0, 0.4),
    var(--pocket-shadow);
}

/* Padding at the end of the gathered results: a pocket with nothing behind it. */
.slot.locked {
  opacity: 0.25;
  cursor: default;
}

/* Veiled while the upload runs. A plain veil rather than a backdrop blur: the
   blur would have fallen on the card image behind it, which Scryfall's image
   rules forbid, so the veil is simply thicker instead. */
.slot.uploading::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(253, 252, 249, 0.68);
}

/*
 * Page-flip transition. The sheet is hinged on the rings, so it swings about its
 * own left edge rather than sliding sideways off the cover.
 */
.page.turning-next {
  transform: rotateY(-26deg);
  opacity: 0;
}

.page.turning-prev {
  transform: rotateY(22deg);
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .page,
  .slot,
  .slot::before,
  .slot img,
  .slot .caption {
    transition: none;
  }

  /* Keep the emphasis, drop the travel: the pocket still lights up on hover. */
  .slot.filled:hover {
    transform: none;
  }

  .slot.filled:hover img {
    transform: none;
  }
}

/*
 * Touch.
 *
 * Everything the pocket does on hover is unreachable without a pointer, and
 * worse than unreachable: a tap leaves :hover stuck on the card it opened, so
 * one pocket sits lifted and lit until something else is tapped. The caption is
 * the real loss — it is the card's name, and it was only ever shown on hover.
 */
@media (hover: none) {
  .slot .caption {
    opacity: 1;
  }

  .slot.filled:hover {
    transform: none;
  }

  .slot.filled:hover img {
    transform: none;
  }

  .slot.filled:hover::before {
    filter: none;
  }
}

/*
 * The card under the finger.
 *
 * A copy of the artwork rather than the pocket it came from: the original stays
 * in place, faded, so it is clear where the card is being taken from. `cover`
 * matches the pocket, which is what keeps a scan of odd proportions from being
 * stretched here — Scryfall's rules forbid that, and an upload deserves the
 * same treatment.
 */
.drag-ghost {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 60;
  pointer-events: none;
  border-radius: 3.5% / 2.5%;
  overflow: hidden;
  box-shadow: 0 16px 34px rgba(0, 0, 0, 0.6);
  will-change: transform;
}

.drag-ghost img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/*
 * A held card must not also be a scroll or a pinch, or the browser takes the
 * gesture away mid-drag. The album does not scroll — the body is pinned to the
 * viewport — so nothing is lost by claiming the pocket's own touches outright.
 */
@media (hover: none) {
  .slot.filled {
    touch-action: none;
  }
}

/*
 * The title page: the sheet in front of page 1, with the binder's name and its
 * picture. It is drawn on the first page in place of the pockets, so it has the
 * same paper, the same punched holes and the same turn. Everything is sized in
 * the page's own container units, so it scales with the album like the cards.
 *
 * The left padding is wider than the right to keep the heading clear of the holes.
 */
.title-sheet {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3cqh;
  padding: 7cqh 9cqw 9cqh 12cqw;
  text-align: center;
}

.title-heading {
  margin: 0;
  max-width: 100%;
  font: 600 clamp(18px, 7.5cqw, 64px) / 1.15 Georgia, "Times New Roman", serif;
  letter-spacing: 0.02em;
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* A short rule under the name, as on the title page of a book. */
.title-heading::after {
  content: "";
  display: block;
  width: 30%;
  margin: 2cqh auto 0;
  border-top: 1px solid rgba(27, 26, 23, 0.3);
}

/* One picture, or two stacked: the page is portrait and art crops are landscape,
   so one above the other uses the sheet far better than side by side. */
.title-pictures {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2.5cqh;
  min-height: 0;
}

.title-art {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.2cqh;
  min-height: 0;
}

/*
 * The whole picture, never cropped: an art crop is landscape, a photograph may be
 * anything, and the page is portrait. It is mounted like a print — a white
 * border and a shadow — and capped in height so the name always fits above it.
 */
.title-art img {
  display: block;
  max-width: 100%;
  max-height: 62cqh;
  border: 1.2cqw solid #fff;
  border-radius: 2px;
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.12),
    0 6px 18px rgba(0, 0, 0, 0.25);
}

/* A heading-less page gives the pictures the room the name would have taken. */
.title-heading[hidden] + .title-pictures img {
  max-height: 74cqh;
}

/* Two share the height one would have had, less a little for the second caption. */
.title-pictures.two img {
  max-height: 28cqh;
}

.title-heading[hidden] + .title-pictures.two img {
  max-height: 34cqh;
}

.title-credit {
  font-size: clamp(9px, 1.9cqw, 13px);
  font-style: italic;
  color: rgba(27, 26, 23, 0.6);
}

.title-empty {
  margin: 0;
  max-width: 30ch;
  font-size: clamp(12px, 3cqw, 18px);
  color: rgba(27, 26, 23, 0.6);
}

/* Out of the way in a corner, and quiet until the page is pointed at. */
.title-edit {
  position: absolute;
  right: 4cqw;
  bottom: 3cqh;
  font: inherit;
  font-size: clamp(11px, 2cqw, 14px);
  padding: 0.3em 0.9em;
  border-radius: 999px;
  border: 1px solid rgba(27, 26, 23, 0.25);
  background: rgba(255, 255, 255, 0.85);
  color: var(--ink);
  cursor: pointer;
  opacity: 0.55;
  transition: opacity 150ms ease;
}

.page:hover .title-edit,
.title-edit:focus-visible {
  opacity: 1;
}

/* An empty title page is an invitation, so the button is the point of it. */
.title-empty:not([hidden]) ~ .title-edit {
  position: static;
  opacity: 1;
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

@media (hover: none) {
  .title-edit {
    opacity: 1;
  }
}
