/* ————— BCN Poster Wall ————— */

:root {
  --wall-dark: #3d3a36;
  --wall-mid: #4a463f;
  --ink: #1a1712;
  --paper: #f2ead8;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* SEO/accessibility text: present in the DOM for crawlers and screen readers,
   removed from the visual layout. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

body {
  font-family: "Space Grotesk", system-ui, sans-serif;
  background-color: var(--wall-dark);
  /* orange brick — the standard backdrop for any category without its own texture */
  background-image: url("/textures/tb-orange-brick.jpg");
  background-size: 700px 545px;
  background-position: top center;
  background-repeat: repeat;
  background-attachment: scroll;
  min-height: 100vh;
  color: var(--paper);
  transition: background-image 0.25s ease;
}

/* category-specific backdrops — the shortlist (tb-*.jpg in textures/) */
body[data-cat="clubs"] {
  background-image: url("/textures/tb-black-brick.jpg");
  background-size: 1400px 530px;
  background-repeat: repeat;
}
body[data-cat="expos"] {
  background-image: url("/textures/tb-concrete.jpg");
  background-size: 1400px 1400px;
  background-repeat: repeat;
}
body[data-cat="escena"] {
  background-image: url("/textures/tb-white-brick-tile.jpg");
  background-size: 1400px 530px;
  background-repeat: repeat;
}
body[data-cat="kids"] {
  background-image: url("/textures/tb-kids3.jpg");
  background-size: 1400px 530px;
  background-repeat: repeat;
}
body[data-cat="music"] {
  background-image: url("/textures/tb-orange-brick-2.jpg");
  background-size: 700px 545px;
  background-repeat: repeat;
}
body[data-cat="festes"] {
  background-image: url("/textures/tb-natural-wood.jpg");
  background-size: 1400px 605px;
  background-repeat: repeat;
}
body[data-cat="cinema"] {
  background-image: url("/textures/tb-black-wood.jpg");
  background-size: 1400px 604px;
  background-repeat: repeat;
}
body[data-cat="visites"] {
  background-image: url("/textures/tb-stucco-tile.jpg");
  background-size: 2000px 1314px;
  background-repeat: repeat;
}
body[data-cat="lletres"] {
  background-image: url("/textures/tb-concrete-panels.jpg");
  background-size: 2100px 1377px;
  background-repeat: repeat;
}

/* ————— top bar: like a street sign bolted to the wall ————— */
.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: center;
  gap: 18px;
  padding: 12px 22px;
  background: #fff;
  backdrop-filter: blur(6px);
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.5);
}

.brand { display: flex; flex-direction: column; align-items: flex-start; gap: 0; white-space: nowrap; flex: 0 0 auto; }
.brand-home { display: block; text-decoration: none; line-height: 0; }
.brand-logo { height: 27px; width: auto; display: block; }
/* The POSTERCITY wordmark is 11:1, wider than the old 9.4:1 mark - at 27px tall
   it took 81% of a 375px screen. Scaled down below 700px so the brand does not
   crowd out the city picker and filter chips. */
@media (max-width: 700px) { .brand-logo { height: 20px; } }
/* text wordmark (used for the ENGANXATS variant) — bold red block */
.brand-word {
  font-family: "Archivo Black", sans-serif;
  font-size: 30px;
  letter-spacing: 1px;
  line-height: 1;
  color: #fff;
  background: #c8371e;
  padding: 6px 13px;
  text-transform: uppercase;
}
.brand-bcn {
  font-family: "Archivo Black", sans-serif;
  font-size: 26px;
  color: #fff;
  background: #c8371e;
  padding: 1px 8px;
  letter-spacing: 1px;
}
.brand-wall {
  font-family: "Anton", sans-serif;
  font-size: 22px;
  letter-spacing: 3px;
  color: var(--paper);
}

/* one line: LOGO — CATEGORIES — FILTERS (dates) — SEARCH. On desktop the chip
   cluster is centered between the logo and the right-hand cluster (date
   chips + search), which are different widths, using the classic flexbox
   trick: a flex item with auto margins on both sides absorbs whatever space
   its content-sized siblings (flex-shrink:0) don't need, splitting it evenly
   — genuinely centering the chips in the remaining gap, not just clamped to
   a guessed width. It still shrinks (flex-shrink:1, min-width:0) before ever
   pushing into the logo or search, and scrolls internally via its own
   overflow-x once it can't shrink further.
   (Two earlier approaches failed here: CSS Grid's "1fr auto 1fr" doesn't
   work because a max-content track sizes to the chips' full unwrapped width
   instead of yielding space to its siblings — verified it computed 0/1250/0,
   squeezing the edges to nothing. A position:absolute + JS-measured-gap
   version also missed, because an absolutely positioned box's shrink-to-fit
   width is computed from `left` to the containing block's edge *before*
   `transform: translateX(-50%)` is applied, not from the actual visual gap —
   so it undersized itself relative to the max-width that was explicitly
   set. Plain flexbox with auto margins has neither problem.) */
