/* ------------------------------------------------------------------
   Shelf & Table - shared stylesheet
   One file for the whole site. No build step. Edit here, not per page.

   Palette and type are the collection page's: card room green, sand text,
   brass accent, Archivo for display and Karla for everything else. The shelf at
   /shelves/chris is generated by build-public.py and carries its own copy
   of these tokens, so the two must stay in step by hand. If you change a
   colour here, change it in the private collection source too.
------------------------------------------------------------------ */

/* The hidden attribute is a user-agent rule, so any author rule that sets
   display beats it. Three things on this site are shown and hidden through
   the property -- the photo overlay, its arrows, and a section with nothing
   in it -- and all three set display, so hiding them did nothing at all: the
   gallery stayed on the screen with lightbox.hidden true and could not be
   closed on any device. Scripted checks read the property and passed while
   the page was still covered, which is the only reason it shipped.

   !important because it has to beat a later rule setting display on the same
   element, which is the whole failure being fixed. */
[hidden] { display: none !important; }

:root {
  /* Card room green. A felt table is literally green, so the ground explains
     itself, and it puts distance between this and every other dark navy site. */
  --felt:      #101F1A;
  --felt-deep: #061110;
  --felt-hi:   #1D3529;
  --sand:      #EFE8D8;
  --sand-dim:  #CEC5AE;
  --ink:       #07110D;
  /* Two accents, one each. Brass is what you can do or what happens next:
     buttons, links, the night being looked at. Chip is what was recorded:
     dates, tallies, the days already met. With one accent doing both, every
     live thing on the page was the same gold and none of it could be
     emphasised. Both measure 7.0:1 on the felt, so neither outweighs the
     other by accident. */
  --chip:      #6FB3A2;
  --brass:     #D79A52;
  /* the same two as channels, for the tints and rules drawn from them */
  --chip-rgb:  111, 179, 162;
  --brass-rgb: 215, 154, 82;
  --sage:      #7A8F81;
  /* the table top, lit from the near edge. Kept as tokens rather than baked
     into the gradient so the whole palette can be swapped in one place. */
  --table-hi:  #1D3529;
  --table-lo:  #061110;

  /* The shelf itself: an IKEA Kallax in oak effect, which is where most of
     these collections really sit. The back panel is a shade down from the
     frame, because a cube is a hole and a hole is darker than its edge. */
  --kallax:       #C4A77D;
  --kallax-back:  #9C8056;
  --kallax-grain: rgba(120,88,48,.07);

  /* the shelf board, lit along its front edge. Warm grey, not cool: a cool
     grey plank reads blue against green. */
  --board-hi:  #A8AC9C;
  --board-mid: #7E866F;
  --board-lo:  #4F5749;
  --board-off-hi: #48524A;   /* an empty shelf, unlit */
  --board-off-lo: #2F3830;

  /* everything that sits under or behind: shadows, sunken inputs */
  --well:      7, 17, 13;

  --line:      rgba(239,232,216,.15);
  --line-hi:   rgba(239,232,216,.28);
  /* dashed outlines have to carry further than a solid hairline to read */
  --line-dash: rgba(239,232,216,.38);

  /* spine fills. Mid-tones, all legible with ink text on top. */
  --spine-brass: #C9793F;
  --spine-chip:  #5B8FB9;
  --spine-moss:  #6E9E7F;
  --spine-gold:  #C9A227;
  --spine-plum:  #9E6E8E;

  --gap: 16px;
  --rail-h: 8px;
}

* { box-sizing: border-box; }

/* the gradient is viewport-fixed, so anything it does not paint has to be
   dark already or a long page flashes white past the fold */
html { -webkit-text-size-adjust: 100%; background: var(--felt-deep); }

body {
  margin: 0;
  padding: 0 0 64px;
  background:
    radial-gradient(120% 80% at 50% -10%, var(--felt-hi) 0%, var(--felt) 42%, var(--felt-deep) 100%);
  background-attachment: fixed;
  color: var(--sand);
  font-family: "Karla", system-ui, sans-serif;
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

.wrap {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 clamp(20px, 3vw, 56px);
}

/* ---------- masthead ---------- */

.masthead { padding: 52px 0 26px; }
.masthead--sub { padding-bottom: 20px; }

/* The landing masthead is centred; every other page keeps its left edge,
   because those pages open with a breadcrumb bar and a centred title above a
   left-aligned crumb trail reads as a mistake. :not() rather than a second
   class so the markup does not have to know. */
.masthead:not(.masthead--sub) { text-align: center; }
.masthead:not(.masthead--sub) .standfirst { margin-left: auto; margin-right: auto; }

.wordmark {
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(30px, 6vw, 66px);
  line-height: 1.02;
  letter-spacing: -0.035em;
  margin: 0;
  color: var(--sand);
  text-transform: uppercase;
}

.wordmark a { color: inherit; text-decoration: none; }

/* The wordmark used as a page's own heading rather than the site masthead.
   A size down, because the town badge underneath is the thing that should
   catch the eye on a town page, and it cannot do that under type set at
   full landing-page scale. */
.wordmark--sub {
  font-size: clamp(26px, 4.4vw, 46px);
  margin-top: 20px;
}

.wordmark .amp {
  font-family: "Karla", system-ui, sans-serif;
  font-weight: 400;
  font-size: 0.46em;
  vertical-align: 0.28em;
  margin: 0 0.14em;
  color: var(--brass);
}

.standfirst {
  margin: 18px 0 0;
  max-width: 42ch;
  font-size: clamp(1rem, 2vw, 1.15rem);
  color: var(--sand-dim);
}

.standfirst--wide { max-width: 56ch; }

/* Four tiers in the masthead, each doing one job: the name, what it is, what
   happens, where. The tagline is set in the display face so it reads as part
   of the wordmark rather than as the start of the prose; the blurb is body
   type, because it is prose. */
.tagline {
  margin: 16px auto 0;
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 600;
  font-size: clamp(15px, 2.3vw, 21px);
  letter-spacing: -0.01em;
  line-height: 1.25;
  color: var(--sand);
  max-width: 34ch;
}

.masthead-blurb {
  margin: 14px auto 0;
  max-width: 58ch;
  font-size: clamp(0.95rem, 1.7vw, 1.05rem);
  line-height: 1.6;
  color: var(--sand-dim);
}

/* Where this happens, set apart from what it is. Mono and small, the same
   treatment every other piece of standing fact on the site gets: the gloss
   under a section heading, the plate on a shelf, the label on a meetup. */
.masthead-where {
  margin: 14px 0 0;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sage);
}


/* ---------- breadcrumb ---------- */

.topbar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding-top: 28px;
}

.crumbs {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--sage);
  margin: 0;
}

.crumbs a { color: var(--sand-dim); text-decoration: none; border-bottom: 1px solid var(--line); }
.crumbs a:hover { color: var(--sand); border-color: var(--sand-dim); }
.crumbs .sep { padding: 0 0.5rem; opacity: 0.45; }

.page-title {
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(28px, 5.2vw, 58px);
  line-height: 1.04;
  letter-spacing: -0.035em;
  text-transform: uppercase;
  margin: 22px 0 0;
}

/* A town masthead reads across, not down.

   Stacked, the wordmark, the brass town and the blurb each took a line of
   their own and the page opened with four rows of header before any content.

   Three columns, so the wordmark is centred on the page and the town sits
   off its right shoulder. A flex row would centre the *pair*, which puts the
   wordmark left of centre and reads as though it had been nudged; centring
   the name itself and letting the town hang off it is what was wanted. The
   outer columns are equal, which is what does the centring. */
.masthead--town {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  column-gap: 20px;
  row-gap: 6px;
  text-align: center;
  padding-bottom: 18px;
}

.masthead--town .wordmark { grid-row: 1; grid-column: 2; margin: 0; }

.masthead--town .townbadge {
  grid-row: 1;
  grid-column: 3;
  justify-self: start;
  margin: 0;
}

/* The blurb follows the name directly. Hung off the badge in row 2, the
   Facebook button sat between the two and split the title from the line
   that explains it. */
.masthead--town .masthead-blurb,
.masthead--town .standfirst {
  grid-row: 2;
  grid-column: 1 / -1;
  margin-left: auto;
  margin-right: auto;
}

/* The group's own link, under the pair it belongs to rather than between
   them. Below the blurb it can take the middle without competing for it. */
.masthead--town .actions--masthead {
  grid-row: 3;
  grid-column: 1 / -1;
  justify-self: center;
  justify-content: center;
  margin: 14px 0 0;
}

@media (max-width: 620px) {
  /* Too narrow to hang the town off the name: stack them, still centred. */
  .masthead--town { grid-template-columns: minmax(0, 1fr); }
  /* grid-row must go back to auto as well as the column. The desktop rules
     pin the name and the badge to row 1 so the badge can hang off the name;
     in one column that puts them in the same cell, and the badge lands on
     top of the word it is meant to sit beside. */
  .masthead--town .wordmark,
  .masthead--town .townbadge,
  .masthead--town .actions--masthead,
  .masthead--town .masthead-blurb,
  .masthead--town .standfirst {
    grid-row: auto;
    grid-column: 1;
    justify-self: center;
  }
  .masthead--town .actions--masthead { justify-content: center; }
}

/* The town, under the group's name.

   Solid brass with ink on it, which is the loudest thing the palette can do:
   everywhere else brass is a hairline or a word, so a filled block of it
   reads as the one shout on the page. That is the point -- this is what
   somebody arriving from a flyer or bensalem.shelfandtable.com should see
   first and recognise.

   Sized between the title above and the prose below, so it steps down from
   one to the other rather than competing with either. */
.townbadge {
  display: inline-block;
  margin: 16px 0 0;
  padding: 0.32em 0.72em 0.36em;
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(19px, 3.2vw, 32px);
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--brass);
  border-radius: 5px;
}

/* ---------- section furniture ---------- */

.section { padding-bottom: clamp(40px, 6vw, 72px); }

/* Heading and gloss stack rather than sitting side by side. Beside the
   heading the gloss read as a stray fragment of the title; beneath it, it
   reads as the subtitle it is. */
