/* ──────────────────────────────────────────────────────────────
   TicketHub shared components — 2026 identity.

   The authoritative definition of the pieces that were previously
   redefined independently inside home.html, browse_events.html and
   _events_grid.html (.event-card, .section-title, .ec-name, .ec-date,
   .ec-price and friends). Page templates should lay these out, not
   restyle them.

   Requires tokens.css and fonts.css to be loaded first.
   ────────────────────────────────────────────────────────────── */

/* ── Layout primitives ──────────────────────────────────────── */

.wrap {
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* The extended mockup lets content breathe to ~2400px on ultra-wide */
.wrap--wide { max-width: var(--measure-wide); }

.section {
  position: relative;
  background-color: var(--bg);
  color: var(--text);
  padding-block: clamp(56px, 7vw, 120px);
}

/* Concrete grain over the paper ground. Kept as a separate layer rather than
   baked into the colour so the same tile serves every light surface. */
.section[data-surface='paper']::before,
.surface-grain::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: var(--tex-grain);
  background-size: 512px 512px;
  opacity: 0.5;
  mix-blend-mode: multiply;
  pointer-events: none;
}

/* Soft smoke wash over the ink ground */
/* The marbled dirty black on its own — the same single-layer treatment the
   footer uses (base.css .site-footer), so the hero, this slab and the footer all
   read as one surface. The concrete grain is deliberately NOT layered on top
   here: it belongs to the paper surfaces, and on near-black it either vanishes
   under multiply or greys the black out under screen.

   .surface-smoke is the escape hatch for ink surfaces that are not .section —
   the homepage hero is the only one so far. It used to hand-roll its own smoke
   layer, which is exactly how the two fell out of sync once already (5027ff5
   retuned the slab and left the hero behind). Same rule, one place. Anything
   opting in needs position: relative and a child that paints above this. */
.section[data-surface='ink']::before,
.surface-smoke::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: var(--tex-smoke);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  opacity: 0.85;
  pointer-events: none;
}

.section > * { position: relative; z-index: 1; }

/* Dark bands in the mockup are inset slabs with big rounded corners rather
   than full-bleed colour changes. */
/* Full width, corners rounding against the paper page — the treatment the
   master artwork (sajt ilustrator file.ai) shows for both this band and the
   hero. No margin-inline: the slab reaches both viewport edges and only the
   corners are cut. */
.section--slab {
  border-radius: var(--radius-xl);
  overflow: hidden;
}

/* ── Section headings ───────────────────────────────────────── */

.section-eyebrow {
  font-family: var(--font-condensed);
  font-weight: 800;
  font-stretch: 85%;
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--red);
  margin-bottom: 0.5rem;
}

.section-title {
  font-family: var(--font-condensed);
  font-weight: 900;
  font-stretch: 75%;
  font-style: italic;
  font-size: clamp(1.75rem, 3.4vw, 3.8rem);
  line-height: 1;
  color: var(--text);
  margin: 0;
}

/* Upright caps variant — "FIND YOUR NIGHT." */
.section-title--upright {
  font-style: normal;
  text-transform: uppercase;
  letter-spacing: 0.005em;
}

.section-title .accent { color: var(--red); }

/* Outward Block opt-in — homepage only.

   Outward's counters are 1.2% of font-size, so the face only reads well above
   ~64px. .section-title itself cannot switch: _events_grid.html combines it
   with .grid-heading (20-30px) and event_shop.html overrides it to 1.3rem, and
   at those sizes Outward is an unreadable black slab. So this is a modifier
   applied by hand in home.html, and it only engages from 900px up — below that
   the homepage headings are ~28px and stay on Archivo italic, which is also
   what the comps show.

   font-style is reset because Outward has no oblique (the handoff's "Round"
   cut is a rounded variant, not a slant), and font-stretch is left alone
   deliberately: it is inert against Outward, which has no width axis, but it
   is exactly what we want Archivo to honour if Outward fails to load. */
@media (min-width: 900px) {
  .section-title--block {
    font-family: var(--font-outward);
    font-style: normal;
    text-transform: uppercase;
    /* .section-title's own clamp bottoms out at 3.4vw, which is only ~31px at
       900px wide — right where this media query switches Outward on, and well
       into the illegible range. This clamp never goes below 56px, which is the
       measured point where the counters survive rasterisation. Upper bound is
       72px: past that the longest editorial heading overflows the 33rem copy
       column by more than one extra line. */
    font-size: clamp(3.5rem, 4.4vw, 4.5rem);
    /* Outward has no left sidebearing and only 12/1000em on the right, so any
       negative tracking is literal ink collision. Positive tracking is also
       what stops words fusing into one slab. */
    letter-spacing: 0.05em;
    line-height: 0.96;
  }
}

