/**
 * @file
 * Hub (collection) card.
 *
 * A "hub" on Mosman Events is a themed collection — Creation Space, Storytimes,
 * Spring School Holidays, HSC Support, Law Week. In the old theme hubs and
 * events shared the same `.detail` markup and looked identical, so a hub read as
 * an event with a long description.
 *
 * THE LOOK
 * --------
 * The card IS the artwork: one square picture, edge to edge, with a pane of
 * glass lying over its bottom third carrying the words. The picture continues
 * under the glass — blurred, moving out of focus rather than stopping — and the
 * join between the two is an arc, the picture dipping into the plate at the
 * centre exactly as a listing card's picture dips into its body.
 *
 * WHAT THIS REPLACED
 * ------------------
 * An instrument panel: a 16:9 letterbox, a numbered plate in the corner, a
 * measurement mesh ruled over the artwork. It was a coherent idea and it was
 * the wrong one — on a page of them the eye read the boxes and the numbering
 * before it read the collections, and the artwork, which is the only thing that
 * tells one hub from another at a glance, was the smallest part of the card.
 * The mesh and the number are gone with it; the "when" chip, the title, the
 * summary and "See what's on" are unchanged, and a screen reader hears exactly
 * what it heard before.
 *
 * IMAGE HANDLING
 * --------------
 * The artwork is square and fills the card. It used to be letterboxed into a
 * 16:9 panel, because hub graphics carry type and `cover` cut the Creation
 * Space wordmark in half; square artwork in a square card crops nothing, so
 * that reason has gone. Both --hub-media-ratio and --hub-media-fit are still
 * tokens, and setting the fit back to `contain` restores letterboxing.
 */

.hub-card {
  position: relative;

  /*
   * THE PICTURE IS THE CARD.
   *
   * It used to be a 16:9 panel with the artwork letterboxed into it, then a
   * body underneath: two stacked boxes, and on a page of them the eye read the
   * boxes before it read the collections. Now the square artwork fills the
   * whole card and the words sit on a plate over its foot.
   *
   * The ratio is a token, so a site that goes back to wide artwork changes one
   * line rather than this rule. Nothing inside can make the card taller: the
   * plate is positioned, and the title and summary are line-clamped, so the
   * square holds whatever the content is.
   */
  aspect-ratio: var(--hub-media-ratio);
  overflow: hidden;
  border-radius: var(--card-radius);
  border: 1px solid var(--card-border);
  /*
   * THE BACKGROUND IS NOW ONLY SEEN WHEN THERE IS NO PICTURE.
   *
   * A hub with no artwork — and the split second before a lazy image decodes —
   * shows this: the collection's own colour washing up from the foot over the
   * brand tint, exactly as an event card does. With a picture in place the
   * media layer covers it completely.
   */
  background:
    linear-gradient(
      to top,
      color-mix(in oklab, var(--card-tint) var(--card-wash), transparent) 0%,
      transparent 45%
    ),
    linear-gradient(
      160deg,
      color-mix(in srgb, var(--me-magenta) 7%, transparent) 0%,
      transparent 42%
    ),
    var(--card-bg);
  box-shadow: var(--lift-rest-shadow);
  transition:
    transform var(--duration-base) var(--ease-out),
    border-color var(--duration-base) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out);
}

/* The lift only; see card-hover.css for the rim. */
.hub-card:hover {
  transform: translateY(var(--lift));
}

.hub-card:focus-within {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* ---------------------------------------------------------------------------
 * Media
 * ------------------------------------------------------------------------ */
/*
 * The media fills the card and everything else sits on top of it. It is a grid
 * so the image and its scrim can share one cell; `inset: 0` rather than a
 * height, so it follows the card's ratio without repeating it.
 */
.hub-card__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: grid;
  place-items: stretch;
  background: var(--hub-media-bg);
  overflow: hidden;
}