.section-head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  border-top: 1px solid var(--line);
  padding-top: 14px;
  margin-bottom: 28px;
}

/* Centred on the landing page, where the two columns face each other across
   a rule and a left edge each would leave the pair looking unbalanced.
   Subpages keep theirs left, aligned with the prose beneath. */
.split .section-head {
  align-items: center;
  text-align: center;
}

/* Smaller than a heading standing on its own: at the full size the longest
   of the three wraps to two lines and the other two do not, which drops its
   gloss half a line below theirs and reads as a misalignment rather than a
   longer name. */
.split .section-head h2 { font-size: clamp(19px, 2.2vw, 25px); }

.section-head h2 {
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(20px, 3.2vw, 30px);
  letter-spacing: -0.025em;
  text-transform: uppercase;
  margin: 0;
}

.section-head h2 a { color: inherit; text-decoration: none; }
.section-head h2 a:hover { color: var(--brass); }

.section-head .gloss {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sage);
}

/* No measure cap on a table's own page: the facts strips above run the full
   width and prose set to 62ch beside them read as a leftover column. */
.prose { color: var(--sand-dim); }
.prose h2 {
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 600;
  font-size: 18px;
  letter-spacing: -0.01em;
  color: var(--sand);
  margin: 32px 0 8px;
}
.prose p { margin: 0 0 14px; }
.prose a { color: var(--sand); text-decoration-color: var(--line-hi); text-underline-offset: 3px; }
.prose a:hover { color: var(--brass); }

/* ---------- the split: tables, shelves, the board ---------- */

.split {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(24px, 3vw, 44px);
  align-items: start;
}

/* Rules between the columns.

   Faded at both ends rather than hard lines stopping dead, because the
   columns are different heights and a full-length rule would end level with
   whichever happens to be taller, which reads as a mistake rather than a
   division. Drawn on the container at the thirds so each sits in a gap, not
   against a column's edge. */
.split::before, .split::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  transform: translateX(-0.5px);
  background: linear-gradient(180deg,
    transparent 0%, var(--line) 6%, var(--line) 88%, transparent 100%);
  pointer-events: none;
}

.split::before { left: 33.3333%; }
.split::after  { left: 66.6667%; }

/* Three columns need more room than two did. Below this the meetup tile is
   squeezed to the point of wrapping its own date, so the row stacks. */
@media (max-width: 1040px) {
  .split { grid-template-columns: minmax(0, 1fr); gap: 40px; }
  /* stacked, there is nothing between */
  .split::before, .split::after { display: none; }
}

.col { min-width: 0; }

/* One upcoming meetup: the whole strip, then what you can do with it.
   Repeated per date rather than the next one getting the strip and the rest
   getting a line, because the reader is choosing between dates. */
.meetup + .meetup { margin-top: 34px; }

.meetup .facts { margin-bottom: 18px; }
.meetup .actions { margin-bottom: 0; }

/* The meetup a "Click for details" link asked for. */
.meetup.is-focused {
  position: relative;
  padding-left: 18px;
}

.meetup.is-focused::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 18px;
  width: 3px;
  border-radius: 2px;
  background: var(--brass);
}

/* ---------- past nights: the facts in a column, the games beside them ----- */

/* A month heading, which is the cheapest way to show that this happens every
   week: the shape of the groups says it, so no tally has to. */
.monthhead {
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin: 30px 0 2px;
}
.monthhead h3 {
  margin: 0;
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--sand-dim);
}
.monthhead .rule { flex: 1 1 auto; height: 1px; background: var(--line); }
.monthhead .tally {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--chip);
}

.nights { list-style: none; margin: 0; padding: 0; }

.night {
  display: grid;
  /* Wide enough for the longest date this list can draw -- a Saturday
     afternoon at the library, which measures 226px -- so no night wraps its
     date and breaks the column of games beside it. */
  grid-template-columns: 240px minmax(0, 1fr);
  gap: 8px 28px;
  align-items: start;
  padding: 18px 0;
  border-bottom: 1px solid var(--line);
}
.night .meta { display: grid; gap: 2px; }

/* The date leads the column, in the brass the rest of the site uses for
   anything dated. The count sits under it, quieter: it is the proof that
   people turn up, not the headline. */
.night .when {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 14px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--chip);
}
.night .players { font-size: 15px; color: var(--sage); }
.night .games { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; }

/* The venue is drawn only when it is not the group's usual one, so the night
   at the library reads as the exception it was. */
.night .elsewhere { font-size: 14px; color: var(--brass); }

@media (max-width: 620px) {
  .night { grid-template-columns: minmax(0, 1fr); gap: 10px; }
}

/* ---------- asking for a game ---------- */

/* Under the night it belongs to, ruled off so it reads as a different kind
   of thing from the facts above it: those are settled, this is still open. */
.asks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
  /* Above the calendar buttons, because asking for a game is the thing a
     reader can do about a night that has not happened yet; adding it to a
     calendar is what you do once you have decided to come. */
  margin: 16px 0 4px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.asks-label {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--chip);
}
.asklist { list-style: none; display: flex; flex-wrap: wrap; gap: 8px 10px; margin: 0; padding: 0; }
.asklist li { display: inline-flex; align-items: baseline; gap: 7px; }
.asklink {
  font-size: 15px;
  font-weight: 600;
  color: var(--sand);
  text-decoration: none;
  border-bottom: 1px solid var(--line-hi);
}
.asklink:hover, .asklink:focus-visible { color: var(--brass); border-color: var(--brass); }
/* Who asked, quieter than what they asked for. */
.askby { font-size: 13px; color: var(--sage); }
.askby::before { content: "— "; }

.askbtn { margin-left: auto; }

/* On the table tile, where the room for it is one line. */
.tableask {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 2.6cqi;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brass);
  background: none;
  border: 0;
  border-bottom: 1px solid rgba(var(--brass-rgb), .5);
  padding: 0 0 2px;
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease;
}
.tableask:hover, .tableask:focus-visible { color: var(--sand); border-color: var(--sand); }

/* the picker */
.askbox {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(12px, 3vw, 32px);
  background: rgba(4, 10, 8, .9);
}
.askpanel {
  position: relative;
  width: min(560px, 100%);
  max-height: min(86vh, 760px);
  overflow: auto;
  display: grid;
  gap: 12px;
  padding: 22px;
  background: var(--felt);
  border: 1px solid var(--line-hi);
  border-radius: 10px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .6);
}
.ask-close {
  position: absolute;
  top: calc(10px + env(safe-area-inset-top, 0px));
  right: 10px;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  font-size: 24px;
  line-height: 1;
  color: var(--sand);
  background: rgba(var(--well), .5);
  border: 1px solid var(--line);
  border-radius: 50%;
  cursor: pointer;
}
.askhead {
  margin: 0;
  font-family: "Archivo", system-ui, sans-serif;
  font-size: 20px;
  letter-spacing: -0.01em;
}
.asknote { margin: 0; font-size: 14px; color: var(--sage); }
.asksearch input, .askname input {
  width: 100%;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 15px;
  color: var(--sand);
  background: rgba(var(--well), .55);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 11px 13px;
}
.asksearch input:focus-visible, .askname input:focus-visible {
  outline: none;
  border-color: var(--brass);
}
.askname { display: grid; gap: 6px; }
.askname > span {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sage);
}

/* The shelf, scrolling inside the panel rather than the panel growing to
   sixty games tall. */
.askgames {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
  max-height: 44vh;
  overflow: auto;
}
.askgame {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 7px 8px;
  text-align: left;
  color: var(--sand);
  background: transparent;
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}
.askgame:hover, .askgame:focus-visible { background: rgba(255, 255, 255, .06); }
.askgame img { width: 34px; height: 36px; object-fit: contain; object-position: center bottom; }
.askgame-name { font-size: 15px; overflow-wrap: anywhere; }
.askgame-p { font-size: 12px; color: var(--sage); white-space: nowrap; }
/* Already asked for: still listed, because its absence would read as a
   missing game rather than as one somebody beat you to. */
.askgame[disabled] { opacity: .45; cursor: default; }
.askgame-got {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--chip);
}
.asknone { padding: 10px 8px; color: var(--sage); font-size: 14px; }

/* The chosen game, held in view while the eye moves to the button. */
.askgame.is-picked {
  background: rgba(var(--brass-rgb), .18);
  outline: 1px solid rgba(var(--brass-rgb), .5);
}
.asksend {
  display: grid;
  gap: 8px;
  position: sticky;
  bottom: 0;
  padding-top: 10px;
  background: var(--felt);
}
.asksubmit[disabled] { opacity: .45; cursor: default; }
/* The numbered labels, so the order of the job is on the page rather than
   in the reader's head. */
.askname > span, .asksearch > span {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sage);
}
.asksearch { display: grid; gap: 6px; }
.askmsg { margin: 0; min-height: 1.2em; font-size: 14px; color: var(--chip); }

@media (max-width: 560px) {
  .askbtn { margin-left: 0; }
}

/* ---------- the board ---------- */

.boardnew { display: grid; gap: 10px; justify-items: start; margin-bottom: 28px; }
.boardnote { margin: 0; font-size: 14px; color: var(--sage); max-width: 56ch; }

.postlist { display: grid; gap: 18px; }

/* A post is a card because it is a thing somebody put up, and the things
   under it are answers to that specific thing rather than to the page. */
.post {
  display: grid;
  gap: 8px;
  padding: 18px 20px;
  background: rgba(var(--well), .3);
  border: 1px solid var(--line);
  border-radius: 8px;
}
.post-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px 14px; }
.post-game {
  margin: 0;
  font-family: "Archivo", system-ui, sans-serif;
  font-size: 19px;
  letter-spacing: -0.01em;
}
.post-count {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--chip);
}
.post-count.is-full { color: var(--sage); }
.post-note { margin: 0; font-size: 15px; line-height: 1.5; overflow-wrap: anywhere; }
.post-by {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  font-size: 13px;
  color: var(--sand-dim);
}
.post-dc, .post-when { color: var(--sage); }