@media (min-width: 641px) {
  .filterbar { margin: 0 auto; }
}

.brand { order: 1; flex: 0 0 auto; }

/* date chips stacked on top of categories, both centered — two shorter
   rows instead of one, so the block roughly matches the logo/city/language
   column's height instead of towering a single tall row over it */
.filterbar {
  order: 2;
  display: flex;
  flex-direction: column;
  /* grow:0 so it sizes to its content (required for the margin:auto
     centering trick above — a growing item would just fill the row instead
     of leaving room for the auto margins to center it in) */
  flex: 0 1 auto;
  /* stretch (not center): in a column flex container, width is the CROSS
     axis, controlled by align-items rather than flex-shrink/min-width —
     align-items:center let #filters/#whenFilters size to their full
     max-content width (every chip unwrapped) instead of being constrained
     to the row's actual space. Stretching them to the row's width, then
     centering each one's own content with justify-content, gets the
     visual centering without that runaway width. */
  align-items: stretch;
  gap: 6px;
  min-width: 0; /* let the children's own overflow-x scroll instead of the row wrapping */
}

/* language picker stacked above search, both right-aligned in the topbar's
   top-right corner (see applyBrand() in app.js for the picker's insertion) */
.rightbar {
  order: 3;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
  min-width: 0;
}

/* categories: NOT centered like .filters.when above — unlike the date row,
   this one routinely overflows its available width, and centering an
   overflowing scroll container makes it start scrolled to the middle by
   default (some chips already scrolled past on both sides) instead of at
   the first chip. Left-aligned within the stretched row instead; it still
   reads as "centered" overall since the row itself is centered in the
   topbar (see the .filterbar margin:auto rule above). */
.filters {
  display: flex;
  flex: 0 1 auto;
  align-items: center;
  gap: 8px;
  min-width: 0;
  overflow-x: auto;
  flex-wrap: nowrap;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  padding: 3px 0;
  cursor: grab;
}
.filters::-webkit-scrollbar { display: none; }
/* while a click-and-drag scroll is in progress (see makeDragScrollable() in
   app.js) — text selection during the drag looks broken, so suppress it */
.filters.drag-scroll { cursor: grabbing; user-select: none; }

/* category dropdown — stand-in for the chip row from tablet width down (see
   the max-width: 900px block below); width:100% only applies once the mobile
   block (max-width: 640px) stretches its row to full width — above that it
   just sizes to its content, like any other control on the row */
.cat-select {
  display: none;
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: #c8371e;
  background-color: #fff;
  border: 1.5px solid rgba(200, 55, 30, 0.55);
  border-radius: 999px;
  padding: 9px 38px 9px 16px;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='7'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23c8371e' stroke-width='2'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
}

/* dates ("filters"): centered above the category row, fixed to its content,
   own scroll only if truly needed. No max-width cap now — it sits in its
   own row in .filterbar instead of squeezed next to search. */
.filters.when {
  flex: 0 0 auto;
  display: flex;
  justify-content: center;
  gap: 8px;
  overflow-x: auto;
  flex-wrap: nowrap;
  scrollbar-width: none;
}
.filters.when::-webkit-scrollbar { display: none; }
/* "My wall" chip shares the date row, set a little apart from the buttons. */
.filters.when .wallpick { margin-left: 20px; flex: 0 0 auto; }

.filter-chip { flex: 0 0 auto; }

/* Collapsed by default: just the magnifying-glass button. Clicking it (or
   .open being set by app.js) reveals the input in its place — saves the
   ~210px the always-open field used to take on the top bar. */
.search {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  position: relative;
}