.hub-card__media a,
.hub-card__media picture,
.hub-card__media .field,
.hub-card__media .field__item {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  grid-area: 1 / 1;
}

.hub-card__media img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: var(--hub-media-fit);
  /*
   * Top, not centre.
   *
   * A square crop of a wider graphic keeps its middle band by default, which is
   * where a poster's title usually is not. Anchoring to the top keeps the
   * masthead of a flyer and the faces in a photograph, and gives up the ground
   * at the bottom — which is under the plate anyway.
   */
  object-position: center top;
  /* A touch of desaturation so a wall of brightly coloured hub graphics reads
     as one set; full colour returns on hover. */
  filter: saturate(0.82) contrast(1.02);
  transition:
    filter var(--duration-slow) var(--ease-out),
    transform var(--duration-slow) var(--ease-out);
}

.hub-card:hover .hub-card__media img,
.hub-card:focus-within .hub-card__media img {
  filter: saturate(1.08) contrast(1.04);
  transform: scale(1.03);
}

/*
 * "When" chip — now the first line ON the plate.
 *
 * It used to be pinned to the bottom-left of the artwork. The plate now covers
 * that corner, and pinning it just above the plate would mean guessing the
 * plate's height from a stylesheet while the plate's height comes from its own
 * text. Inside the plate it needs no arithmetic and cannot collide.
 */
.hub-card__when {
  align-self: start;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
  padding: 0.25rem 0.55rem;
  border: 1px solid color-mix(in srgb, var(--me-magenta) 55%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--me-black) 80%, transparent);
  color: var(--me-magenta-300);
  font-size: var(--step--2);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  line-height: 1.2;
}

.hub-card__pulse {
  inline-size: 0.4rem;
  block-size: 0.4rem;
  flex: none;
  border-radius: 50%;
  background: var(--me-magenta);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--me-magenta) 70%, transparent);
  animation: hub-pulse 2.4s var(--ease-out) infinite;
}