/* Who has said yes. Names, never addresses -- there are none here to print. */
.post-in { margin: 2px 0 0; display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: baseline; }
.post-in-label {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sage);
}
.post-name { font-size: 14px; color: var(--sand); }
.post-name i { font-style: normal; font-size: 12px; color: var(--sage); }

.post-do { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }

/* The form opens under the post it answers, rather than over it. */
.post-form {
  display: grid;
  gap: 12px;
  margin-top: 6px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.post-form textarea {
  width: 100%;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 15px;
  line-height: 1.5;
  color: var(--sand);
  background: rgba(var(--well), .55);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 11px 13px;
  resize: vertical;
}
.post-form textarea:focus-visible { outline: none; border-color: var(--brass); }
.post-form .asksend { position: static; background: none; justify-items: start; }

/* ---------- the ledger: the nights, added up ---------- */

.pastwrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 280px);
  gap: 46px;
  align-items: start;
}
/* The ledger is a summary of what is beside it, so when there is no room for
   two columns it goes under the thing it summarises, not over it. */
@media (max-width: 900px) {
  .pastwrap { grid-template-columns: minmax(0, 1fr); gap: 34px; }
  /* Stacked, the ledger is the last thing on the page, so there is nothing
     for it to stay level with. */
  .pastwrap .ledger { position: static; }
}

.ledger {
  border-top: 1px solid var(--line);
  padding-top: 16px;
}
.ledgerhead {
  margin: 0 0 4px;
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--sand-dim);
}
.ledgernote {
  margin: 0 0 14px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--sage);
}

.ledgerlist {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
}

/* Name, then a bar for the count, then the count and the night it last came
   out. The bar is the column that gives: the two numbers are fixed width so
   they line up down the list however long the names are. */
.ledgerrow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 42px 26px 54px;
  align-items: baseline;
  gap: 10px;
  padding: 6px 0;
  border-bottom: 1px solid rgba(239, 232, 216, .07);
}
.ledgerrow:last-child { border-bottom: 0; }

.lname {
  font-size: 14px;
  color: var(--sand);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  overflow-wrap: anywhere;
}
.lname:hover, .lname:focus-visible { color: var(--brass); border-bottom-color: var(--brass); }

.lbar {
  display: block;
  height: 4px;
  border-radius: 2px;
  background: rgba(var(--chip-rgb), .14);
  align-self: center;
}
.lbar i {
  display: block;
  height: 100%;
  border-radius: 2px;
  background: var(--chip);
}

.lnights {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--sand-dim);
}
.llast {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: right;
  color: var(--sage);
}

/* ---------- the calendar on a town page ---------- */

.calendar {
  /* the ground the threads are measured against */
  position: relative;
  display: grid;
  /* A column either side of the month, so the nights are dealt into two
     short stacks rather than one long one and the month sits in the middle
     of its own page.

     Every track is capped and the whole block is centred, rather than the
     middle one taking a 1fr: given the spare width, the columns grew wider
     than anything written in them, and the slack showed up as dead ground
     outside the cards instead of as margin either side of the block. */
  grid-template-columns: minmax(0, 280px) minmax(300px, 460px) minmax(0, 280px);
  justify-content: center;
  gap: 30px;
  align-items: start;
  margin-top: 6px;
}
/* One column below two-column width: the columns become one stack, and the
   cards carry their own order so the nights still read in date order. */
/* Prefixed to outrank .calside's own display, which is further down the
   file: unprefixed, the columns stayed columns and the left one sat above
   the month. */
@media (max-width: 1040px) {
  .calendar { grid-template-columns: minmax(0, 1fr); gap: 22px; }
  .calendar .calside { display: contents; }
  .calendar .calgrid-wrap { order: 0; }
}

.calhead {
  /* above the threads, so even a stray curve passes behind the month */
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}
.calhead .calmonth {
  margin: 0;
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 700;
  font-size: 21px;
  letter-spacing: -0.01em;
}
.calnav { display: flex; gap: 6px; }
.calnav button {
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  font-size: 18px;
  line-height: 1;
  color: var(--sand);
  background: rgba(var(--well), .45);
  border: 1px solid var(--line);
  border-radius: 50%;
  cursor: pointer;
  transition: .15s;
}
.calnav button:hover, .calnav button:focus-visible { border-color: var(--line-hi); }

.calgrid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
.calgrid .dow {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--chip);
  text-align: center;
  padding-bottom: 6px;
}
.calgrid .day {
  aspect-ratio: 1;
  max-width: 100%;
  display: grid;
  place-items: center;
  border: 1px solid transparent;
  border-radius: 6px;
  background: rgba(var(--well), .25);
  color: var(--sand-dim);
  font-family: "Karla", system-ui, sans-serif;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}
.calgrid .day.blank { background: none; }

/* A night that happened, and a night still to come. Both are marked, because
   a month with next Friday already on it is the point of showing a calendar
   rather than a list. */
button.day.met {
  cursor: pointer;
  font-weight: 700;
  color: var(--sand);
  background: rgba(var(--chip-rgb), .14);
  border-color: rgba(var(--chip-rgb), .45);
  transition: .15s;
}
button.day.met:hover { background: rgba(var(--chip-rgb), .3); }
button.day.met.ahead {
  border-style: dashed;
  background: rgba(var(--brass-rgb), .14);
  border-color: rgba(var(--brass-rgb), .55);
}
button.day.met.ahead:hover { background: rgba(var(--brass-rgb), .3); }
button.day.met[aria-pressed="true"] {
  background: var(--brass);
  border-color: var(--brass);
  border-style: solid;
  color: var(--ink);
}
.calgrid .day.today { outline: 1px dashed var(--line-hi); outline-offset: 1px; }

/* Every night of the month on show, one under the other, each tied back to
   its own square by a drawn thread. */
.calside {
  display: grid;
  gap: 22px;
  align-content: start;
  /* above the threads, so a curve never crosses a game title */
  position: relative;
  z-index: 2;
}

.calnight {
  border-left: 2px solid var(--line);
  padding: 2px 0 2px 18px;
  display: grid;
  gap: 10px;
  align-content: start;
  transition: border-color .15s;
}
.calnight.is-lit { border-left-color: var(--brass); }

/* The left column's rule sits on its inner edge, so both columns are ruled
   against the month rather than away from it. Below the breakpoint there is
   only one column and every card is ruled on the left again. */
@media (min-width: 1041px) {
  /* Dropped to the first row of days, by the header's own measured height,
     so the month and the year keep the top of the block to themselves and
     no thread is drawn across them on its way to a card. */
  .calside { padding-top: var(--calhead, 60px); }

  /* Not justify-items:end -- that shrank the left cards to their text and
     left the slack outside them, so the block had a 137px gutter on one
     side and 50px on the other. Filling the track keeps both equal. */
  .calside--l .calnight {
    border-left: 0;
    border-right: 2px solid var(--line);
    padding: 2px 18px 2px 0;
  }
  .calside--l .calnight.is-lit { border-right-color: var(--brass); }
}
.calnight p { margin: 0; }
.calnight .when { font-size: 13px; color: var(--chip); }
.calnight .what { font-size: 18px; }
.calnight .played { gap: 14px; margin-top: 2px; }

/* The games under the label rather than beside it: a column of titles can be
   read down, where a wrapped row of five has to be read around corners. */
/* Doubled class to outrank .played, which is further down the file and was
   winning on order at equal specificity -- it centred every title. */
.played.played--col {
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}

/* The boxes are deliberately different sizes -- that is what makes them read
   as boxes rather than bullets -- so in a column each gets the same slot and
   the titles start on one line instead of stepping in and out. */
.played--col .gamelink {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  align-items: center;
  gap: 11px;
  justify-items: start;
}
.played--col .gamebox { justify-self: center; }

.calnone {
  margin: 0;
  color: var(--sage);
  font-size: 15px;
}

/* The threads. Drawn behind the cards and over the month, pale enough to be
   furniture until the night they belong to is the one being pointed at. */
.calwires {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  z-index: 1;
}
.calwires path {
  fill: none;
  stroke: rgba(var(--chip-rgb), .5);
  stroke-width: 1;
  opacity: .45;
  transition: stroke .15s, opacity .15s;
}
.calwires path.is-lit {
  stroke: var(--brass);
  opacity: 1;
}

.calmore {
  /* Under the month rather than under whichever column happened to run
     longest, which is where it landed when it was left to the page flow. */
  text-align: center;
  margin: 34px 0 0;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.calmore a { color: var(--sand-dim); text-decoration: none; border-bottom: 1px solid var(--line); }
.calmore a:hover, .calmore a:focus-visible { color: var(--brass); border-color: var(--brass); }

/* Under its own centred heading, not hard against the left margin where the
   page flow puts it. */
.boarddoor {
  text-align: center;
  margin: 26px 0 0;
}


/* The photo button, on the games line. Quieter than a game link: the
   pictures are the reward for pressing it, not the thing being advertised.
   Drawn only for meetups that have any. */
.photobtn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-left: 4px;
  padding: 6px 12px;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sand-dim);
  background: rgba(var(--well), .42);
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  transition: .15s;
}
.photobtn:hover, .photobtn:focus-visible {
  color: var(--sand);
  border-color: var(--line-hi);
  background: rgba(var(--well), .62);
}

/* A lens, not a camera: two circles read at 12px where a camera body does
   not. */
.photomark {
  width: 13px;
  height: 13px;
  flex: 0 0 auto;
  border: 1.5px solid currentColor;
  border-radius: 3px;
  position: relative;
}
.photomark::after {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 5px; height: 5px;
  margin: -2.5px 0 0 -2.5px;
  border: 1.5px solid currentColor;
  border-radius: 50%;
}