/* Hairline rule under an editorial headline */
.title-rule {
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--rule) 22%, var(--text) 100%);
  border: 0;
  margin: 0.85rem 0 1rem;
}

/* ── Buttons ────────────────────────────────────────────────── */

.pill-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55em;
  padding: 0.72em 1.5em;
  border-radius: var(--radius-pill);
  border: 1.5px solid transparent;
  font-family: var(--font-condensed);
  font-weight: 800;
  font-stretch: 88%;
  font-size: 0.85rem;
  letter-spacing: 0.045em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
    border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.pill-btn:active { transform: translateY(1px); }

/* Filled: reads as the inverse of whatever surface it sits on */
.pill-btn--solid {
  background: var(--text);
  color: var(--bg);
  border-color: var(--text);
}
.pill-btn--solid:hover { background: var(--red); border-color: var(--red); color: var(--paper-2); }

.pill-btn--outline {
  background: transparent;
  color: var(--text);
  border-color: var(--text);
}
.pill-btn--outline:hover { background: var(--text); color: var(--bg); }

.pill-btn--red {
  background: var(--red);
  color: var(--paper-2);
  border-color: var(--red);
}
.pill-btn--red:hover { background: var(--red-bright); border-color: var(--red-bright); }

/* ── Event card ─────────────────────────────────────────────── */

.event-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  text-decoration: none;
  color: var(--text);
  transition: transform var(--dur) var(--ease);
  /* --i is the card's index; grids use it to stagger the entrance */
  animation: fadeInUp 0.45s var(--ease) both;
  animation-delay: calc(var(--i, 0) * 55ms);
}

.event-card:hover { transform: translateY(-4px); }
.event-card:focus-visible {
  outline: 2px solid var(--red);
  outline-offset: 6px;
  border-radius: var(--radius-xl);
}

.event-card__media {
  position: relative;
  aspect-ratio: 1 / 1;
  /* 8px flat, not --radius-xl: the card is 148 or 256 square, and the token's
     larger corner ate a visible bite out of artwork that small. */
  border-radius: 8px;
  overflow: hidden;
  background: var(--surface-2);
  box-shadow: var(--shadow-card);
}

.event-card__media img,
.event-card__media picture {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* No hover zoom on the artwork. It was a transform: scale(1.04) on the img,
   which on a drifting carousel meant the card under the pointer grew every
   time the row stopped — two things happening for one gesture. The card's
   own -4px lift is the hover feedback now. */

/* Placeholder when an event has no artwork */
.event-card__media--empty {
  background: var(--ink);
}

.event-card__body { display: flex; flex-direction: column; gap: 0.15rem; }

.event-card__name {
  font-family: var(--font-hero);
  font-weight: 400;
  font-size: 1.15rem;
  line-height: 1.1;
  color: var(--text);
}

.event-card__date {
  font-family: var(--font-hero);
  font-size: 0.95rem;
  line-height: 1.2;
  color: var(--text);
}
/* The day number is red in the mockup */
.event-card__date .day { color: var(--red); }

.event-card__venue,
.event-card__price {
  font-family: var(--font-hero);
  font-size: 0.95rem;
  line-height: 1.2;
  color: var(--text);
}

/* ── Event carousel ─────────────────────────────────────────── */

.carousel {
  /* Named because three rules need the same number: the gap itself, and the
     two max-widths that count cards and gaps to decide where the row stops. */
  --car-gap: clamp(16px, 1.6vw, 28px);
  /* The card is a fixed size, not a fraction of the row — 148 on a phone,
     256 from 900px up. Both are what the artwork is drawn at; a fluid track
     only ever upscaled a 256px source. */
  --card-w: 148px;

  display: grid;
  grid-auto-flow: column;
  /* A bare length, deliberately NOT minmax(0, --card-w). With a 0 minimum a
     track's base size is 0, and the maximize-tracks step then hands the
     container's space out EQUALLY to every track before any of them reaches
     its growth limit — so eight cards in a 358px phone viewport got ~30px
     each instead of one row of 148s that scrolls. A fixed track size has no
     base/limit gap to distribute into: every card is --card-w, the row
     overflows, and overflow-x: auto turns that into the swipe. */
  grid-auto-columns: var(--card-w);
  gap: var(--car-gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  padding-block: 4px;
  /* let the first and last card sit flush with the content measure while the
     row itself still bleeds to the viewport edge */
  padding-inline: var(--gutter);
  margin-inline: calc(-1 * var(--gutter));
}
.carousel::-webkit-scrollbar { display: none; }
.carousel > * { scroll-snap-align: start; }

/* Set by motion.js on a rail it is actually drifting (see initCarouselDrift).

   Snap has to go while it does: mandatory snapping and a scrollLeft written
   every frame are the same property fought over by two owners, and the rail
   ends up jerking back to the nearest card instead of gliding. It stays on
   under prefers-reduced-motion, where the drift never starts and the rail is a
   plain swipeable row — which is also what it is with JS off.

   scroll-behavior is pinned too: base.css sets `smooth` on <html>, and while
   that does not inherit, a stray `smooth` on a scroller would queue an
   animation per frame. */
.carousel.is-drifting {
  scroll-snap-type: none;
  scroll-behavior: auto;
}


/* The card is drawn at 256px from 900px up. The row is NOT capped to a card
   count any more: it used to stop at four cards (1108px) through the laptop
   band and five (1392px) from 1500px, which on anything wider than that ended
   the rail in the middle of the page with the rest of the section empty beside
   it — the row read as broken rather than as a row that continues.

   It now fills its wrap and keeps the same edge bleed the phone layout uses:
   gutter padding in, a negative gutter margin out, so the first card lines up
   with the section heading while the row itself runs past the viewport edge.
   The cards that do not fit are what there is to scroll — which is what a
   carousel is for, and what motion.js's drift slides through. */
@media (min-width: 900px) {
  .carousel { --card-w: 256px; }
}

/* The partial card peeking at the edge carries a red folded corner */
.carousel__peek {
  position: relative;
}
.carousel__peek::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 46px;
  height: 46px;
  background: var(--red);
  border-top-left-radius: var(--radius-xl);
  border-bottom-right-radius: var(--radius-xl);
  pointer-events: none;
}

/* ── Event grid (browse) ────────────────────────────────────── */

/* Same two drawn sizes as the carousel — a card is 148 square or 256 square,
   nowhere in between — so auto-fill counts columns instead of a fluid minmax
   stretching the artwork to whatever a third of the viewport happens to be.
   Tracks are left-packed and the slack sits at the end of the row. */
.events-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, 148px);
  /* Centred on a phone, where a 320px viewport fits one 148 track and a
     start-packed row would leave it stranded against the left edge with 140px
     of slack beside it. */
  justify-content: center;
  gap: clamp(18px, 2vw, 32px);
}