@keyframes hub-pulse {
  70% { box-shadow: 0 0 0 0.5rem transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* The magenta rule that marks the card as a collection rather than an event.
   It is a scale transform rather than a width change so it animates on the
   compositor.

   It has moved from the foot of the picture to the foot of the CARD. Where it
   was, it is now underneath the plate and invisible; where it is, it underlines
   the plate and is the one piece of full-strength brand colour on a card that
   is otherwise the photograph's. */
.hub-card::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 5;
  pointer-events: none;
  block-size: 3px;
  background: linear-gradient(
    to right,
    var(--me-magenta) 0%,
    color-mix(in srgb, var(--me-magenta) 35%, transparent) 100%
  );
  transform-origin: left center;
  transform: scaleX(0.34);
  transition: transform var(--duration-base) var(--ease-out);
}

.hub-card:hover::after,
.hub-card:focus-within::after {
  transform: scaleX(1);
}

/* ---------------------------------------------------------------------------
 * The plate.
 *
 * A pane of glass lying on the bottom third of the photograph. The picture does
 * not stop at its edge — it carries on underneath, out of focus, the way
 * something looks through water.
 *
 * WHAT MAKES IT READ AS GLASS RATHER THAN AS A PANEL
 *
 * Five things, and transparency on its own is none of them:
 *
 *  - A BLURRED COPY OF THE ARTWORK, lying under the plate. Out of focus is the
 *    only one of these the eye reads as THICKNESS; a sharp picture at low
 *    opacity reads as a faded picture, not as something seen through anything.
 *    It is a real second image — a thumbnail, a few kilobytes — and NOT
 *    `backdrop-filter`, which is what this used to be and what made the card
 *    flicker while the page scrolled. See .hub-card__body::before.
 *  - a saturation lift, so what comes through keeps the picture's colour
 *    instead of turning grey.
 *  - THE REFRACTION BAND. The top 1.5rem of the plate is nearly clear and the
 *    scrim has not started yet, so the artwork swims through the edge at close
 *    to full strength; from there down the glass thickens to its working
 *    density over about an inch. This is where the "under water" is: at the
 *    edge, where the eye goes, and where no text sits.
 *  - a bright hairline riding the arc, and a dark one along the bottom: the two
 *    edges of a physical pane catching the light.
 *  - the arc itself.
 *
 * THE ARC RUNS THE SAME WAY AS THE LISTING'S
 *
 * The picture dips INTO the plate at the centre — the plate's top edge is
 * lowest in the middle — which is exactly what an event card's picture does
 * over its body, and what every band on the site does over the page. An earlier
 * version curved the other way, the plate bulging up, on the argument that it
 * is an object lying on the card rather than a band cut out of it. That reads
 * as a different component on a page that also shows event cards.
 *
 * A concave edge is the one shape `border-radius` cannot draw, so this is the
 * footer's technique at card scale: a hard-edged elliptical MASK takes a bite
 * out of the top. If a browser ignores the mask the plate is a rectangle with a
 * straight top — the same graceful failure the footer has, and the text stays
 * exactly as legible.
 *
 * WHY IT IS AS DENSE AS IT IS UNDER THE WORDS — the part not to "fix" later.
 * The text has to clear WCAG AA over artwork nobody has vetted, including the
 * near-white Spring School Holidays graphic. Scrim and glass together let about
 * 8% of the picture through where the words are, which is the most that can and
 * still keep the accent link above 4.5:1 on a white image. The band at the top
 * is a different matter: nothing is written on it, so it can be as clear as it
 * likes. See --hub-scrim and --hub-glass in css/base/tokens.css.
 *
 * THERE IS NO `backdrop-filter` ANYWHERE IN THIS CARD, AND THERE MUST NOT BE.
 * The first version of the plate used one, and it flickered: a filtered surface
 * has to re-read what is behind it while the page moves, and with the header
 * filtered as well the two of them tore the page between them. Both are gone —
 * see the note on .site-header. A blurred bitmap costs one rasterisation and
 * then behaves like any other image, which is why this version can be used on
 * every card without the cost the medallion's blur had.
 * ------------------------------------------------------------------------ */
.hub-card__body {
  position: absolute;
  z-index: 3;
  inset-inline: 0;
  inset-block-end: 0;
  /* A third of the card, unless the words need more. No maximum: a plate that
     had to grow would be clipped at the card's top edge, which is picture, and
     capping it would instead cut the words off at the bottom. The summary is
     line-clamped so it does not come up in practice. */
  min-block-size: 33%;
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  /* The top padding clears the refraction band, so the first line of type
     starts on glass that is already at working density. */
  padding: calc(var(--hub-refract) + var(--space-2xs)) var(--space-m) var(--space-m);

  /*
   * THE BITE. Same geometry as the footer's, at component scale: an ellipse as
   * wide as the plate and --arc-sm deep, centred on the top edge, with both
   * colour stops at 100% so the edge is hard rather than feathered.
   */
  -webkit-mask-image: radial-gradient(50% var(--arc-sm) at 50% 0%, transparent 100%, #000 100%);
  mask-image: radial-gradient(50% var(--arc-sm) at 50% 0%, transparent 100%, #000 100%);

  /*
   * `mask-repeat: no-repeat` matters as much as the image.
   *
   * The blurred layer below is deliberately larger than the plate so its blur
   * has material to sample at the edges. Without this the mask would TILE
   * outside the border box and that overspill would paint; with it, everything
   * past the plate's own box is simply masked away.
   */
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}

/*
 * THE WATER: a blurred copy of the artwork, lying under the plate.
 *
 * `--card-img` is a thumbnail of the same picture the card is showing, set
 * inline by the template. Scaling a 100px image up to the width of a card is
 * already most of the blur; the filter finishes it and lifts the saturation so
 * what comes through keeps the artwork's colour.
 *
 * HOW IT LINES UP WITH THE REAL PICTURE. The card is square and so is the
 * artwork, so `100% auto` makes this copy exactly as tall as the card, and
 * anchoring it to the bottom puts it exactly where the card's own image is. The
 * picture appears to carry on under the glass because it does.
 *
 * The overscan — 112% wide, pushed a little below the bottom — is so the blur
 * has something to sample at the left, right and bottom edges. Without it the
 * layer fades to nothing at its own borders and the plate gets a vignette. The
 * mis-scale that buys is a few percent, under a blur that makes the picture
 * unrecognisable anyway.
 *
 * `z-index: -1` puts it behind the plate's own content but in front of the
 * plate's (empty) background, and the gradient layer below is a later sibling
 * at the same depth, so it paints on top of this. Order, bottom to top: water,
 * glass, text.
 */
.hub-card__body::before,
.hub-card__body::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

.hub-card__body::before {
  background: var(--card-img, none) no-repeat center calc(100% + 1rem) / 112% auto;
  filter: blur(10px) saturate(160%);
}

/* The glass itself, over the water. */
.hub-card__body::after {
  background:
    /*
     * The specular edge, riding the arc.
     *
     * A straight highlight across the top would be cut in half by the bite, so
     * this is the mask's own ellipse grown by 2px: transparent inside, a thin
     * bright line immediately outside, nothing beyond. It follows the curve
     * because it IS the curve.
     */
    radial-gradient(
      50% var(--arc-sm) at 50% 0%,
      transparent 100%,
      color-mix(in srgb, var(--me-white) 20%, transparent) 100%,
      color-mix(in srgb, var(--me-white) 20%, transparent) calc(100% + 2px),
      transparent calc(100% + 2px)
    ),
    /* The under-edge: the pane's own shadow on itself. */
    linear-gradient(
      to top,
      color-mix(in srgb, var(--me-black) 30%, transparent) 0,
      transparent 2px
    ),
    /*
     * The glass, thickening downwards. Nearly clear at the edge, working
     * density by the end of the refraction band, and that density the rest of
     * the way down.
     */
    linear-gradient(
      to bottom,
      color-mix(in srgb, var(--card-bg) 12%, transparent) 0,
      color-mix(in srgb, var(--card-bg) var(--hub-glass), transparent) var(--hub-refract),
      color-mix(in srgb, var(--card-bg) var(--hub-glass), transparent) 100%
    ),
    /*
     * The scrim, under the glass and measured from the plate's own top rather
     * than painted on the photograph.
     *
     * It used to be a gradient over the picture itself, which meant it had to
     * start well above the plate to avoid a visible edge — and that darkened
     * the very band the picture is supposed to swim through. Measured from the
     * plate instead, it can be nothing at all where the glass is clear and full
     * strength two centimetres lower, where the words are. The plate goes from
     * clear water to deep water down its own height, which is what deep water
     * does.
     */
    linear-gradient(
      to bottom,
      transparent var(--hub-refract),
      color-mix(in srgb, var(--me-black) var(--hub-scrim), transparent) calc(var(--hub-refract) + 2rem),
      color-mix(in srgb, var(--me-black) var(--hub-scrim), transparent) 100%
    );
}


.hub-card__title {
  font-size: var(--step-1);
  line-height: var(--leading-snug);
}

.hub-card__title a {
  color: var(--text-primary);
  text-decoration: none;
}

/*
 * The whole card is the link target — and it takes a negative inset to say so.
 *
 * The plate is `position: absolute`, so it is the containing block for anything
 * positioned inside it, and `inset: 0` here covered the plate alone: the
 * picture above it, which is most of the card, was not clickable. (It was the
 * same before this layout, for the same reason — the body was `position:
 * relative`.)
 *
 * -300% of the plate's own height reaches past the top of the card for any
 * plate down to a third of it, and the card's `overflow: hidden` clips both the
 * paint and the hit area back to the card. Nothing outside the card becomes
 * clickable.
 */
.hub-card__title a::after {
  content: "";
  position: absolute;
  inset: -300% 0 0 0;
  z-index: 4;
}

.hub-card:hover .hub-card__title a {
  color: var(--me-magenta-300);
}

.hub-card__summary {
  color: var(--text-secondary);
  font-size: var(--step--1);
  line-height: var(--leading-normal);
}

.hub-card__more {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin-block-start: auto;
  padding-block-start: var(--space-s);
  color: var(--me-magenta-300);
  font-size: var(--step--2);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

/* A hairline that draws itself in from the left on hover, above the label.
   Anchored to `.hub-card__more` itself (hence its `position: relative`); an
   earlier draft anchored it to the card body and had to guess the label height
   in ems to place it. */
.hub-card__more::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-start: calc(var(--space-s) / 2);
  block-size: 1px;
  background: var(--border-subtle);
  transform-origin: left center;
  transform: scaleX(0.18);
  transition:
    transform var(--duration-base) var(--ease-out),
    background-color var(--duration-base) var(--ease-out);
}

.hub-card:hover .hub-card__more::before,
.hub-card:focus-within .hub-card__more::before {
  transform: scaleX(1);
  background: color-mix(in srgb, var(--me-magenta) 55%, transparent);
}

.hub-card__more-arrow {
  transition: transform var(--duration-base) var(--ease-out);
}

.hub-card:hover .hub-card__more-arrow,
.hub-card:focus-within .hub-card__more-arrow {
  transform: translateX(0.3rem);
}

/* ---------------------------------------------------------------------------
 * Reduced motion: keep every state change, drop the movement.
 * ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .hub-card,
  .hub-card__media img,
  .hub-card__more-arrow,
  .hub-card__more::before,
  .hub-card::after {
    transition: none;
  }

  .hub-card:hover {
    transform: none;
  }

  .hub-card:hover .hub-card__media img,
  .hub-card:focus-within .hub-card__media img {
    transform: none;
  }

  .card-sweep {
    display: none;
  }

  .hub-card__pulse {
    animation: none;
  }
}

/* ---------------------------------------------------------------------------
 * The section's own "See all" control.
 *
 * Centred under the grid rather than floated opposite the heading, which is
 * where the old "All collections" link sat: at the top of a section it invites
 * you to leave before you have seen anything.
 * ------------------------------------------------------------------------ */
.hubs__actions {
  display: flex;
  justify-content: center;
  margin-block-start: var(--space-l);
}

/*
 * The same control as the listing's "See 111 more events".
 *
 * That one is `.filter-more__button`, and the only thing it adds to `.button`
 * is a width floor: `min(18rem, 100%)`. Two outlined pills side by side on the
 * landing page, one of them noticeably narrower because its sentence is
 * shorter, read as two different components. Repeating the measure here rather
 * than sharing a class keeps each section's control in its own file — but the
 * value is deliberately the same one, and should move with it.
 */
.hubs__actions .button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-inline-size: min(18rem, 100%);
  text-decoration: none;
}

/* While the rest of the list is being fetched. */
.hubs__actions .button.is-loading {
  opacity: 0.7;
  pointer-events: none;
}

/*
 * RETIRED: the count pill.
 *
 * The number now lives in the control's own sentence — "See 4 more hubs" —
 * because that is how the event listing says it, and the section immediately
 * above this one is that listing. Kept as a definition rather than deleted
 * because `data-me-reveal-count` is still part of reveal.js's markup contract:
 * a block that ships the element and no label patterns still gets a badge.
 */
.hubs__count {
  display: inline-grid;
  place-items: center;
  min-inline-size: 1.6em;
  padding-inline: 0.35em;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--me-magenta) 22%, transparent);
  color: var(--me-magenta-300);
  font-size: var(--step--2);
  font-weight: var(--weight-bold);
  line-height: 1.6;
}

.hubs__count:empty {
  display: none;
}