/* The gallery. Dark enough that the photographs are the only lit thing on
   screen, which is the whole point of a lightbox. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(4px, 2vw, 20px);
  padding: clamp(12px, 3vw, 32px);
  background: rgba(4, 10, 8, .94);
}
.lb-stage { margin: 0; display: grid; justify-items: center; gap: 12px; }
.lb-stage img {
  max-width: min(92vw, 1100px);
  max-height: 78vh;
  width: auto;
  height: auto;
  border-radius: 6px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .6);
}
.lb-count {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sage);
  font-variant-numeric: tabular-nums;
}
.lb-close, .lb-step {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  font-size: 26px;
  line-height: 1;
  color: var(--sand);
  background: rgba(var(--well), .55);
  border: 1px solid var(--line);
  border-radius: 50%;
  cursor: pointer;
  transition: .15s;
}
/* Clear of the notch and of the browser's own top chrome: on a phone the
   overlay runs under both, and a close button tucked into the true corner
   is a button you cannot press. */
.lb-close {
  position: absolute;
  top: calc(clamp(12px, 3vw, 28px) + env(safe-area-inset-top, 0px));
  right: calc(clamp(12px, 3vw, 28px) + env(safe-area-inset-right, 0px));
  z-index: 2;
}
.lb-close:hover, .lb-step:hover, .lb-close:focus-visible, .lb-step:focus-visible {
  border-color: var(--line-hi);
  background: rgba(var(--well), .8);
}

/* Below this the arrows would sit on top of the picture; a swipe moves
   between photographs there instead. */
@media (max-width: 560px) {
  .lb-step { display: none; }
  .lb-stage img { max-width: 94vw; }
}

/* The games played, each with a little board game box beside it.

   A box rather than a pill: everything else on this site that stands for a
   game is drawn as the object, and a lozenge of brass said "button" when the
   thing being pointed at is a box on a table. The band across it is the same
   printed strip the shelf boxes carry, at the size it survives.

   Colours cycle through four of the spine palette so a row of games does not
   read as four copies of one box. */
.played {
  display: grid;
  gap: 10px;
  margin-top: 8px;
}

/* As many boxes as the column is wide enough to hold, at a size where the
   cover is actually a cover. The track is the box: the name under it wraps
   inside that width rather than setting it, so the shelf stays even however
   long the titles are. */
.playedgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(86px, 1fr));
  /* Two rows per tile -- the box, then its name -- sized across the whole
     row rather than per tile, so every box stands on the same line and every
     name starts on the same line, whatever shape the boxes are. */
  grid-auto-rows: auto auto;
  gap: 18px 14px;
}

.gametile {
  display: grid;
  grid-row: span 2;
  grid-template-rows: subgrid;
  gap: 8px;
  justify-items: center;
  align-items: end;
  text-decoration: none;
  min-width: 0;
}

/* The name labels the box rather than standing beside it, so it is centred
   under the thing it names and allowed two lines before it gives up. */
.gamename {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.3;
  text-align: center;
  color: var(--brass);
  overflow-wrap: anywhere;
  transition: color .15s ease;
}
.gametile:hover .gamename,
.gametile:focus-visible .gamename { color: var(--sand); }
.gametile:focus-visible { outline: 2px solid var(--brass); outline-offset: 4px; border-radius: 3px; }

/* Every box takes the full width of its column and keeps its own height.

   A fixed slot meant cropping a wide box to fill it -- which took the title
   off Hot Lead and Magical Athlete -- or letterboxing it and leaving the
   picture adrift in empty space. At the column's width and its own
   proportions, nothing is cut and nothing floats: the row's shared baseline
   does the lining up instead. */
.playedgrid .gamebox.gamebox--art {
  width: 100%;
  height: auto;
  aspect-ratio: auto;
  object-fit: fill;
}
/* The drawn ones have no picture to respect, so they keep a box's shape. */
.playedgrid .gamebox {
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 4;
}

.played-label {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sage);
}

.gamelink {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
  color: var(--sand);
  text-decoration: none;
  transition: color .15s ease;
}

/* A stand-in box, for a game played at a meetup that nobody here owns a
   cover for. Portrait, because a game box stood on a table is taller than it
   is wide, with a printed band across it, which is what separates a game box
   from a paperback at this size. The shelves above draw real covers now, so
   this is the only drawn box left on the site.

   A real element rather than ::before, because the band needs a pseudo of
   its own and one element only has the two. */
/* A real cover takes the slot the drawn box would have: the same height, the
   full width of the column's box slot, and sized by the stylesheet rather
   than by the picture -- so a row keeps its rhythm whether the shelf happens
   to own the game or not. */
/* Doubled class, because .gamebox sets the drawn box's size further down the
   file and was winning on order at equal specificity. */
.gamebox.gamebox--art {
  flex: 0 0 auto;
  width: 34px;
  height: 36px;
  /* Contain, not cover: a box that is wider than the slot gets scaled down
     to fit rather than having its sides cut off, which took the title off
     Hot Lead and Magical Athlete. It sits on the bottom of the slot so the
     covers line up along one shelf and the name stays near the picture. */
  object-fit: contain;
  object-position: center bottom;
  border-radius: 2px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, .45);
  transition: transform .15s ease;
}
.gametile:hover .gamebox--art,
.gametile:hover .gamebox { transform: translateY(-2px); }

.gamebox {
  position: relative;
  flex: 0 0 auto;
  width: 27px;
  height: 36px;
  border-radius: 2px 2px 1px 1px;
  background: var(--c, var(--brass));
  box-shadow:
    inset -3px 0 0 rgba(var(--well), .30),
    inset 0 0 0 1px rgba(var(--well), .22),
    inset 0 3px 0 rgba(255,255,255,.10),
    0 2px 5px rgba(0,0,0,.38);
  transition: transform .15s ease;
}

/* the printed band across the side of the box */
.gamebox::after {
  content: "";
  position: absolute;
  left: 0;
  right: 3px;
  top: 34%;
  height: 15%;
  background: rgba(255,255,255,.15);
  border-top: 1px solid rgba(var(--well), .18);
  border-bottom: 1px solid rgba(var(--well), .18);
}

/* The title is underlined, like every other link on the site, because a box
   and a name in sand text gave no sign the pair was clickable at all. Brass
   from the start rather than only on hover: these leave for BoardGameGeek,
   and a link off the site should say so before it is hovered. */
.gamelink span {
  color: var(--brass);
  border-bottom: 1px solid rgba(var(--brass-rgb), .45);
  border-bottom: 1px solid color-mix(in srgb, var(--brass) 45%, transparent);
  padding-bottom: 2px;
  transition: color .15s ease, border-color .15s ease;
}

.gamelink:hover span,
.gamelink:focus-visible span {
  color: var(--sand);
  border-bottom-color: var(--sand);
}

.gamelink:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 4px;
  border-radius: 2px;
}
/* Box shapes. A row of identical rectangles reads as one repeated icon; real
   boxes on a table are a mix of tall, square and flat. The variant is chosen
   from the title in render.js, so a game keeps its box everywhere.

   Heights stay at or under 36px: the row is centre-aligned beside 16px text,
   and a taller box would push the line height around. */
.gamebox--s1 { width: 34px; height: 27px; }
.gamebox--s1::after { top: 42%; }

.gamebox--s2 { width: 31px; height: 31px; }
/* Banded down the face rather than across it, the way a square box with a
   central motif tends to be. */
.gamebox--s2::after {
  left: 36%;
  right: auto;
  top: 0;
  bottom: 0;
  width: 22%;
  height: auto;
  border-top: 0;
  border-bottom: 0;
  border-left: 1px solid rgba(var(--well), .18);
  border-right: 1px solid rgba(var(--well), .18);
}

.gamebox--s3 { width: 23px; height: 36px; }
/* A plain spine, no band. Some boxes just have a printed edge. */
.gamebox--s3::after {
  left: auto;
  right: 3px;
  top: 12%;
  bottom: 12%;
  width: 2px;
  height: auto;
  border: 0;
  background: rgba(255,255,255,.13);
}

.gamebox--s4 { width: 33px; height: 24px; }
.gamebox--s4::after { top: 26%; height: 11%; }
/* The second band. Only this variant has one, which is why .gamebox itself
   has no ::before to reset. */
.gamebox--s4::before {
  content: "";
  position: absolute;
  left: 0;
  right: 3px;
  top: 58%;
  height: 11%;
  background: rgba(255,255,255,.15);
  border-top: 1px solid rgba(var(--well), .18);
  border-bottom: 1px solid rgba(var(--well), .18);
}

.gamelink:hover .gamebox, .gamelink:focus-visible .gamebox { transform: translateY(-3px); }

/* Present for screen readers, absent on screen: the band's own label sits
   inside the form as .signup-label, so a visible h2 would say it twice. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- the town directory ---------- */

/* Full width under the split, so it is centred like the two headings above
   it rather than sitting off to one side. */
.section-head--wide { align-items: center; text-align: center; }

.towns {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  border-top: 1px solid var(--line);
}

.townrow {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 16px;
  padding: 18px 4px;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  color: inherit;
  transition: background .15s ease, padding-left .15s ease;
}

.townrow:hover, .townrow:focus-visible {
  background: rgba(var(--well), .35);
  padding-left: 12px;
}

.townrow:focus-visible { outline: 2px solid var(--brass); outline-offset: -2px; }

.townrow-name {
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(19px, 2.6vw, 26px);
  letter-spacing: -0.02em;
  text-transform: uppercase;
  color: var(--sand);
}

.townrow:hover .townrow-name { color: var(--brass); }

.townrow-meta {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--sage);
}

.townrow-go { color: var(--brass); font-size: 18px; }

/* The invitation row. Dashed, like every other empty slot on this site: a
   bare shelf board, a table with nobody at it. */
.townrow--open { border-bottom-style: dashed; }
.townrow--open .townrow-name { color: var(--sage); font-weight: 600; }
.townrow--open:hover .townrow-name { color: var(--brass); }

@media (max-width: 560px) {
  /* the arrow is decoration, and the meta wants the whole width on a phone */
  .townrow { grid-template-columns: minmax(0, 1fr); gap: 4px; }
  .townrow-go { display: none; }
}

/* ---------- a shelf: real boxes stood in a Kallax ---------- */

.shelves { display: grid; grid-template-columns: minmax(0, 1fr); gap: 26px; }

.shelfunit {
  display: block;
  min-width: 0;
  text-decoration: none;
  color: inherit;
  transition: transform .18s ease;
}