/* From 900px the row is start-packed instead, so its left edge lines up with
   the section heading above it rather than floating in the middle. */
@media (min-width: 900px) {
  .events-grid {
    grid-template-columns: repeat(auto-fill, 256px);
    justify-content: start;
  }
}

/* Type follows the card: 148px of artwork will not carry a 1.15rem name.
   Not scoped to .events-grid — the carousel card is 148 below 900px too. */
@media (max-width: 899.98px) {
  .event-card__name { font-size: 1rem; }
  .event-card__date,
  .event-card__venue,
  .event-card__price { font-size: 0.85rem; }
}

.grid-heading {
  font-size: clamp(1.25rem, 2vw, 1.9rem);
  margin-bottom: clamp(18px, 2.4vw, 32px);
}
.grid-heading span { color: var(--red); }

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: clamp(40px, 7vw, 90px) 20px;
  text-align: center;
}

.empty-icon {
  width: 62px;
  height: 62px;
  display: grid;
  place-items: center;
  border: 2px solid var(--line);
  border-radius: 50%;
  color: var(--text-dim);
  font-size: 1.4rem;
}

.empty-title {
  font-family: var(--font-condensed);
  font-weight: 900;
  font-stretch: 78%;
  font-size: clamp(1.05rem, 1.8vw, 1.5rem);
  text-transform: uppercase;
  color: var(--text);
}

/* ── Torn paper / banner shapes ─────────────────────────────── */
/* Outlines are inline SVG (see other/_shapes.html) so they can be stroked;
   these rules position and colour them. */

/* fill and stroke are set on the <svg> itself, not on a descendant selector:
   the geometry lives inside a <symbol> reached through <use>, and CSS
   descendant selectors do not cross that shadow boundary. Both properties
   inherit, so they reach the shape; vector-effect does not inherit and is
   therefore a presentation attribute on the shapes in other/_shapes.html. */
.torn {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  fill: none;
  stroke: var(--rule);
  stroke-width: 1.5;
}

/* Photo sitting inside an offset jagged frame */
.torn-frame {
  position: relative;
  display: inline-block;
  isolation: isolate;
}
.torn-frame > img,
.torn-frame > picture {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: auto;
}
.torn-frame .torn {
  /* the outline sits well proud of the photo on all sides, as in the comp */
  inset: -12%;
  width: 124%;
  height: 124%;
  z-index: 0;
}