.search-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  background: rgba(200, 55, 30, 0.06);
  border: 1.5px solid rgba(200, 55, 30, 0.4);
  border-radius: 50%;
  color: #c8371e;
  cursor: pointer;
  padding: 0;
  transition: border-color 0.15s, background 0.15s;
}
.search-toggle:hover { border-color: #c8371e; background: rgba(200, 55, 30, 0.12); }

.search input {
  width: 0;
  min-width: 0;
  font-family: "Space Grotesk", sans-serif;
  font-size: 13px;
  letter-spacing: 0.5px;
  color: var(--ink);
  background: rgba(200, 55, 30, 0.06);
  border: 1.5px solid rgba(200, 55, 30, 0.4);
  border-radius: 999px;
  padding: 7px 0;
  margin-left: 0;
  outline: none;
  opacity: 0;
  pointer-events: none;
  transition: width 0.18s ease, padding 0.18s ease, margin 0.18s ease, opacity 0.12s ease, border-color 0.15s, background 0.15s;
}
.search.open input {
  width: 210px;
  padding: 7px 15px;
  margin-left: 8px;
  opacity: 1;
  pointer-events: auto;
}
.search.open .search-toggle { border-color: #c8371e; background: rgba(200, 55, 30, 0.12); }
.search input::placeholder { color: rgba(80, 80, 80, 0.6); }
.search input:focus {
  border-color: #c8371e;
  background: rgba(200, 55, 30, 0.1);
}
.filter-chip {
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  padding: 6px 13px;
  border: 1.5px solid rgba(200, 55, 30, 0.4);
  border-radius: 999px;
  background: transparent;
  color: #c8371e;
  cursor: pointer;
  transition: all 0.15s;
}
.filter-chip:hover { border-color: #c8371e; transform: translateY(-1px); }
.filter-chip.active {
  background: #c8371e;
  color: #fff;
  border-color: #c8371e;
}

.filters.when { flex: none; }
.filters.when .filter-chip {
  border-color: rgba(200, 55, 30, 0.85);
}
.filters.when .filter-chip:hover { border-color: #e8443a; }
.filters.when .filter-chip.active {
  background: #c8371e;
  border-color: #c8371e;
  color: #fff;
}

/* "Destacats" — premium/paid placement chip at the start of the category row */
.filter-chip.destacats {
  border-color: #f5c518;
  color: #f5c518;
}
.filter-chip.destacats::before { content: "★ "; }
.filter-chip.destacats:hover { border-color: #ffd534; color: #ffd534; }
.filter-chip.destacats.active {
  background: #f5c518;
  border-color: #f5c518;
  color: var(--ink);
}

/* City picker — current city in red under the logo, with a dropdown of cities */
.citypick { position: relative; margin-top: -1px; }
/* Language picker — same dropdown pattern as the city picker, but sits in
   the topbar's top-right corner (above search) rather than under the city
   name, smaller and muted so it doesn't compete for attention. .langpick
   shares .citypick's position:relative (needed for its dropdown menu). */
.langpick-btn {
  font-family: "Space Grotesk", sans-serif;
  font-size: 10px;
  font-weight: 700;
  color: var(--paper);
  opacity: 0.55;
}
/* right-edge-anchored (not .citypick-menu's default left:0) — opening
   leftward from the button instead of rightward keeps it from running off
   the viewport now that the button sits at the far right edge */
.langpick .citypick-menu { left: auto; right: 0; }
.langpick-btn .citypick-arrow {
  border-left-width: 4px;
  border-right-width: 4px;
  border-top-width: 4px;
}
.langpick-btn:hover { opacity: 0.85; }
.citypick-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: "Archivo Black", sans-serif;
  font-size: 15px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: #ff0004;
  line-height: 1;
}
/* wide, flat CSS triangle so it can be broader than a text caret */
.citypick-arrow {
  width: 0;
  height: 0;
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-top: 6px solid currentColor;
  transition: transform 0.15s;
}
.citypick-btn[aria-expanded="true"] .citypick-arrow { transform: rotate(180deg); }

.citypick-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 60;
  list-style: none;
  margin: 0;
  padding: 4px;
  min-width: 140px;
  background: rgba(20, 18, 15, 0.97);
  border: 1.5px solid #e4002b;
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.55);
}
.citypick-menu li { list-style: none; }
.citypick-menu a {
  display: block;
  padding: 8px 12px;
  border-radius: 5px;
  text-decoration: none;
  color: var(--paper);
  font-family: "Archivo Black", sans-serif;
  font-size: 14px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}
.citypick-menu a:hover { background: rgba(255, 0, 4, 0.18); color: #ff5a3c; }
.citypick-menu a.current { color: #ff0004; }

/* Catalunya's row in the city menu carries a caret that unfolds its ~130 town
   pages. The row itself stays a plain link to the wall, so the caret has to be
   a separate control sitting on top of it rather than wrapping it. */
.citypick-menu li.has-towns { position: relative; }
.citypick-menu li.has-towns > a { padding-right: 34px; }
.townpick-toggle {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  border-radius: 5px;
  cursor: pointer;
  color: var(--paper);
}
.townpick-toggle:hover { background: rgba(255, 0, 4, 0.18); color: #ff5a3c; }
/* A + / - sign rather than the caret the city and language pickers use. Those
   open a menu; this one expands a list in place, and the sign says so. Scoped
   to .townpick-sign so the shared .citypick-arrow triangle is untouched. */
.townpick-toggle .townpick-sign {
  display: block;
  width: 1em;
  font-family: "Space Grotesk", system-ui, sans-serif;
  font-size: 17px;
  font-weight: 700;
  line-height: 1;
  text-align: center;
}
.townpick-toggle .townpick-sign::before { content: "+"; }
/* A real U+2212 minus, written as the literal character rather than as a CSS
   hex escape - the escape was mangled on the way into the file and left a
   stray control character behind. */
.townpick-toggle[aria-expanded="true"] .townpick-sign::before { content: "−"; }

/* The list is nested inside the (already scrollable) floating menu, so it gets
   its own cap rather than fighting the parent for height. */
.townlist {
  max-height: 46vh;
  overflow-y: auto;
  margin: 2px 0 6px;
  padding: 4px 0 4px 10px;
  border-left: 2px solid rgba(228, 0, 43, 0.5);
}
.townlist a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 10px;
  /* the town names are proper nouns in mixed case - the uppercase Archivo
     Black the city rows use makes a 130-item list unreadable */
  font-family: "Space Grotesk", sans-serif;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
}
.townlist a span { color: var(--muted, #a99e8a); font-size: 11px; flex: none; }
.townlist a:hover span { color: inherit; }
.townlist .townlist-all {
  margin-top: 4px;
  border-top: 1px solid rgba(242, 234, 216, 0.15);
  color: #ff5a3c;
  font-weight: 700;
}

/* Catalunya-wide categories (Festivals / Festes / Fires) — a cool, muted tint
   that sets them apart from the warm Barcelona-only chips without shouting. */
.filter-chip.regional {
  border-color: rgba(80, 80, 80, 0.55);
  color: #555;
}
.filter-chip.regional:hover { border-color: #555; color: #333; }
.filter-chip.regional.active {
  background: #555;
  border-color: #555;
  color: #fff;
}

/* "La meva paret" — a mode toggle living at the end of the category row */
.filter-chip.meva { border-color: rgba(200, 55, 30, 0.85); }
.filter-chip.meva:hover { border-color: #e8443a; }
.filter-chip.meva.active {
  background: #c8371e;
  border-color: #c8371e;
  color: #fff;
}

/* ————— the wall ————— */
.wall {
  columns: 5 250px;
  column-gap: 32px;
  padding: 140px 40px 85px;
  max-width: 1700px;
  margin: 0 auto;
}

.loading { column-span: all; display: flex; justify-content: center; padding: 90px 0; }
.loading-poster {
  font-family: "Anton", sans-serif;
  font-size: 42px;
  line-height: 1.05;
  color: var(--ink);
  background: var(--paper);
  padding: 34px 40px;
  transform: rotate(-3deg);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
  animation: wobble 1.2s ease-in-out infinite alternate;
}
@keyframes wobble { from { transform: rotate(-3deg); } to { transform: rotate(2deg); } }

/* ————— posters ————— */
.poster {
  display: flex;
  flex-direction: column;
  aspect-ratio: 210 / 297; /* A4 */
  break-inside: avoid;
  margin: 0 0 32px;
  position: relative;
  text-decoration: none;
  color: inherit;
  transform: rotate(var(--rot, 0deg));
  transition: transform 0.18s ease, filter 0.18s ease, box-shadow 0.18s ease, z-index 0s;
  filter: saturate(0.96);
  /* tight, barely-blurred shadow — paper sitting flush against the wall, not floating */
  box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.4);
}
.poster:hover {
  transform: rotate(0deg) scale(1.035);
  z-index: 30;
  filter: saturate(1.05) brightness(1.04);
  box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.45);
}

/* brief pulse for a poster deep-linked from the SEO event page, so a visitor
   arriving via ?poster=<id> can spot it among the rest of the wall */
.poster.highlight {
  animation: poster-highlight-pulse 1.3s ease-in-out 2;
  z-index: 40;
}
@keyframes poster-highlight-pulse {
  0%, 100% { box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.4); }
  50% { box-shadow: 0 0 0 6px rgba(200, 55, 30, 0.65), 0 8px 20px rgba(0, 0, 0, 0.5); }
}

/* torn-paper alpha masks — posters age with their events.
   light: mask zoomed in so only edge nibbles show
   medium: the full tear
   heavy: two masks intersected, damage compounds */
.poster.torn,
.banner.torn {
  mask-image: var(--mask);
  mask-size: var(--msize, 100% 100%);
  mask-position: center;
  mask-repeat: no-repeat;
}
.poster.torn.heavy {
  mask-image: var(--mask), var(--mask2);
  mask-size: 100% 100%, 108% 108%;
  mask-composite: intersect;
}

/* paper also fades and yellows as it ages */
.poster.age1 { filter: saturate(0.92) brightness(0.98); }
.poster.age2 { filter: saturate(0.82) sepia(0.08) brightness(0.95); }
.poster.age3 { filter: saturate(0.68) sepia(0.18) brightness(0.9); }

/* real glued-paper texture overlay (scan picked per event, random flips) */
.poster .tex {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-size: 100% 100%;
  mix-blend-mode: multiply;
  opacity: 0.75;
  transform: scale(var(--fx, 1), var(--fy, 1));
  z-index: 3;
}

/* — RA flyer posters (photo) — */
.poster-img img {
  display: block;
  width: 100%;
  flex: 1 1 0;
  min-height: 0;
  object-fit: cover;
  background: #222;
}
.poster-img .strip { flex: none; }
.poster-img .strip {
  background: var(--ink);
  color: var(--paper);
  padding: 9px 11px 11px;
}
.poster-img .strip .t {
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  font-size: 13px;
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.poster-img .strip .v {
  font-size: 10.5px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  opacity: 0.65;
  margin-top: 4px;
}

/* — BCN typographic posters — */
.poster-type {
  padding: 22px 18px 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.7' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23p)' opacity='0.07'/%3E%3C/svg%3E");
}
.poster-type .cat {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  border: 1.5px solid currentColor;
  align-self: flex-start;
  padding: 3px 8px;
}
.poster-type .title {
  font-size: clamp(20px, 2.1vw, 30px);
  line-height: 1.02;
  text-transform: uppercase;
  overflow-wrap: break-word;
  flex: 1;
  min-height: 0;
  overflow: hidden; /* long titles get cropped by the A4 edge, like real prints */
}
.poster-type.f-anton .title { font-family: "Anton", sans-serif; letter-spacing: 0.5px; }
.poster-type.f-archivo .title { font-family: "Archivo Black", sans-serif; font-size: clamp(17px, 1.8vw, 25px); }
.poster-type.f-playfair .title { font-family: "Playfair Display", serif; font-style: italic; text-transform: none; }

.poster-type .foot {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 8px;
  border-top: 2px solid currentColor;
  padding-top: 9px;
}
.poster-type .when {
  font-family: "Anton", sans-serif;
  font-size: 17px;
  letter-spacing: 1px;
}
.poster-type .where {
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 1px;
  text-transform: uppercase;
  text-align: right;
  opacity: 0.75;
}

/* pin-to-personal-wall pushpin — centred on top, where the tear masks
   least often bite, so it doesn't get clipped away */
.pin {
  position: absolute;
  top: 7px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 7;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  background: rgba(20, 18, 15, 0.5);
  border-radius: 50%;
  cursor: pointer;
  opacity: 0;
  user-select: none;
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  transition: opacity 0.15s ease, background 0.15s ease, transform 0.12s ease;
}
.pin svg { width: 16px; height: 16px; display: block; }
.poster:hover .pin { opacity: 1; }
.pin:hover { transform: translateX(-50%) scale(1.15); }
.pin.on {
  opacity: 1;
  color: #fff;
  background: #e8443a;
}
/* touch devices have no hover — always show the pin there */
@media (hover: none) {
  .pin { opacity: 0.9; }
}

/* friend-group voting on a shared wall — a permanent ✋ button pinned at
   the poster's right edge, in the same slot vote stickers cascade from
   (see positionSticker() in app.js: right:-13px, top:8% is slot 0), styled
   exactly like a .vote-sticker (below) so it reads as part of the same
   cluster rather than a separate UI control. Unlike everyone else's
   sticker it belongs to no particular voter and is always there — the
   invitation to press it — dimmed until you have, then it doubles as your
   own sticker via its "on" state, so the rest stack starting one slot
   below it. */
.votebox {
  position: absolute;
  z-index: 41; /* above regular stickers (40), so it stays clickable */
  right: -13px;
  top: 8%;
}
.votebtn {
  position: relative;
  display: block;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-size: 34px;
  line-height: 1;
  color: inherit;
  opacity: 0.5;
  transition: opacity 0.15s ease, transform 0.12s ease, filter 0.15s ease;
  text-shadow:
    -1.5px -1.5px 0 #fff, 1.5px -1.5px 0 #fff, -1.5px 1.5px 0 #fff, 1.5px 1.5px 0 #fff,
    0 -2px 0 #fff, 0 2px 0 #fff, -2px 0 0 #fff, 2px 0 0 #fff;
}
.votebtn:hover { transform: scale(1.1); }
.votebtn.on {
  opacity: 1;
  filter: drop-shadow(0 0 5px rgba(228, 0, 43, 0.85));
}
.votebtn .n {
  position: absolute;
  bottom: -2px;
  right: -6px;
  min-width: 15px;
  height: 15px;
  padding: 0 3px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  font-size: 9px;
  text-shadow: none;
  color: #fff;
  background: #e4002b;
  border: 1.5px solid var(--paper, #f2ead8);
  border-radius: 999px;
}

/* date badge on photo posters */
.datebadge {
  position: absolute;
  top: 14px;
  left: 12px;
  z-index: 6;
  background: #c8371e;
  color: #fff;
  font-family: "Anton", sans-serif;
  font-size: 13px;
  letter-spacing: 1px;
  padding: 4px 10px;
  transform: rotate(-2deg);
}

/* Rotten Tomatoes score — cinema posters only, top-right, mirroring the
   date badge on the left */
.rt-badge {
  position: absolute;
  top: 14px;
  right: 12px;
  z-index: 6;
  background: #14a35a;
  color: #fff;
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.3px;
  padding: 4px 9px;
  border-radius: 999px;
  transform: rotate(2deg);
}
.rt-badge.rotten { background: #4a463f; }

/* One ✋ sticker per voter, pinned to the poster's edge — pops in then
   holds (not a fade-out toast): it stays as long as the vote does, stacked
   in vote order (see positionSticker() in app.js). pointer-events must stay
   "auto" (not the poster card's default) so hovering shows the voter's name
   via the title attribute — a stray click still just follows the poster
   link underneath, same as clicking anywhere else on the card. */
.vote-sticker {
  position: absolute;
  z-index: 40;
  font-size: 34px;
  line-height: 1;
  pointer-events: auto;
  cursor: default;
  transform: rotate(var(--tilt, 0deg));
  animation: stickerPop 420ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
  /* emoji glyphs ignore -webkit-text-stroke (they're colour bitmaps, not
     outlined text), so fake a white outline with a ring of shadows instead —
     keeps the sticker readable over busy poster art. */
  text-shadow:
    -1.5px -1.5px 0 #fff, 1.5px -1.5px 0 #fff, -1.5px 1.5px 0 #fff, 1.5px 1.5px 0 #fff,
    0 -2px 0 #fff, 0 2px 0 #fff, -2px 0 0 #fff, 2px 0 0 #fff;
}
@keyframes stickerPop {
  0% { opacity: 0; transform: rotate(var(--tilt, 0deg)) scale(0.3); }
  70% { opacity: 1; transform: rotate(var(--tilt, 0deg)) scale(1.15); }
  100% { opacity: 1; transform: rotate(var(--tilt, 0deg)) scale(1); }
}

.sentinel { column-span: all; height: 1px; }

/* — horizontal ad banners, wheat-pasted between poster rows — */
.banner {
  column-span: all;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  position: relative;
  margin: 12px 0 24px;
  padding: 22px 26px;
  min-height: 120px;
  background: var(--paper);
  transform: rotate(var(--rot, 0deg));
  text-decoration: none;
  filter: saturate(0.96);
  transition: transform 0.18s ease;
}
.banner:hover { transform: rotate(0deg) scale(1.008); }
.banner .tex { background-size: cover; }
.banner .b-title {
  font-family: "Anton", sans-serif;
  font-size: clamp(26px, 4vw, 52px);
  letter-spacing: 3px;
  color: #c8371e;
  text-transform: uppercase;
  text-align: center;
}
.banner .b-sub {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.65;
  text-align: center;
}
.banner img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.banner .adsense-box { width: 100%; }

.empty {
  column-span: all;
  text-align: center;
  font-family: "Anton", sans-serif;
  font-size: 28px;
  opacity: 0.5;
  padding: 80px 0;
}

/* bar above the wall — share your wall / act on a shared one. Lives OUTSIDE
   the multicol .wall (iOS Safari overlaps column-span:all with the columns),
   in a host that mirrors the wall's width/padding; the wall then drops its
   own top padding via body.has-wallbar. */
.wallbar-host {
  max-width: 1700px;
  margin: 0 auto;
  padding: 140px 40px 0;
}
body.has-wallbar .wall { padding-top: 24px; }
.wallbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  margin: 0 0 24px;
  padding: 14px 18px;
  /* solid panel — over the brick wall a translucent tint let the red text vanish */
  background: var(--paper);
  border: 1.5px solid #c8371e;
  border-radius: 10px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
}
/* title + "Shared wall" stacked underneath it, not inline */
.wallbar-title { display: flex; flex-direction: column; gap: 2px; }
.wallbar-label {
  font-family: "Archivo Black", sans-serif;
  font-size: 19px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: #c8371e;
}
/* share + delete pills grouped tightly together */
.wallbar-icons { display: flex; gap: 6px; }
.wallbar-btn {
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  padding: 9px 18px;
  border: 0;
  border-radius: 999px;
  background: #c8371e;
  color: #fff;
  cursor: pointer;
  transition: background 0.15s, transform 0.15s;
}
.wallbar-btn:hover { background: #e4442b; transform: translateY(-1px); }
/* icon-only pill — share/delete on the shared-wall bar, where a text label
   would be redundant (the action is obvious from context + the tooltip) */
.wallbar-icon-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  font-size: 15px;
  border: 0;
  border-radius: 50%;
  background: #c8371e;
  color: #fff;
  cursor: pointer;
  transition: background 0.15s, transform 0.15s;
}
.wallbar-icon-btn:hover { background: #e4442b; transform: translateY(-1px); }
.wallbar-icon-btn.wallbar-delete { background: rgba(20, 18, 15, 0.5); }
.wallbar-icon-btn.wallbar-delete:hover { background: #c8371e; }
.wallbar-sub {
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: #c8371e;
  opacity: 0.6;
}
/* "La meva paret" as a dropdown listing My wall + saved friends' walls */
.wallpick { position: relative; flex: 0 0 auto; }
.wallpick-btn { display: inline-flex; align-items: center; gap: 7px; }
.wallpick-btn .citypick-arrow { border-top-color: currentColor; }
.wallpick-menu { left: auto; right: 0; min-width: 190px; }
/* detached to <body> when open, so it escapes #filters' overflow-x clipping —
   top/right are set inline in JS from the button's actual screen position */
.wallpick-menu--floating { position: fixed; top: 0; z-index: 250; }
.wallpick-sep {
  padding: 8px 12px 4px;
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: #cbbfa8;
  opacity: 0.7;
}
.wallpick-item a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  text-transform: none;
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.3px;
}
.wallpick-x {
  font-size: 16px;
  line-height: 1;
  opacity: 0.45;
  cursor: pointer;
  padding: 0 2px;
}
.wallpick-x:hover { opacity: 1; color: #ff5a3c; }
/* pin-menu "also add to" row, once the poster has landed on that wall */
.pin-menu .wallpick-item a.added { color: #7bbf6a; pointer-events: none; }
/* share dialog — optionally name the wall before sharing */
.share-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(20, 18, 15, 0.55);
  backdrop-filter: blur(3px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}
.share-modal.show { opacity: 1; pointer-events: auto; }
.share-card {
  width: 100%;
  max-width: 380px;
  background: var(--paper);
  border-radius: 14px;
  padding: 24px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.share-title {
  font-family: "Archivo Black", sans-serif;
  font-size: 18px;
  color: var(--ink);
  margin: 0 0 16px;
}
.share-input {
  width: 100%;
  font-family: "Space Grotesk", sans-serif;
  font-size: 15px;
  color: var(--ink);
  background: #fff;
  border: 1.5px solid rgba(200, 55, 30, 0.4);
  border-radius: 8px;
  padding: 11px 14px;
  outline: none;
}
.share-input:focus { border-color: #c8371e; }
.share-input + .edit-label { margin-top: 16px; }
/* labels above each field in the two-field edit-wall modal */
.edit-label {
  display: block;
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.55;
  margin: 0 0 6px;
}
.share-actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 12px;
  margin-top: 18px;
}
.share-cancel {
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--ink);
  background: none;
  border: 0;
  cursor: pointer;
  opacity: 0.6;
}
.share-cancel:hover { opacity: 1; }

/* transient confirmation toast */
.toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translateX(-50%) translateY(20px);
  background: var(--ink);
  color: var(--paper);
  font-family: "Space Grotesk", sans-serif;
  font-weight: 700;
  font-size: 14px;
  padding: 12px 22px;
  border-radius: 999px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 100;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* smaller chips so more fit before the row has to scroll, on the way down
   from desktop to the tablet breakpoint where they become a dropdown */
@media (max-width: 1100px) {
  .filter-chip { font-size: 11px; padding: 5px 10px; letter-spacing: 0.6px; }
  .filters { gap: 6px; }
}
/* categories collapse into the dropdown starting at tablet width — the chip
   row doesn't have room to stay readable/tappable much past this. Date
   chips (.filters.when) stay a row; they're few enough to always fit. */
@media (max-width: 900px) {
  #filters { display: none; }
  .cat-select { display: block; }
  /* "My wall" is reachable via the category <select> here — don't also crowd
     the date row with its chip. */
  .filters.when .wallpick { display: none; }
}

@media (max-width: 640px) {
  .wall { columns: 2 150px; column-gap: 8px; padding: 44px 10px 60px; }
  .wallbar-host { padding: 20px 10px 0; }
  body.has-wallbar .wall { padding-top: 16px; }
  .poster { margin-bottom: 8px; }
  /* too narrow for one line — logo and search share a row (same height,
     search on the right, so that row stays compact), chip rows stack below */
  .topbar { flex-direction: row; flex-wrap: wrap; align-items: flex-start; padding: 10px 12px; gap: 8px; }
  .brand { order: 1; flex: 1 1 auto; min-width: 0; overflow: hidden; }
  .brand-bcn { font-size: 20px; }
  .brand-wall { font-size: 16px; }
  /* .rightbar (search + date chips together on desktop) splits back apart on
     mobile: search stays on the logo's row, date chips join the stacked
     filter rows below, in that original order. */
  .rightbar { display: contents; }
  /* no room to stack the language picker above search in this cramped
     shared row — sits beside it instead (order:2, same as search; source
     order — it's inserted before search — puts it just to search's left) */
  .langpick { order: 2; }
  .search { order: 2; flex: 0 0 auto; }
  /* expanding the input shouldn't force the whole row wider than the topbar
     — cap it and let the brand (flex-shrink, above) give up the room */
  .search.open { flex: 1 1 auto; max-width: 230px; }
  .search.open input { width: 100%; }
  /* date chips (source order: first) then the category dropdown, both
     stretched full-width and stacked — .filters.when needs no per-breakpoint
     override anymore now that it lives here rather than in .rightbar */
  .filterbar { order: 3; flex-direction: column; align-items: stretch; gap: 6px; flex-basis: 100%; }
  /* the dropdown (already shown from 900px down) now stretches to fill its
     full-width row, same as any other stacked control here */
  .cat-select { width: 100%; }
}

/* Crawlable inter-city links at the foot of every wall (server-rendered in
   index.html from the city list). Subtle, brand-toned, wraps on narrow. */
.city-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px 18px;
  padding: 34px 20px 40px;
  margin-top: 20px;
  border-top: 1px solid rgba(242, 234, 216, 0.12);
  font-family: "Space Grotesk", sans-serif;
  font-size: 13px;
}
.city-footer__brand {
  font-family: "Anton", sans-serif;
  letter-spacing: 1px;
  color: #c8371e;
  text-transform: uppercase;
}
.city-footer nav { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 16px; }
.city-footer a {
  color: #cbbfa8;
  text-decoration: none;
  letter-spacing: 0.5px;
  border-bottom: 1px solid transparent;
}
.city-footer a:hover { color: #f2ead8; border-bottom-color: #c8371e; }
.city-footer a.current { color: #f2ead8; border-bottom-color: #c8371e; }

/* City/language dropdowns are detached to <body> (see floatMenu in app.js) so
   the sticky topbar can't clip them — on phones the city list was cropped and
   unusable. Fixed positioning is set inline from the button's screen rect. */
.citypick-menu--floating { position: fixed; z-index: 250; max-height: 70vh; overflow-y: auto; }