.shelfunit:hover, .shelfunit:focus-visible { transform: translateY(-3px); }
.shelfunit:focus-visible { outline: 2px solid var(--brass); outline-offset: 6px; }

/* The unit.

   Frame and dividers are one gap wide, the same member all the way round.
   That is the whole difference between a cube shelf and a grid of pictures
   with a border on it, and it is why the gap and the padding are the same
   variable. On the real thing the member is about a ninth of a cube, which
   the clamp holds across the widths this lands at.

   Capped, because on the shelves index the column is wide enough to blow the
   cubes up past the size of the boxes they are drawn from. */
.kallax {
  --g: clamp(7px, 2.4%, 12px);
  display: grid;
  gap: var(--g);
  padding: var(--g);
  max-width: 460px;
  border-radius: 2px;
  background: var(--kallax);
  background-image:
    repeating-linear-gradient(90deg, transparent 0 6px, var(--kallax-grain) 6px 7px),
    linear-gradient(180deg, rgba(255,255,255,.12), rgba(0,0,0,.10));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.32),
              inset 0 -1px 0 rgba(0,0,0,.28),
              0 14px 22px -14px rgba(0,0,0,.75);
}

.kallax--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

/* Half the width, not half the cubes blown up to fill the column. A 2x2 and
   a 4x2 are the same furniture in two sizes, so a cube has to measure the
   same in both or the small shelf reads as a different object. The gap is a
   share of the unit, so halving the width needs the share doubled to keep the
   member the same ninth of a cube. */
.kallax--2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  --g: clamp(7px, 4.6%, 12px);
  width: 51.5%;
  min-width: 150px;
  max-width: 236px;
}

/* a cube: square, the back panel darker than the frame, a lip shadow along
   the top so it reads as a hole rather than as a tile */
.cube {
  aspect-ratio: 1;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 6%;
  background: var(--kallax-back);
  box-shadow: inset 0 3px 5px -2px rgba(0, 0, 0, .45),
              inset 0 0 0 1px rgba(0, 0, 0, .18);
}

/* A box stood in a cube.

   Contained, never cropped. A game box is taller than it is wide, so filling
   a square cube with one cut the title off the top of every other cover.
   Here it keeps its real proportions and sits on the floor of the cube, which
   leaves the back panel showing at the sides exactly as the real thing does.
   The lit top and left edges are the box's own thickness catching the light,
   and they are borders rather than an inset shadow, which an opaque image
   would paint straight over. */
.cube img {
  box-sizing: border-box;
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  border-top: 1px solid rgba(255, 255, 255, .30);
  border-left: 1px solid rgba(255, 255, 255, .16);
  box-shadow: 0 3px 5px -1px rgba(0, 0, 0, .6),
              0 0 0 1px rgba(0, 0, 0, .45);
}

.plate {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding-top: 12px;
}

.plate-name {
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 800;
  font-size: 19px;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  color: var(--sand);
}

.shelfunit:hover .plate-name { color: var(--brass); }

.plate-count {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sage);
}

/* An empty shelf. The same unit with nothing in it, unlit, because an empty
   Kallax is already the whole offer: this is the thing, yours could be in it.
   The dashed ring says the space is going spare rather than that the picture
   failed to load. */
.shelfunit.is-open .kallax {
  --kallax: var(--board-off-hi);
  --kallax-back: var(--board-off-lo);
  --kallax-grain: rgba(0, 0, 0, .08);
  box-shadow: inset 0 0 0 1px var(--line-dash),
              0 10px 18px -14px rgba(0, 0, 0, .7);
}

.shelfunit.is-open .cube {
  box-shadow: inset 0 2px 4px -2px rgba(0, 0, 0, .40),
              inset 0 0 0 1px rgba(0, 0, 0, .14);
}

/* The invitation, under the empty shelf it is inviting someone onto. Same
   voice as .rail-note: small, quiet, letter-spaced. The inner <span> is the
   call to action; it cannot be an <a>, because the whole unit already is. */
.shelf-invite {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.1em;
  line-height: 1.7;
  color: var(--sage);
  margin: 0;
  padding-top: 12px;
  max-width: 40ch;
}

.shelf-invite span { color: var(--sand-dim); border-bottom: 1px solid var(--line); }
.shelfunit:hover .shelf-invite span { color: var(--brass); border-color: var(--brass); }

.rail-note {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.1em;
  line-height: 1.7;
  color: var(--sage);
  margin: 18px 0 0;
}

.rail-note a { color: var(--sand-dim); text-decoration: none; border-bottom: 1px solid var(--line); }
.rail-note a:hover { color: var(--brass); border-color: var(--brass); }

/* ---------- a table: seen from above, components on the felt ---------- */

.tables { display: grid; grid-template-columns: minmax(0, 1fr); gap: 34px; }

.tableunit { display: grid; gap: 18px; }

.tabletop {
  position: relative;
  width: 100%;
  /* a table drawn much bigger than this stops reading as a table and starts
     reading as a background */
  max-width: 460px;
  margin-inline: auto;
  /* 5:4, a little squarer than it was. 7:5 dates from the seated figures:
     four people round the edge took the felt's height, so the table was
     widened to keep the facts on it. With components in the corners instead,
     the felt keeps its full height, and a squarer table reads more like one
     you would sit at. Not square: at 1:1 the middle is empty felt above and
     below a three-line event. */
  aspect-ratio: 5 / 4;
  container-type: inline-size;
  /* nothing is meant to sit outside the felt now, but a rotated component
     near an edge must never push the page sideways, so clip */
  overflow: hidden;
}

/* ---------- what is on the table ---------- */

/* Components in the corners, not people at the edges. The middle of the felt
   is the one part that cannot be spent -- it holds the name, the venue, the
   date and sometimes a button -- and the figures used to squeeze that column
   until it broke out of the felt on a phone.

   Inside the surface rather than beside it, so the felt can grow into the
   space the seats used to occupy and the corners are clipped by its own
   rounded edge. */
.bits {
  position: absolute;
  inset: 0;
  /* The surface is a centring grid, and place-items applies to absolutely
     positioned children as well: without this the layer shrank to fit its
     own contents and sat in the middle, which put every component five to
     seven pixels outside the felt instead of inside its corners. */
  place-self: stretch;
  z-index: 2;
  pointer-events: none;
}

.bits > span { position: absolute; }

/* A deck. Three offset shadows rather than one, or it reads as a single card
   with a drop shadow. */
.stackcards {
  width: 8.4cqi;
  height: 11.2cqi;
  border-radius: 2px;
  background: linear-gradient(165deg, var(--sand) 0%, var(--sand-dim) 100%);
  box-shadow:
    0.4cqi 0.4cqi 0 rgba(var(--well), .30),
    0.8cqi 0.8cqi 0 rgba(var(--well), .17),
    0 3px 8px rgba(var(--well), .50);
}

.die {
  width: 5cqi;
  aspect-ratio: 1;
  border-radius: 1cqi;
  background: linear-gradient(150deg, var(--sand) 0%, var(--sand-dim) 100%);
  box-shadow: 0 2px 5px rgba(var(--well), .62);
}

.die i {
  position: absolute;
  width: 17%;
  aspect-ratio: 1;
  margin: -8.5% 0 0 -8.5%;
  border-radius: 50%;
  background: var(--ink);
  opacity: .70;
}

/* Two meeples standing together. The pair is the positioned thing so the two
   of them can simply sit side by side. */
.meeplepair {
  display: flex;
  align-items: flex-end;
  gap: 1.3cqi;
}

/* Body plus head, the head as a pseudo-element so a meeple stays one tag. */
.meeple {
  position: relative;
  display: block;
  width: 4.2cqi;
  height: 5.2cqi;
  border-radius: 46% 46% 22% 22%;
  background: var(--mc, var(--sage));
  box-shadow: 0 1px 3px rgba(var(--well), .55);
}

.meeple::before {
  content: "";
  position: absolute;
  left: 50%;
  top: -32%;
  width: 62%;
  aspect-ratio: 1;
  margin-left: -31%;
  border-radius: 50%;
  background: inherit;
}

.surface {
  position: absolute;
  z-index: 1;
  /* A uniform ring in width units. The old vertical-then-horizontal insets
     existed to leave room for the seats on each edge; with the seats gone
     the felt takes that space back, which is what buys the words enough
     width to stay on the surface at phone sizes. */
  inset: 7cqi;
  border-radius: 10px;
  background:
    /* A board printed faintly into the cloth. This is the whole of the "game
       in play" idea that survives contact with the text: an actual board in
       the middle would sit under the date. At three percent it reads as a
       playmat up close and as plain felt from across the room. */
    repeating-linear-gradient(90deg, rgba(239,232,216,.030) 0 1px, transparent 1px 8.5cqi),
    repeating-linear-gradient(0deg,  rgba(239,232,216,.030) 0 1px, transparent 1px 8.5cqi),
    radial-gradient(120% 100% at 50% 22%, var(--table-hi) 0%, var(--felt) 58%, var(--table-lo) 100%);
  box-shadow: inset 0 0 0 1px var(--line-hi),
              inset 0 0 40px rgba(0,0,0,.55),
              0 18px 34px -18px rgba(0,0,0,1);
  display: grid;
  place-items: center;
  /* cqi, not %. Percentage padding resolves against the inline size on every
     side, so "9%" meant nine percent of the *width* above and below too. On
     a square that was the same number either way; on a wide table it became
     a deep vertical padding that pushed the event button off the felt. */
  padding: 2.6cqi 6cqi;
  text-align: center;
}

/* the accent edge of the table top */
.surface::before {
  content: "";
  position: absolute;
  inset: -6px;
  border-radius: 13px;
  border: 3px solid var(--accent, var(--brass));
  opacity: .85;
}

/* Above the arms, which reach across the felt on their way to the middle. */
.surface-inner { position: relative; z-index: 3; display: grid; gap: 1.8cqi; justify-items: center; }

.tablename {
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(13px, 4.4cqi, 20px);
  line-height: 1.1;
  letter-spacing: -0.025em;
  text-transform: uppercase;
  margin: 0;
  color: var(--sand);
  /* 11ch broke "Bensalem Gaming Group" over three lines and made the name
     the loudest thing on a tile whose job is the next meetup */
  max-width: 15ch;
}