/* Headline inside a torn banner */
.torn-banner {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* the comp's ribbon runs most of the section width rather than hugging
     the words, so the torn ends read as part of the composition */
  width: min(1100px, 94%);
  padding: 1.5em 3.2em;
}
/* :not(.torn) matters — a bare `> *` would override the outline's
   position:absolute and drop it into the flow above the text. */
.torn-banner > *:not(.torn) { position: relative; z-index: 1; }

/* Small torn tag — "Claim your card." */
.torn-tag {
  position: relative;
  display: inline-block;
  padding: 0.9em 3.1em;
  font-family: var(--font-hero);
  font-size: clamp(0.95rem, 1.3vw, 1.25rem);
  color: var(--paper-2);
  white-space: nowrap;
}
.torn-tag > span { position: relative; z-index: 1; }
.torn-tag .dot { color: var(--red); }

/* ── Editorial block ────────────────────────────────────────── */

.editorial {
  display: grid;
  gap: clamp(24px, 4vw, 72px);
  align-items: center;
  grid-template-columns: 1fr;
}

@media (min-width: 860px) {
  .editorial { grid-template-columns: 1fr 1fr; }
  /* --reverse swaps the photo to the right; its copy keeps the default
     left alignment, matching the comp's alternating rhythm */
  .editorial--reverse .editorial__media { order: 2; }
  .editorial--reverse .editorial__copy { order: 1; }
}

/* Sized in rem, not ch: ch resolves against this element's own 1rem font,
   which came out ~350px and wrapped "Find your night." onto two lines. */
.editorial__copy { max-width: 33rem; }

/* Copy that sits to the right of its photo is right-aligned, and its rule
   runs the other way so the heavy end still meets the text edge. */
.editorial__copy--right {
  text-align: right;
  margin-left: auto;
}
.editorial__copy--right .title-rule {
  background: linear-gradient(270deg, transparent, var(--rule) 22%, var(--text) 100%);
}

/* Stacked layout: the copy runs the full column and reverts to left-aligned.
   Leaving it right-aligned at 33rem pushed it past the viewport edge. */
@media (max-width: 859px) {
  .editorial__copy { max-width: 100%; }
  .editorial__copy--right {
    text-align: left;
    margin-left: 0;
  }
  .editorial__copy--right .title-rule {
    background: linear-gradient(90deg, transparent, var(--rule) 22%, var(--text) 100%);
  }
}

.editorial__body {
  font-family: var(--font-condensed);
  font-weight: 700;
  font-stretch: 90%;
  font-size: 0.9rem;
  line-height: 1.5;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #3a3a3a;
}

.editorial__sub {
  font-family: var(--font-condensed);
  font-weight: 900;
  font-stretch: 75%;
  font-size: clamp(1rem, 1.6vw, 1.6rem);
  line-height: 1;
  text-transform: uppercase;
  color: var(--text);
  margin-top: 0.15em;
}

/* ── Forms ──────────────────────────────────────────────────── */

.input-field {
  width: 100%;
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--radius);
  color: var(--text);
  padding: 0.7em 1em;
  font-family: var(--font-ui);
  font-size: 0.95rem;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.input-field::placeholder { color: var(--text-dim); }
.input-field:focus {
  outline: none;
  border-color: var(--red);
  box-shadow: 0 0 0 3px rgba(202, 51, 51, 0.15);
}

/* ── Card ───────────────────────────────────────────────────── */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.card:hover { border-color: var(--red); }

/* ── Motion ─────────────────────────────────────────────────── */

@keyframes fadeInUp {
  from { opacity: 0; transform: translateY(18px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes scaleIn {
  from { opacity: 0; transform: scale(0.93); }
  to { opacity: 1; transform: scale(1); }
}

.animate-in { animation: fadeInUp 0.45s var(--ease) both; }
.animate-in-d1 { animation: fadeInUp 0.45s 0.05s var(--ease) both; }
.animate-in-d2 { animation: fadeInUp 0.45s 0.1s var(--ease) both; }
.animate-in-d3 { animation: fadeInUp 0.45s 0.15s var(--ease) both; }
.animate-in-d4 { animation: fadeInUp 0.45s 0.2s var(--ease) both; }
.animate-in-d5 { animation: fadeInUp 0.45s 0.25s var(--ease) both; }
.animate-scale-in { animation: scaleIn 0.4s var(--ease) both; }

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    /* delays must go too, not just durations: the payment page holds its green
       panel on a 5.6s animation-delay, which would otherwise still make a
       reduced-motion visitor wait before the panel cleared. */
    animation-delay: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Utilities ──────────────────────────────────────────────── */

.text-accent { color: var(--red); }
.text-dim { color: var(--text-dim); }
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