.tablewhere {
  font-family: "Karla", system-ui, sans-serif;
  font-size: clamp(9px, 2.6cqi, 12px);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent, var(--brass));
}

.tablenext {
  margin: 0;
  border-top: 1px solid var(--line);
  padding-top: 1.8cqi;
  max-width: 24ch;
}

.tablenext {
  font-family: "Karla", system-ui, sans-serif;
  /* About a third larger than it was. At 3.5cqi the event filled barely a
     quarter of a felt that, with the seats gone and the table squarer, has
     room for far more; the corners are where the components sit, and the
     middle is for reading. */
  font-size: clamp(15px, 4.6cqi, 22px);
  line-height: 1.3;
  color: var(--sand);
}

.tablenext span { display: block; color: var(--sand-dim); }

/* an empty table: nobody seated, nothing scheduled */
.tableunit.is-open .surface {
  background: none;
  box-shadow: inset 0 0 0 1px var(--line);
}

.tableunit.is-open .surface::before { border-style: dashed; opacity: .6; }
.tableunit.is-open .tablename { color: var(--sand-dim); }

/* an empty table is an invitation, not the main event */
.tableunit.is-open .tabletop { max-width: 380px; }

/* Nothing has been set out on an empty table. The bare felt is the invitation
   now, the way the empty chairs were before the people came off the drawing:
   every other table has a deck and dice on it, and this one is waiting. */
.tableunit.is-open .bits { display: none; }

.tableblurb { margin: 0; color: var(--sand-dim); }

/* The two links inside a table, on one line. */
.tablelinks {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 2cqi;
}

.tabledetails {
  /* Above .tablelink, which is stretched over the whole tile at z-index 1.
     Underneath it, every click on the details link went to the group page
     instead: it looked like a link and behaved like part of the table. */
  position: relative;
  z-index: 2;
  font-family: "Karla", system-ui, sans-serif;
  font-size: clamp(10px, 2.9cqi, 13px);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sand);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong, var(--line));
  padding-bottom: 2px;
}

.tabledetails:hover, .tabledetails:focus-visible { color: var(--brass); border-color: var(--brass); }

/* The meetup a "Click for details" link asked for, once you land on it. */
.schedule li.is-focused {
  background: rgba(var(--well), .45);
  box-shadow: inset 3px 0 0 var(--brass);
  padding-left: 14px;
}

/* Links that actually go to Facebook, in Facebook's blue with Facebook's
   mark. Only ever applied to links whose href is facebook.com: the point of
   borrowing a brand's colour is to tell someone where a click lands, which
   stops being true the moment it decorates something else.

   #1877F2 is their brand blue. On this dark green it needs lightening to
   stay readable, so the text sits at a tint of it and the mark carries the
   colour itself. */
a.fb {
  --fb: #1877F2;
  --fb-lift: #4E96F7;
  align-items: center;
  gap: 7px;
  color: var(--fb-lift);
  border-color: rgba(24, 119, 242, .55);
  background: rgba(24, 119, 242, .12);
}

/* Display is set per kind, never on a.fb itself. Setting it there outranked
   .nextevent's own "display: none" and put the narrow-screen copy of the
   event link on screen at every width, under the table, beside the one
   already inside it. */
a.btn.fb, a.tableevent.fb { display: inline-flex; }

a.fb .fbmark { flex: 0 0 auto; color: var(--fb-lift); }

a.fb:hover, a.fb:focus-visible {
  color: #fff;
  border-color: var(--fb-lift);
  background: rgba(24, 119, 242, .28);
}

a.fb:hover .fbmark, a.fb:focus-visible .fbmark { color: #fff; }

/* Discord, built like the Facebook button so the two read as a pair: the
   brand colour at the same weights, and display set per kind rather than on
   a.dc itself, for the same reason it is not set on a.fb. */
a.dc {
  --dc: #5865F2;
  --dc-lift: #8891F5;
  align-items: center;
  gap: 7px;
  color: var(--dc-lift);
  border-color: rgba(88, 101, 242, .55);
  background: rgba(88, 101, 242, .12);
}
a.btn.dc { display: inline-flex; }
a.dc .dcmark { flex: 0 0 auto; color: var(--dc-lift); }
a.dc:hover, a.dc:focus-visible {
  color: #fff;
  border-color: var(--dc-lift);
  background: rgba(88, 101, 242, .28);
}
a.dc:hover .dcmark, a.dc:focus-visible .dcmark { color: #fff; }

/* the event box on a table keeps its shape, and only changes colour */
a.tableevent.fb, a.nextevent.fb { border-color: rgba(24, 119, 242, .55); }

/* the underline treatment the game titles use does not apply here: these
   already read as buttons */
a.fb span { border-bottom: 0; }

/* Actions sitting in a masthead, under the blurb. Centred, because the
   masthead they belong to is. */
.actions--masthead { margin-top: 20px; justify-content: center; }

/* Dates after the next one, under the group's table. Quieter than the
   meetup line on the table itself, because that one is the answer to "when
   is it" and these are the answer to "when else". */
.alsoon {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.alsoon-label {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent, var(--brass));
  flex: 0 0 auto;
}

.alsoon ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }

.alsoon li {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 10px;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 12px;
  color: var(--sand);
}

.alsoon-where { color: var(--sand-dim); }

.alsoon-event {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent, var(--brass));
  text-decoration: none;
  border-bottom: 1px solid currentColor;
}

.alsoon-event:hover, .alsoon-event:focus-visible { color: var(--sand); }

/* The join row, under the facts strip on a table's own page. Needs air on
   both sides: above it the strip has a rule, below it the prose starts, and
   without the gap the buttons read as part of the paragraph. */
.actions--join { margin: 4px 0 30px; }

/* ---------- shared card furniture, used under a table ---------- */

.panel .table-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
}

.panel p { margin: 0; color: var(--sand-dim); max-width: 54ch; }

.panel {
  background: var(--felt);
  border-radius: 4px;
  border-left: 8px solid var(--accent, var(--brass));
  padding: clamp(18px, 2.6vw, 26px);
  box-shadow: inset 0 0 0 1px var(--line),
              0 10px 22px -14px rgba(0,0,0,.9);
  display: grid;
  gap: 16px;
}

/* ---------- controls: pills, as on the collection page ---------- */

.actions { display: flex; flex-wrap: wrap; gap: 8px; }

.btn {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 9px 15px;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--sand-dim);
  background: transparent;
  text-decoration: none;
  cursor: pointer;
  transition: .15s;
}

/* A link wearing the button: inline on its own, so the padding would sit on
   the line rather than make a shape. */
a.btn { display: inline-block; }

.btn:hover, .btn:focus-visible { border-color: var(--sand-dim); color: var(--sand); }
.btn:focus-visible { outline: 2px solid var(--brass); outline-offset: 3px; }

.btn-solid {
  background: var(--accent, var(--brass));
  border-color: var(--accent, var(--brass));
  color: var(--ink);
  font-weight: 600;
}

.btn-solid:hover, .btn-solid:focus-visible {
  background: var(--sand); border-color: var(--sand); color: var(--ink);
}

/* ---------- signup ---------- */

/* the meetup line, for where it sits outside the table rather than on it */
.next {
  display: none;
  gap: 6px 14px;
  align-items: baseline;
  flex-wrap: wrap;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 15px;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding: 12px 0;
  margin: 0;
  color: var(--sand);
}

.next span { color: var(--sand-dim); }

/* The next meetup's Facebook event, sitting under the meetup line inside the
   table. Sized in cqi like everything else on the table top, so it shrinks
   with the table instead of bursting out of it.

   Rendered twice, exactly as the meetup line above it is: .tableevent inside
   the table for normal widths, .nextevent outside for narrow ones. Below
   430px the table is too small to hold its own text, so both drop out
   together and reappear underneath. A link that outlived its meetup line
   would be pointing at a date nobody can see. */
/* The whole drawn table is a link to its page.

   An empty anchor stretched over the tabletop rather than the article being
   wrapped in one, because the Facebook event link sits inside it and an
   anchor inside an anchor is invalid and would not be clickable. This one
   sits under the event link, which is lifted above it, so each click lands
   where it looks like it should. */
.tablelink {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: 12px;
}

.tablelink:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: -4px;
}

.tableevent {
  position: relative;
  z-index: 2;
  display: inline-block;
  font-family: "Karla", system-ui, sans-serif;
  font-size: clamp(10px, 2.9cqi, 13px);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1.2;
  color: var(--accent, var(--brass));
  text-decoration: none;
  padding: 1.7cqi 3cqi;
  border: 1px solid rgba(var(--brass-rgb), .55);
  border: 1px solid color-mix(in srgb, var(--accent, var(--brass)) 55%, transparent);
  border-radius: 4px;
  background: rgba(var(--brass-rgb), .12);
  background: color-mix(in srgb, var(--accent, var(--brass)) 12%, transparent);
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}

.tableevent:hover, .tableevent:focus-visible {
  color: var(--sand);
  border-color: var(--accent, var(--brass));
  background: rgba(var(--brass-rgb), .26);
  background: color-mix(in srgb, var(--accent, var(--brass)) 26%, transparent);
}

/* Same box, at the size the narrow layout uses. */
.nextevent {
  display: none;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1.2;
  color: var(--accent, var(--brass));
  text-decoration: none;
  padding: 9px 14px;
  border: 1px solid rgba(var(--brass-rgb), .55);
  border: 1px solid color-mix(in srgb, var(--accent, var(--brass)) 55%, transparent);
  border-radius: 4px;
  background: rgba(var(--brass-rgb), .12);
  background: color-mix(in srgb, var(--accent, var(--brass)) 12%, transparent);
  align-self: flex-start;
  margin-top: 14px;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}

.nextevent:hover, .nextevent:focus-visible {
  color: var(--sand);
  border-color: var(--accent, var(--brass));
  background: rgba(var(--brass-rgb), .26);
  background: color-mix(in srgb, var(--accent, var(--brass)) 26%, transparent);
}

/* ---------- facts strip ---------- */

.facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  margin: 0 0 30px;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.facts > div {
  padding: 14px 24px 14px 0;
  margin-right: 24px;
  border-right: 1px solid var(--line);
  flex: 1 1 auto;
}

.facts > div:last-child { border-right: 0; margin-right: 0; }

.facts dt {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sage);
  margin: 0 0 4px;
  order: 2;
}

.facts dd {
  margin: 0 0 2px;
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 800;
  font-size: 22px;
  letter-spacing: -0.02em;
  line-height: 1.2;
}

.facts > div { display: flex; flex-direction: column; }

/* ---------- schedule ---------- */

.schedule { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); }

.schedule li {
  display: flex;
  gap: 18px;
  align-items: baseline;
  flex-wrap: wrap;
  padding: 14px 0;
  border-bottom: 1px solid var(--line);
}

.schedule .when {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--brass);
  flex: 0 0 14rem;
}

.schedule .what { font-weight: 600; color: var(--sand); }
.schedule .note { color: var(--sage); font-size: 14px; width: 100%; }
.schedule .actions { width: 100%; margin-top: 10px; }
.schedule .btn { padding: 7px 12px; font-size: 10px; }

/* the generated Facebook post, on the cross-post panel */
.posttext {
  width: 100%;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 12px;
  line-height: 1.6;
  color: var(--sand);
  background: rgba(var(--well), .55);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 14px;
  resize: vertical;
  margin: 0 0 16px;
}

.posttext:focus-visible { outline: none; border-color: var(--brass); }

/* ---------- callout ---------- */

/* Numbered steps, for the two-step business of getting a shelf hosted.

   The number is drawn rather than left to the list marker, because a marker
   cannot be given the brass or set in the mono face the rest of the site
   uses for anything counted or labelled. */
.steps {
  list-style: none;
  margin: 0 0 30px;
  padding: 0;
  display: grid;
  gap: 26px;
  counter-reset: step;
  max-width: 62ch;
}

.steps li {
  counter-increment: step;
  position: relative;
  padding-left: 46px;
}

.steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 1px;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line-strong, var(--line));
  border-radius: 50%;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 12px;
  color: var(--brass);
}

.steps h3 {
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 700;
  font-size: 17px;
  margin: 0 0 8px;
  color: var(--sand);
}

.steps p { margin: 0 0 12px; color: var(--sand-dim); line-height: 1.6; }
.steps p:last-child { margin-bottom: 0; }

.steps-note {
  font-size: 14px;
  color: var(--sage);
}

.steps a:not(.btn) { color: var(--sand); border-bottom: 1px solid var(--line); text-decoration: none; }
.steps a:not(.btn):hover { color: var(--brass); border-color: var(--brass); }

.callout {
  border: 1px dashed var(--line-dash);
  border-radius: 4px;
  padding: clamp(20px, 3vw, 28px);
  max-width: 62ch;
}

.callout h3 {
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -0.02em;
  font-size: 17px;
  margin: 0 0 10px;
}

.callout p { margin: 0 0 16px; color: var(--sand-dim); }

/* ---------- footer ---------- */

footer {
  border-top: 1px solid var(--line);
  padding: 20px 0 0;
  display: flex;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 10px;
  letter-spacing: 0.16em;
  color: var(--sage);
  text-transform: uppercase;
}

footer a { color: var(--sand-dim); text-decoration: none; border-bottom: 1px solid var(--line); }
footer a:hover { color: var(--sand); border-color: var(--sand-dim); }

footer nav { display: flex; gap: 16px; flex-wrap: wrap; }

/* ---------- narrow screens ---------- */

@media (max-width: 700px) {
  /* a shelf gets shorter, not narrower: the boxes already flex to fit */
  .schedule .when { flex: 1 1 100%; }
  .facts > div { border-right: 0; margin-right: 0; flex-basis: 45%; }
}

/* Below this the table drawing is too small to hold its own text, so the
   meetup details come out of the middle and sit underneath it instead.

   430px while the seats were there: four figures took a fifth of the width
   off each edge, so the felt ran out of room long before the phone did. With
   components in the corners the felt keeps that width, and a 375px phone now
   holds the date inside the table where it belongs. Below 360 it genuinely
   does not fit and the details still come out. */
@media (max-width: 360px) {
  .tabletop { max-width: 270px; margin: 0 auto; }
  /* cqi here too: a percentage would be nine percent of the width top and
     bottom, which is what pushed content off the felt at full size. */
  .surface { padding: 4cqi 6cqi; }
  .tableunit .tablenext { display: none; }
  /* Specific enough to beat a.tableevent.fb. A media query adds no
     specificity of its own, so the branded rule was keeping the in-table
     event link on screen here and the page showed two of them. */
  .tableunit a.tableevent { display: none; }
  .tableunit .next { display: flex; }
  .tableunit a.nextevent { display: inline-flex; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ---------- moderation ----------

   A bar along the bottom rather than the top: it belongs to me and not to
   the page, and the top of every page here is already the masthead. */
.adminbar {
  position: fixed;
  left: 50%;
  bottom: calc(14px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  justify-content: center;
  max-width: calc(100vw - 32px);
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: linear-gradient(180deg, var(--table-hi), var(--table-lo));
  box-shadow: 0 10px 30px rgba(0, 0, 0, .45);
}

.adminnow, .adminmsg {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sage);
}

.adminfield { display: flex; align-items: center; gap: 8px; }
.adminfield span {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sage);
}

.adminin {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 13px;
  width: 170px;
  padding: 7px 10px;
  border-radius: 4px;
  border: 1px solid var(--line);
  background: rgba(var(--well), .55);
  color: var(--sand);
}
.adminin:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }

/* The cross on a request. Quiet until pointed at: it sits inside a list
   people read for the games, not for the controls. */
.modx {
  margin-left: 8px;
  padding: 0 5px;
  border: 0;
  background: transparent;
  color: var(--sage);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  opacity: .5;
  transition: .15s;
}
.modx:hover, .modx:focus-visible { opacity: 1; color: var(--brass); }
.modx:disabled { opacity: .25; cursor: default; }

.modbtn { color: var(--sage); }
.modbtn:hover, .modbtn:focus-visible { color: var(--brass); border-color: var(--brass); }

/* ---------- the question form ---------- */

.askform { display: grid; gap: 16px; margin-top: 4px; }

.askform textarea {
  width: 100%;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 15px;
  line-height: 1.5;
  color: var(--sand);
  background: rgba(var(--well), .55);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 11px 13px;
  resize: vertical;
}
.askform textarea:focus-visible { outline: none; border-color: var(--brass); }

/* The honeypot. Off the page rather than display:none, because a field that
   is not displayed is a field some of them know to skip. Taken out of the
   tab order and off the accessibility tree, so nobody using the form by
   keyboard or by screen reader ever meets it. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.asksent {
  margin: 0;
  color: var(--chip);
  font-size: 15px;
  line-height: 1.55;
}

/* The row .asksend lays out is a sticky grid, built for the picker dialog
   where the button really does span the panel. Standing in a page it only
   looks stretched, so here it sits at its own width with the status line
   beside it. */
.askform .asksend {
  position: static;
  background: none;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
  padding-top: 0;
}

/* Inherited the browser's own link colour, which is a blue nothing else on
   this site is. */
.asknote a { color: var(--sand-dim); text-decoration: underline; text-underline-offset: 2px; }
.asknote a:hover, .asknote a:focus-visible { color: var(--brass); }

/* ---------- the invitation, and the board read from the town page ----------

   The dashed box under the empty shelf said "want yours hosted here" and was
   the friendliest thing on the page, so the other two columns get one in the
   same voice. Not the same element though: that one is a shelf with no books
   on it, and this is only the box. */
.invitebox {
  display: block;
  margin-top: 18px;
  padding: 16px 18px;
  border: 1px dashed var(--line-dash);
  border-radius: 3px;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.1em;
  line-height: 1.7;
  color: var(--sage);
  text-align: center;
  text-decoration: none;
  transition: .15s;
}

.invitebox span { color: var(--sand-dim); border-bottom: 1px solid var(--line); }
.invitebox:hover { border-color: var(--sand-dim); }
.invitebox:hover span { color: var(--brass); border-color: var(--brass); }
.invitebox:focus-visible { outline: 2px solid var(--brass); outline-offset: 3px; }

/* A look at the board, not the board. Three at most, each one a whole row
   somebody can press rather than a line of text with a link somewhere in it. */
.railposts { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }

.railpost {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 4px 10px;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: rgba(var(--well), .3);
  text-decoration: none;
  transition: .15s;
}
.railpost:hover { border-color: var(--sand-dim); }
.railpost:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }

.railgame {
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 600;
  font-size: 15px;
  color: var(--sand);
  min-width: 0;
}
.railpost:hover .railgame { color: var(--brass); }

/* Chip, because a tally is a recorded fact and not a thing to go and do. */
.railcount {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--chip);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.railby {
  grid-column: 1 / -1;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sage);
}

/* ---------- where we play ----------

   A fixed picture of the township with the pins laid over it in per cent,
   so the whole thing scales with the column and a pin stays on its building
   at any width. */
.mapframe {
  position: relative;
  /* The township is half again as tall as it is wide, so at the full width
     of the column it runs to sixteen hundred pixels and a reader meets a
     wall of map. Held to something that fits on a screen. */
  max-width: 620px;
  margin: 0 auto 30px;
  border: 1px solid var(--line);
  border-radius: 6px;
  overflow: hidden;
  background: rgba(var(--well), .3);
}

.mapbase {
  display: block;
  width: 100%;
  height: auto;
  /* The tiles are drawn for white pages, and a white rectangle this size
     glares against the felt and makes the pins the dimmest thing on a map
     that exists for the pins.

     Inverted and spun half a turn round the colour wheel, which flips the
     paper to near-black while putting the hues back roughly where they
     started -- parks stay green, water stays blue, roads stay warm. Cheaper
     and truer than tinting it, and it needs no second set of tiles. */
  filter: invert(1) hue-rotate(180deg) saturate(.6) brightness(.78) contrast(1.05);
}

/* The picture failed. Hide the broken-image mark and let the frame hold the
   outline and the pins on their own. */
.mapframe.is-bare .mapbase { display: none; }
.mapframe.is-bare { min-height: 420px; }
.mapframe.is-bare .mapedge { position: static; height: 420px; }

.mapcredit {
  position: absolute;
  right: 6px;
  bottom: 4px;
  margin: 0;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 9px;
  letter-spacing: .06em;
  color: var(--sand-dim);
  background: rgba(7, 17, 13, .6);
  padding: 2px 6px;
  border-radius: 3px;
}

/* The township line, laid over the picture in the picture's own pixels.
   A faint wash inside it as well as the line, so the place this group plays
   in reads as a shape rather than as the middle of a larger map. */
.mapedge {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.mapedge path {
  fill: rgba(var(--chip-rgb), .07);
  stroke: var(--chip);
  stroke-width: 1.6;
  stroke-linejoin: round;
  /* so the line stays the same weight whatever width the map is drawn at */
  vector-effect: non-scaling-stroke;
  opacity: .8;
}

/* Anchored by the foot of the pole, which is the address. A dot has to be
   centred on the point and so covers the thing it points at; a flag stands
   beside it and leaves that corner visible. */
.pin {
  position: absolute;
  transform: translate(-3px, -17px);
  display: flex;
  align-items: flex-start;
  gap: 6px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  white-space: nowrap;
}

/* A pin on the right-hand side of the map would otherwise write its label
   off the edge and have it clipped. Over there the label goes on the other
   side of the pole instead, which keeps the foot of the flag on the
   address either way. */
.pin--left { flex-direction: row-reverse; transform: translate(-11px, -17px); }

.pinflag { flex: none; overflow: visible; transition: .15s; }

.pinpole {
  stroke: var(--sand);
  stroke-width: 1.6;
  stroke-linecap: round;
  fill: none;
}

.pincloth {
  fill: var(--brass);
  stroke: var(--ink);
  stroke-width: .7;
  stroke-linejoin: round;
  transition: .15s;
}

.pintag {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: .06em;
  color: var(--sand);
  background: rgba(7, 17, 13, .78);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 3px 9px;
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
}

/* The tally in chip: how many nights is a recorded fact, not an action. */
.pintag i {
  font-style: normal;
  color: var(--chip);
  font-variant-numeric: tabular-nums;
}

.pin:hover .pinflag, .pin.is-lit .pinflag { transform: scale(1.18); transform-origin: 3px 17px; }
.pin:hover .pincloth, .pin.is-lit .pincloth { fill: var(--sand); }
.pin:hover .pintag, .pin.is-lit .pintag { border-color: var(--brass); }
.pin:focus-visible { outline: 2px solid var(--brass); outline-offset: 4px; border-radius: 999px; }

.venues {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 18px;
}

.venue {
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: 6px;
  transition: .15s;
}
.venue.is-lit { border-color: var(--brass); }

.venuename {
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 600;
  font-size: 17px;
  color: var(--sand);
  margin: 0 0 3px;
}

.venueaddr { margin: 0; font-size: 13px; color: var(--sand-dim); }

.venuecount {
  margin: 8px 0 10px;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 10px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--chip);
}

.venuenights { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.venuenights li {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 13px;
  padding-top: 6px;
  border-top: 1px dashed var(--line);
}
.venuenights li:first-child { border-top: 0; padding-top: 0; }
.venuenights a { color: var(--sand-dim); text-decoration: none; border-bottom: 1px solid transparent; }
.venuenights a:hover { color: var(--brass); border-color: var(--brass); }
.venuegames { color: var(--sage); font-size: 11px; white-space: nowrap; }

/* On a phone two names a kilometre apart overlap each other, and a map
   whose labels collide is worse than one with none. The dots stay, with a
   finger-sized target around them, and the cards underneath do the naming
   -- tapping a dot scrolls to the one it belongs to. */
@media (max-width: 560px) {
  .pintag { display: none; }
  .pin { padding: 9px; margin: -9px; }
}

/* Between two links on the same line, so they read as a pair of choices
   rather than one sentence. */
.calsep { color: var(--line); margin: 0 10px; }

/* The one line under the masthead blurb that leads to the argument behind
   it. Quiet: a reader who wants the next meetup should not have to step
   over a manifesto to reach it. */
/* The town masthead is a grid with every row placed by hand, so anything
   that does not name a row is auto-placed into the first empty cell, which
   is beside the wordmark. It goes last, under the buttons: it is the
   quietest thing here and should not come between the blurb and the pair of
   links that answer it. */
.masthead--town .masthead-why {
  grid-row: 4;
  grid-column: 1 / -1;
  justify-self: center;
}

.masthead-why {
  margin: 14px 0 0;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.masthead-why a {
  color: var(--sand-dim);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
}
.masthead-why a:hover, .masthead-why a:focus-visible {
  color: var(--brass);
  border-color: var(--brass);
}

/* The opening sentence of the why page, a shade larger than what follows. */
.prose .lede { font-size: 17px; color: var(--sand); }

/* A page that is nothing but argument needs a measure, and a little more
   air than a page of facts does. Set in characters rather than pixels, so
   it holds the same number of words a line at any text size somebody has
   chosen.

   Light text on a dark ground smears for anybody with astigmatism: the
   glyphs bloom and the gap between lines fills in. Opening the leading is
   most of the cure, and it costs a dark page nothing. The colour is already
   the dimmer sand rather than full white, which is the other half of it. */
.prose--read { max-width: 64ch; }

.prose--read p {
  font-size: 17px;
  line-height: 1.78;
  margin: 0 0 20px;
}

.prose--read h2 { margin-top: 38px; }

/* The one line set brighter than the rest, so it stays the opening sentence
   and not a second paragraph. */
.prose--read .lede { font-size: 18px; line-height: 1.7; }

/* Subscribing to the whole calendar, under the list rather than on any one
   night: it is not about a single Friday. */
.subscribe {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
  margin: 26px 0 0;
  padding-top: 22px;
  border-top: 1px solid var(--line);
}

.subscribe-note {
  font-size: 13px;
  line-height: 1.6;
  color: var(--sage);
  max-width: 46ch;
}

.subscribe-note code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  color: var(--sand-dim);
  word-break: break-all;
}

/* A link to one night. Quiet until the row is pointed at: it is a handle for
   somebody who wants to send this night to a friend, not a thing to read. */
.nightlink {
  margin-left: 8px;
  color: var(--sage);
  text-decoration: none;
  font-size: 13px;
  opacity: 0;
  transition: .15s;
}
.night:hover .nightlink,
.nightlink:focus-visible { opacity: 1; }
.nightlink:hover { color: var(--brass); }
.nightlink:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }

/* The night somebody was sent to. Marked, because a column of near identical
   rows gives the eye nothing to land on once the scrolling has stopped. */
.night.is-lit { border-left: 2px solid var(--brass); padding-left: 14px; margin-left: -16px; }


/* ---------- the kiosk ----------

   A sign on a table rather than a page in a hand: everything a size up, and
   nothing on it that has to be read twice. */

.kiosk { padding: 30px 0 60px; }

.sign { text-align: center; }

.sign-eyebrow {
  margin: 0 0 10px;
  font-family: "Karla", system-ui, sans-serif;
  font-size: 14px;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--chip);
}

.sign-name {
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(44px, 9vw, 104px);
  line-height: .98;
  letter-spacing: -.03em;
  margin: 0 0 18px;
  color: var(--sand);
}

.sign-line {
  margin: 0 auto 34px;
  max-width: 24ch;
  font-size: clamp(17px, 2.4vw, 24px);
  line-height: 1.45;
  color: var(--sand-dim);
}

.sign-codes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(18px, 4vw, 48px);
}

/* Each code keeps its own column. Without a width the captions run into
   one another, and a line of text that reads as the neighbour's address is
   worse than no line at all. */
.sign-codes li {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: clamp(104px, 13vw, 170px);
}

.sign-codes img {
  width: clamp(104px, 13vw, 160px);
  height: auto;
  /* The symbol is drawn edge to edge; give it the quiet zone a scanner
     wants, or a phone has to be held unreasonably still. */
  padding: 10px;
  background: rgba(var(--well), .35);
  border-radius: 6px;
}

.sign-what {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: "Archivo", system-ui, sans-serif;
  font-weight: 600;
  font-size: 15px;
  color: var(--sand);
}

/* The two that have a face of their own wear it. The lifted stops rather
   than the brand hexes: #1877F2 and #5865F2 are drawn for white pages and
   go muddy on felt, and these are the same two the buttons elsewhere on
   the site already use. */
.sign-codes .is-fb .sign-what { color: #4E96F7; }
.sign-codes .is-dc .sign-what { color: #8891F5; }
.sign-codes .fbmark, .sign-codes .dcmark { flex: 0 0 auto; }

/* The symbol itself stays pale. A code in brand blue on a dark ground is a
   code a phone has to work at, and the whole job of the thing is to be read
   in one go by somebody who is not trying very hard. */

.sign-url {
  font-family: "Karla", system-ui, sans-serif;
  font-size: 11px;
  line-height: 1.4;
  letter-spacing: .04em;
  color: var(--sage);
  text-align: center;
  overflow-wrap: anywhere;
}

/* The logo on the sign: the site wordmark at sign size, with the town
   beside it exactly as every page wears it. */
.sign-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 16px;
  margin: 0 0 20px;
}

.sign .wordmark { font-size: clamp(38px, 7.5vw, 86px); margin: 0; }

.sign .townbadge {
  font-size: clamp(16px, 2.4vw, 30px);
  padding: 6px 18px;
  margin: 0;
}

/* Nothing on the sign can be followed.

   It is a thing to read from a few feet away, left face up on a table while
   whoever brought it is playing a game. A link on it is a way for the sign
   to stop being a sign: one stray press and it is a privacy policy for the
   rest of the evening, with nobody watching. The guard sits on the page
   rather than on any one element, so a link added here later is inert by
   default rather than by somebody remembering. */
.kiosk a { pointer-events: none; }
