/* =============================================================================
   FEATURED LOCATIONS GRID — [gr_facility_locator view="grid"]
   Depends on guardian-facility-locator-tokens.css for every --gfl-* value.
   MOBILE OVERRIDES LIVE AT THE END OF THIS FILE. Media queries contribute no
   specificity, so an override placed above its base rule silently loses.
   ============================================================================= */

/* -----------------------------------------------------------------------------
   Colour mapping notes (see task-11-report.md for the full table):
   - --gfl-primary / --gfl-accent map 1:1 onto the Figma teal (#004A62) and
     amber (#F2A11F) literals.
   - --gfl-link stands in for Figma's lighter caption teal (#5292A7); no
     lighter-teal token exists in the shared set, so this reuses the nearest
     semantic token rather than inventing a one-off shade.
   - --gfl-border / --gfl-surface-2 stand in for the card border and the empty-
     state notice panel elsewhere in this file, on the same "no matching
     near-white token" reasoning. The thumbnail rail (Section 4) is a stricter
     Figma match and does NOT reuse these two: Figma's rail wash (#FAFAF9) and
     hairline (#F2F1F0) are distinct hex values from --gfl-surface-2 (#F5F5F3)
     and --gfl-border (#D2E6ED) — reusing the tokens there would visibly miss
     the design — so the rail uses #FAFAF9 / #F2F1F0 as plain literals instead.
   - The pill background (#E5EFF2) and the card shadow's brand tint
     (rgba(0,74,98,0.05) in Figma — literally --gfl-primary's own RGB at 5%
     alpha) are reproduced with color-mix() against --gfl-primary so they
     track the live Elementor value instead of a frozen literal.
   - Pure white/black and the photo scrim's black gradient are left as plain
     literals: they are not brand colours, no --gfl-* token represents them,
     and this matches the existing precedent at `.fl-verify-link { color: #fff }`.
   - The CTA-call button intentionally does NOT use Figma's white-on-amber
     text: measured contrast is ~2.1:1 (fails 4.5:1). It uses --gfl-primary
     text on the amber fill instead (~4.6:1) — see task-11-report.md Step 7.
   ----------------------------------------------------------------------------- */

.gfl-grid,
.gfl-grid *,
.gfl-grid *::before,
.gfl-grid *::after {
  box-sizing: border-box;
}

/* =============================================================================
   1. LAYOUT — grid, list, card shell
   ============================================================================= */

.gfl-grid {
  width: 100%;
  font-family: var(--gfl-font, "IBM Plex Sans", sans-serif);
}

/* Typed AND prefixed with .gfl-grid to reach (0,2,1): this is the plugin's first surface
   that renders a <ol>/<ul> into host page content (the map view never renders a list), so
   there is no existing list-style reset anywhere in guardian-facility-locator-public.css to
   fall back on. A theme rule like `.entry-content ol { list-style: decimal; padding-left:
   40px }` sits at (0,1,1) — a bare `ol.gfl-grid-list` would only tie that (also (0,1,1)),
   winning by source order alone, which depends on enqueue order (Elementor kit styles can
   load late). `.gfl-grid` is the shortcode's outermost wrapper and always present, so
   prefixing it costs nothing and turns the tie into a real (0,2,1) win on the specificity
   math itself, the same way every other armoured rule in this file wins. */
.gfl-grid ol.gfl-grid-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 30px;
}

.gfl-grid-card {
  position: relative;
  border-radius: 24px;
  overflow: hidden;
  background: var(--gfl-surface, #FCFDFF);
  /* Figma's card hairline is a warm grey, not the light blue --gfl-border carries for the map
     view. Kept as a literal here rather than retuning the shared token, which 14 map-view rules
     also read — same reasoning as the thumbnail rail's #FAFAF9 / #F2F1F0 in Section 4. */
  border: 1px solid #F2F1F0;
  box-shadow: 0 8px 24px 0 rgba(0, 74, 98, 0.05);                                   /* fallback: --gfl-primary's RGB at 5% */
  box-shadow: 0 8px 24px 0 color-mix(in srgb, var(--gfl-primary, #004A62) 5%, transparent);
  transition: box-shadow 0.2s ease;
}

/* Expanded state keeps the SAME 24px radius as collapsed (Figma draws 24px collapsed / 32px
   expanded, but the client asked for one consistent radius) — only the shadow deepens.
   border-radius is intentionally not in the transition list above: it no longer changes
   between states, so animating it would be dead weight. */
.gfl-grid-card.is-open {
  box-shadow: 0 12px 32px 0 rgba(0, 74, 98, 0.05);                                  /* fallback: --gfl-primary's RGB at 5% */
  box-shadow: 0 12px 32px 0 color-mix(in srgb, var(--gfl-primary, #004A62) 5%, transparent);
}

/* =============================================================================
   2. COLLAPSED SUMMARY — the always-visible photo header/toggle button
   ============================================================================= */

/* Selector re-rooted to .gfl-grid-hero-wrap (not .gfl-grid-card) now that the summary button
   lives inside its own wrapper alongside the paging dots — same (0,2,1) specificity tier as
   before, the ancestor class just changed. */
.gfl-grid-hero-wrap > button.gfl-grid-summary {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  width: 100%;
  height: 260px;
  padding: 40px;
  border: 0;
  margin: 0;
  background-color: var(--gfl-primary, #004A62);                                    /* shows when a card has no image */
  /* The carousel viewport. This element is stationary while .gfl-grid-hero-track slides
     inside it, so the clip has to live here — putting it on the track would clip the track's
     own slides and move the clip along with them. */
  overflow: hidden;
  color: #FFFFFF;                                                                    /* non-brand: legible text over a photo/scrim, no --gfl-* token represents "on-photo white" */
  font: inherit;
  text-align: left;
  cursor: pointer;
  position: relative;
  isolation: isolate;
  /* Animation 3: grows to the 460px hero height on open. */
  transition: height 0.35s ease;
}

/* (0,3,1). Restates color even though it does not change — themes ship
   button:hover { color: #fff } at (0,1,1), and specificity cannot win a
   property that was never declared. */
.gfl-grid-hero-wrap > button.gfl-grid-summary:hover,
.gfl-grid-hero-wrap > button.gfl-grid-summary:focus-visible {
  color: #FFFFFF;
  /* background-color is restated for the same reason as color, and it is not redundant: the
     base rule sets it, and an Elementor kit's `.elementor-kit-N button:hover` is also (0,2,1) —
     an exact tie that wins on source order whenever the kit CSS enqueues after the plugin's.
     Most visible on a featured facility with no images, where this fill IS the hero. */
  background-color: #004A62;                                                        /* fallback: pre-var() literal */
  background-color: var(--gfl-primary, #004A62);
}

.gfl-grid-hero-wrap > button.gfl-grid-summary:focus-visible {
  outline: 2px solid var(--gfl-primary);
  outline-offset: -4px;
}

/* Animation 3: the summary button IS the single hero now (the old .gfl-grid-hero duplicate is
   gone), so it grows 260px -> 460px on open. (0,4,1) — a state qualifier on top of the (0,2,1)
   base rule above, touching only `height` so it never competes with the hover/focus rules. */
.gfl-grid-card.is-open .gfl-grid-hero-wrap > button.gfl-grid-summary {
  height: 460px;
}

/* The sliding track: one <span> holding all of a card's hero photos side by side, translated
   horizontally to bring the active one into view. <span>, not <div> — it lives inside the
   .gfl-grid-summary <button>, and a <div> inside a <button> is invalid HTML. Positioned exactly
   where the old single <img> sat (absolute, inset: 0), behind the scrim/overlay text/chevron
   (z-index: 0, same as before).

   DO NOT put `overflow: hidden` on this element. It is the thing that MOVES, so clipping here
   clips its own slides to its own box: slides 2..n sit outside that box and vanish, and
   translating the track then carries the one visible slide off-screen, leaving the bare
   background. The clip belongs on the stationary viewport around it — see `overflow: hidden`
   on .gfl-grid-summary above. */
.gfl-grid-hero-track {
  position: absolute;
  inset: 0;
  display: flex;
  width: 100%;
  height: 100%;
  z-index: 0;
  /* Animation 3b: the horizontal slide. translateX(-N * 100%) moves exactly N container-widths,
     because each slide below is flex: 0 0 100% of this same box. */
  transition: transform 0.4s ease-out;
}

.gfl-grid-hero-slide {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Closed: weighted toward the lower-mid of the photo. Most facility photos are shot
     straight-on with plain sky filling the upper third, which is exactly what a short 260px
     bar has no room for — so the crop favours the building/entrance instead. */
  object-position: 50% 70%;
  /* Animation 3: recomposes the crop as the button grows, rather than just revealing more of
     the same framing. */
  transition: object-position 0.35s ease;
}

/* Open: shifts up toward the frame's true center of interest, using the extra 460px of height
   to bring the sky/architecture context into frame instead of leaving it as dead space. Not a
   button/anchor rule, so no color/text-decoration restatement applies here. */
.gfl-grid-card.is-open .gfl-grid-hero-slide {
  object-position: 50% 30%;
}

/* Scrim so white overlay text stays legible over any photo. Plain black-alpha
   gradient — not a brand colour, no --gfl-* token represents it. */
.gfl-grid-hero-wrap > button.gfl-grid-summary::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 40%, rgba(0, 0, 0, 0.7) 100%);
  z-index: 1;
  pointer-events: none;
}

.gfl-grid-summary-text {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
  flex: 1 1 auto;
  /* Animation 3: fades out on open — the name/location are duplicated in the body below, so
     nothing is lost. */
  transition: opacity 0.3s ease;
}

/* Not a button/anchor rule (it's a <span>), so no color/text-decoration restatement applies. */
.gfl-grid-card.is-open .gfl-grid-summary-text {
  opacity: 0;
  pointer-events: none;
}

.gfl-grid-summary-name {
  font-family: var(--gfl-heading, "DM Serif Text", serif);
  font-size: 34px;
  line-height: 1.15;
  font-weight: 400;
  color: #FFFFFF;
  margin: 0;
  overflow-wrap: break-word;
  word-break: break-word;
  white-space: normal;
}

.gfl-grid-summary-loc {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: #FFFFFF;
  opacity: 0.9;
}

.gfl-grid-pin {
  position: relative;
  z-index: 2;
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2C7.6 2 4 5.6 4 10c0 6 8 12 8 12s8-6 8-12c0-4.4-3.6-8-8-8zm0 11a3 3 0 110-6 3 3 0 010 6z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2C7.6 2 4 5.6 4 10c0 6 8 12 8 12s8-6 8-12c0-4.4-3.6-8-8-8zm0 11a3 3 0 110-6 3 3 0 010 6z'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* Root cause of the earlier contrast bug: this chevron's 1px white border had been dropped
   somewhere along the way, leaving only a translucent rgba(255,255,255,0.14) fill directly over
   a photograph — with nothing but that faint tint separating chip from backdrop, it measured
   well under 3:1 on light photos and all but disappeared. An opaque fill was tried as the fix,
   but that was the wrong diagnosis: Figma's actual design is translucent fill + solid white
   ring, and the RING is what does the separating work (WCAG 1.4.11 non-text contrast is a
   backdrop-independent property of the border itself, not of the fill against a variable photo).
   Restoring the border is the real fix; the fill goes back to the translucent value Figma
   specifies. */
.gfl-grid-chevron {
  position: relative;
  z-index: 2;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 20px;
  background-color: rgba(255, 255, 255, 0.14);                                      /* non-brand: translucent chip wash, no --gfl-* token represents it */
  border: 1px solid #FFFFFF;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.2s ease;
}

.gfl-grid-chevron::before {
  content: '';
  width: 16px;
  height: 16px;
  background-color: #FFFFFF;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.7 8.3a1 1 0 011.4 0L12 12.2l3.9-3.9a1 1 0 111.4 1.4l-4.6 4.6a1 1 0 01-1.4 0L6.7 9.7a1 1 0 010-1.4z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.7 8.3a1 1 0 011.4 0L12 12.2l3.9-3.9a1 1 0 111.4 1.4l-4.6 4.6a1 1 0 01-1.4 0L6.7 9.7a1 1 0 010-1.4z'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* Chevron flips once JS flips aria-expanded/is-open together. Deliberately NOT included in
   .gfl-grid-summary-text's fade-out above (approved by the user): the chevron is the only
   visible close affordance once the card is open — the name/location text it sits beside fades
   away and is repeated in the body below, but the chevron isn't repeated anywhere in the
   expanded card, so it has to stay visible and simply rotate to signal "tap to close". */
.gfl-grid-card.is-open .gfl-grid-chevron {
  transform: rotate(180deg);
}

/* =============================================================================
   3. EXPANDED BODY
   ============================================================================= */

.gfl-grid-body {
  background: var(--gfl-surface, #FCFDFF);
}

/* Animation 2: the grid-template-rows 0fr -> 1fr drawer.
   :not([hidden]) is deliberate, not decorative. `display: grid` here is a normal-priority
   author declaration, and author-origin rules always beat the UA's `[hidden] { display: none }`
   regardless of specificity — so an unconditional `display: grid` on this selector would defeat
   `hidden` entirely, permanently un-hiding the drawer's content from the accessibility tree.
   Scoping to :not([hidden]) means the rule simply does not match at all while `hidden` is
   present, so [hidden] wins uncontested in the closed/no-JS state. The instant JS calls
   body.removeAttribute('hidden'), this rule starts matching and the element is committed to
   its 0fr "just-unhidden" frame — which is exactly the state the forced reflow in grid.js needs
   to exist before .is-open is added, or there is nothing for the transition to animate from. */
.gfl-grid-body:not([hidden]) {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.35s ease;
}

.gfl-grid-card.is-open .gfl-grid-body:not([hidden]) {
  grid-template-rows: 1fr;
}

.gfl-grid-body-inner {
  display: flex;
  flex-direction: column;
  /* Required for the 0fr -> 1fr trick: a 1fr-sized grid track still sizes to its child's
     content by default (min-height: auto), so without overflow: hidden and min-height: 0 here
     the row never actually collapses to 0 while closed. */
  overflow: hidden;
  min-height: 0;
}

/* =============================================================================
   4. HERO + THUMBNAIL RAIL
   ============================================================================= */

.gfl-grid-hero-wrap {
  position: relative;
  width: 100%;
}

/* .gfl-grid-hero is gone: the summary button's own sliding track (.gfl-grid-hero-track /
   .gfl-grid-hero-slide, Section 2) is now the single hero for both the collapsed bar and the
   expanded card — see the duplicate-image fix in the changelog. */

/* Sibling of the summary button (not a child — nesting interactive elements is invalid HTML),
   absolutely positioned over the photo via .gfl-grid-hero-wrap's `position: relative`. Hidden
   by default: on desktop the thumbnail rail is the picker, and even on the mobile breakpoint
   where dots replace the rail (below), they only render while the card is open — see that
   media query for why. */
.gfl-grid-dots {
  display: none;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 40px;
  z-index: 2;
  justify-content: center;
  gap: 8px;
  /* The row spans the full width so it can center its buttons, but only the buttons themselves
     should be clickable — without this, empty space beside the dots would swallow clicks meant
     for the summary button underneath. */
  pointer-events: none;
}

.gfl-grid-dots button.gfl-grid-dot {
  width: 10px;
  height: 10px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background-color: rgba(255, 255, 255, 0.5);                                       /* non-brand: pip over a photo */
  cursor: pointer;
  pointer-events: auto;
}

/* (0,3,1) — state rule, aria-pressed counts like a pseudo-class. */
.gfl-grid-dots button.gfl-grid-dot[aria-pressed="true"] {
  background-color: var(--gfl-accent, #F2A11F);
}

/* No `color` restatement here: this button carries no visible text (its
   only content is a .gfl-sr-only label), so the hover-color trap this
   project has shipped twice does not apply — there is no visible color to
   lose to a theme rule. */
.gfl-grid-dots button.gfl-grid-dot:hover,
.gfl-grid-dots button.gfl-grid-dot:focus-visible {
  background-color: var(--gfl-accent, #F2A11F);
}

.gfl-grid-dots button.gfl-grid-dot:focus-visible {
  outline: 2px solid var(--gfl-primary);
  outline-offset: 2px;
}

/* align-items: stretch (not the flex default flex-start) so the thumbnail rail's column is
   exactly as tall as its row sibling (.gfl-grid-content) — see .gfl-grid-thumbs below, whose
   #FAFAF9 wash needs to reach the full height of the row, not just wrap its own content.

   Change #5: the expanded card's background pattern now lives HERE, not on .gfl-grid-content —
   moved out one level so it paints the entire detail panel edge to edge (including behind the
   thumbnail rail's own column) instead of leaving a gap around .gfl-grid-content's own padding
   box. .gfl-grid-thumbs' #FAFAF9 wash (Section 4) is opaque and sits in its own column on top of
   this, which is fine — it deliberately hides the pattern under the rail. The white radial
   gradient is unchanged: it still fades the texture out toward the upper-left, layered on top
   of the pattern image in the same two-layer background-image declaration. No separate
   pseudo-element/z-index is needed — CSS always paints an element's own background behind its
   own content, so every descendant (rail, text, tags, CTAs) stacks above it automatically. This
   element only exists inside the expanded body, and .gfl-grid-body collapses to zero height with
   overflow: hidden while the card is closed (Section 3), so the pattern is never visible on a
   collapsed card without needing an .is-open qualifier here. */
.gfl-grid-detail {
  display: flex;
  align-items: stretch;
  background: #fff url("../assets/img-expanded-featured-card-background.jpg") no-repeat bottom right / cover;
}

.gfl-grid-thumbs {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 248px;
  padding: 48px 76px;
  /* Paints the column's full border box, so stretching the column to the row's height (via
     .gfl-grid-detail's align-items: stretch above) carries the wash all the way to the bottom
     of the expanded panel instead of stopping after the last thumbnail. */
  background-color: #FAFAF9;                                                        /* non-brand: near-white panel wash, no --gfl-* token represents it (see Section header note) */
  /* Rounds to match the card's own 24px radius (change #2) at the corner this panel occupies. */
  border-bottom-left-radius: 24px;
}

.gfl-grid-thumbs button.gfl-grid-thumb {
  position: relative;                                                               /* anchors the selected ring drawn by ::after below */
  width: 96px;
  height: 80px;
  padding: 0;
  border: 1px solid #F2F1F0;                                                        /* non-brand: near-white hairline, no --gfl-* token represents it */
  border-radius: 8px;
  overflow: hidden;
  background: none;
  cursor: pointer;
}

/* (0,3,2) — the selected thumbnail's 3px accent ring.
   Drawn by a pseudo-element ON TOP of the image, NOT by `box-shadow: inset`. An inset shadow
   paints above the background but BELOW content, and the <img> fills this button's whole
   content box, so an inset ring is completely hidden behind the photo — which is exactly how
   the first attempt at this shipped invisible.
   Absolutely positioned over the image and given its own border, so it costs no layout: the
   button's border-box and the <img> inside it keep identical metrics resting or selected, and
   nothing shrinks or shifts on selection. */
.gfl-grid-thumbs button.gfl-grid-thumb[aria-pressed="true"]::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 3px solid var(--gfl-accent, #F2A11F);
  border-radius: inherit;
  pointer-events: none;
}

/* No `color` restatement here either: a thumb's only content is an <img>,
   there is no text color for a theme's button:hover rule to clobber. */
.gfl-grid-thumbs button.gfl-grid-thumb:hover,
.gfl-grid-thumbs button.gfl-grid-thumb:focus-visible {
  border-color: var(--gfl-accent, #F2A11F);
}

.gfl-grid-thumbs button.gfl-grid-thumb:focus-visible {
  outline: 2px solid var(--gfl-primary);
  outline-offset: 2px;
}

.gfl-grid-thumbs button.gfl-grid-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* =============================================================================
   5. TYPOGRAPHY BLOCK
   ============================================================================= */

.gfl-grid-content {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  padding: 48px 60px;
}

.gfl-grid-content-inner {
  display: flex;
  flex-direction: column;
  gap: 26px;
  max-width: 665px;
}

.gfl-grid-eyebrow {
  margin: 0;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--gfl-link, #00728B);
}

.gfl-grid-name {
  margin: 0;
  font-family: var(--gfl-heading, "DM Serif Text", serif);
  font-weight: 400;
  font-size: 38px;
  line-height: 1.2;
  color: var(--gfl-primary, #004A62);
  overflow-wrap: break-word;
  word-break: break-word;
}

.gfl-grid-excerpt {
  margin: 0;
  font-size: 16px;
  line-height: 1.6;
  color: var(--gfl-text, #474646);
}

/* The excerpt is a <div> because it may carry the facility description as a fallback, and that
   is wp_editor() content wrapped in its own <p> tags. Those paragraphs would otherwise take the
   host theme's font-size, colour and margins instead of the card's — inheritance does not cover
   font-size or margin.
   Prefixed with .gfl-grid to reach (0,2,1): a bare `.gfl-grid-excerpt p` is (0,1,1), which only
   TIES a theme's `.entry-content p` and would then win or lose on enqueue order. */
.gfl-grid .gfl-grid-excerpt p {
  margin: 0 0 12px;
  font-size: inherit;
  line-height: inherit;
  color: inherit;
}

.gfl-grid .gfl-grid-excerpt p:last-child {
  margin-bottom: 0;
}

.gfl-grid-label {
  margin: 0 0 12px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--gfl-text, #474646);
}

.gfl-grid-address {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--gfl-text, #474646);
}

.gfl-grid-finepoint {
  font-size: 13px;
  line-height: 1.5;
  color: var(--gfl-text, #474646);
}

/* =============================================================================
   6. TAGS
   ============================================================================= */

/* Typed and .gfl-grid-prefixed to (0,2,1) for the same reason as .gfl-grid ol.gfl-grid-list
   above — see Section 1's comment. Same trap, same fix, applied to this file's other list. */
.gfl-grid ul.gfl-grid-tags {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.gfl-grid li.gfl-grid-tag {
  padding: 8px 16px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 700;
  background-color: #E5EFF2;                                                        /* plain-hex fallback first */
  background-color: color-mix(in srgb, var(--gfl-primary, #004A62) 8%, white);
  color: var(--gfl-primary, #004A62);
}

/* =============================================================================
   7. CTAs
   ============================================================================= */

.gfl-grid-ctas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
}

.gfl-grid-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 14px 24px;
  border-radius: 10px;
  border: 1.5px solid transparent;
  font-size: 15px;
  font-weight: 700;
  text-decoration: none;
  line-height: 1;
  white-space: nowrap;
}

/* (0,2,1): typed so it outranks a theme's `.page-content a` at (0,1,1).
   Text is --gfl-primary, NOT white-on-amber as Figma's literal shows: measured
   contrast of #fff on --gfl-accent is ~2.1:1 (fails 4.5:1). --gfl-primary on
   --gfl-accent measures ~4.6:1. See task-11-report.md Step 7. */
a.gfl-grid-cta.gfl-grid-cta-call {
  background-color: var(--gfl-accent, #F2A11F);
  color: var(--gfl-primary, #004A62);
  /* Restated for the same reason `color` gets restated elsewhere in this file: a theme's
     `.entry-content a { text-decoration: underline }` at (0,1,1) would otherwise beat this
     bare-class base rule, and specificity cannot win a property this file never declared. */
  text-decoration: none;
}

/* (0,3,1). Fills solid with --gfl-primary and flips text to white — not the lighter-amber/
   dark-text hover this used to be. White on #004A62 measures ~9.74:1, comfortably past 4.5:1.
   Both `color` and `text-decoration` are restated even where they don't visually change from
   the base rule's own declarations, per this file's standing rule: a theme's
   `button:hover { color: #fff }` / `.entry-content a { text-decoration: underline }` at
   (0,1,1) can only be beaten by a property this rule actually declares itself. */
a.gfl-grid-cta.gfl-grid-cta-call:hover,
a.gfl-grid-cta.gfl-grid-cta-call:focus-visible {
  background-color: #004A62; /* fallback: pre-var() literal */
  background-color: var(--gfl-primary, #004A62);
  color: #FFFFFF;
  text-decoration: none;
}

a.gfl-grid-cta.gfl-grid-cta-call:focus-visible {
  outline: 2px solid var(--gfl-primary);
  outline-offset: 2px;
}

.gfl-grid-cta-icon {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.6 10.8c1.4 2.8 3.8 5.2 6.6 6.6l2.2-2.2c.3-.3.7-.4 1-.2 1.1.4 2.3.6 3.6.6.6 0 1 .4 1 1V20c0 .6-.4 1-1 1C10.9 21 3 13.1 3 3.9c0-.6.4-1 1-1h3.4c.6 0 1 .4 1 1 0 1.3.2 2.5.6 3.6.1.4 0 .8-.2 1L6.6 10.8z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.6 10.8c1.4 2.8 3.8 5.2 6.6 6.6l2.2-2.2c.3-.3.7-.4 1-.2 1.1.4 2.3.6 3.6.6.6 0 1 .4 1 1V20c0 .6-.4 1-1 1C10.9 21 3 13.1 3 3.9c0-.6.4-1 1-1h3.4c.6 0 1 .4 1 1 0 1.3.2 2.5.6 3.6.1.4 0 .8-.2 1L6.6 10.8z'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* (0,2,1): typed so it outranks a theme's `.page-content a` at (0,1,1). */
a.gfl-grid-cta.gfl-grid-cta-site {
  background-color: var(--gfl-surface, #FCFDFF);
  border-color: var(--gfl-primary, #004A62);
  color: var(--gfl-primary, #004A62);
  /* Restated for the same reason `color` gets restated elsewhere in this file: a theme's
     `.entry-content a { text-decoration: underline }` at (0,1,1) would otherwise beat this
     bare-class base rule, and specificity cannot win a property this file never declared. */
  text-decoration: none;
}

/* (0,3,1). Fills solid on hover; color is restated as white because it DOES
   change here (dark-on-light becomes light-on-dark) — still an explicit
   declaration, not an inherited one, so a theme's button:hover{color:#fff}
   at (0,1,1) cannot silently take credit for it either. */
a.gfl-grid-cta.gfl-grid-cta-site:hover,
a.gfl-grid-cta.gfl-grid-cta-site:focus-visible {
  background-color: var(--gfl-primary, #004A62);
  border-color: var(--gfl-primary, #004A62);
  color: #FFFFFF;
}

a.gfl-grid-cta.gfl-grid-cta-site:focus-visible {
  outline: 2px solid var(--gfl-primary);
  outline-offset: 2px;
}

.gfl-grid-cta-arrow {
  display: inline-block;
}

.gfl-grid-cta-arrow::after {
  content: '\2197';
}

/* =============================================================================
   8. ENTRANCE STAGGER (Animation 1)
   Cards fade up and rise slightly as they scroll into view, firing once each (grid.js
   unobserves after the first intersection). The stagger delay itself comes from the
   sibling-index() rule below where supported, and from a per-card inline style written by
   grid.js everywhere else — see the @supports block for how the two stay exclusive.

   Scoped under .gfl-grid.gfl-anim, which grid.js only adds once it has confirmed
   IntersectionObserver exists and it is actually running. This is deliberate, not
   stylistic: the `opacity: 0` start state below must never appear in the unconditional CSS,
   or a card is permanently invisible on every page where the script fails to load or errors
   out before this line runs.
   ============================================================================= */

.gfl-grid.gfl-anim .gfl-grid-card {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}

.gfl-grid.gfl-anim .gfl-grid-card.is-inview {
  opacity: 1;
  transform: none;
}

/* Progressive enhancement: where sibling-index() is supported, the stagger delay comes from
   the stylesheet instead of a per-card inline style written by grid.js. Same numbers either
   way — sibling-index() is 1-based where grid.js's loop index is 0-based, hence the `- 1`,
   and the min() reproduces its STAGGER_MAX_STEPS cap so a grid of thirty featured facilities
   still finishes staggering in 350ms rather than making the last card wait two seconds.

   grid.js runs the matching CSS.supports() test and skips writing the inline style when this
   block applies; without that guard the inline value would win and this rule would be dead.
   The observer still drives WHEN a card animates — this only changes where the delay is
   declared. sibling-index() reached Baseline newly available in August 2026, so the JS path
   remains the fallback for anything older. */
@supports (transition-delay: calc(min(sibling-index() - 1, 5) * 70ms)) {
  .gfl-grid.gfl-anim .gfl-grid-card {
    transition-delay: calc(min(sibling-index() - 1, 5) * 70ms);
  }
}

/* =============================================================================
   9. UTILITIES
   ============================================================================= */

/* Mandatory here (not conditional): a grid-only page never loads
   guardian-facility-locator-public.css, so relying on that copy would ship
   this text visibly instead of screen-reader-only. */
.gfl-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;
}

.gfl-grid-empty-notice {
  margin: 0;
  padding: 24px;
  border-radius: var(--gfl-radius);
  background: var(--gfl-surface-2);
  border: 1px solid var(--gfl-border);
  color: var(--gfl-text, #474646);
  font-size: 14px;
  line-height: 1.6;
}

/* =============================================================================
   MOBILE OVERRIDES THAT MUST FOLLOW THEIR BASE RULES
   Media queries contribute no specificity. Everything below overrides a rule
   defined above at equal specificity, so it only wins by source order. Moving
   any of it upward silently breaks it.

   Breakpoint: 768px. This matches the narrow-viewport breakpoint already used
   throughout guardian-facility-locator-public.css for the same kind of
   desktop-modal -> mobile-sheet collapse, rather than inventing a new one
   (the task brief's illustrative snippet used 782px, the WP-admin-bar
   breakpoint, which this plugin does not otherwise use for layout).
   ============================================================================= */

@media (max-width: 768px) {
  /* --- Section 2/4: summary height, thumbnail rail -> dots --------------- */

  /* Shorter mobile crop, expressed as explicit heights rather than the old
     `aspect-ratio: 4/3`. aspect-ratio and an explicit, transitioned height cannot coexist — a
     box with two definite dimensions ignores aspect-ratio entirely — and Animation 3 needs a
     real `height` value on both ends to transition between (you cannot transition to/from
     `auto`, which is what aspect-ratio relies on). These two values approximate the same
     tighter, shorter mobile crop the old 4:3 rule produced at typical card widths, just as
     fixed heights instead of a computed ratio. object-position and object-fit carry over
     unchanged from the desktop rule. */
  .gfl-grid-hero-wrap > button.gfl-grid-summary {
    height: 220px;
  }

  .gfl-grid-card.is-open .gfl-grid-hero-wrap > button.gfl-grid-summary {
    height: 340px;
  }

  /* Thumbnails are desktop-only; the dots plus swipe replace them. */
  .gfl-grid-thumbs {
    display: none;
  }

  /* Dots are a small-viewport affordance, but even here they only appear once the card is
     open: collapsed, the card is just a photo + name (matching desktop), and while collapsed
     .gfl-grid-summary-text is still visible and would collide with a dot row sitting over the
     same bottom-of-photo area. By the time the card opens and the dots appear, the summary
     text has faded out (Animation 3), so there is nothing left to collide with. */
  .gfl-grid-card.is-open .gfl-grid-dots {
    display: flex;
  }

  /* Hit-slop: the base rule's dot is a 10px visual pip (see Section 4
     above), which is well under the 44px tap-target minimum. Growing the
     button box to 44px and clipping its existing background-color to the
     content box keeps the tappable area compliant without inflating the
     visual dot — no color values are touched, this only changes what
     region the current background paints into. */
  .gfl-grid-dots button.gfl-grid-dot {
    width: 44px;
    height: 44px;
    padding: 17px;
    background-clip: content-box;
  }

  /* The 44px boxes above touch each other, so the row needs NO extra gap on mobile — the
     padding already provides the visual separation. Without this the pips read as far too far
     apart, which is the reason the hit-slop was previously removed; removing it instead drops
     the tap target to 10px, below the WCAG 2.5.8 minimum, on a control that only exists on
     touch devices. Shrink the gap, not the box. */
  .gfl-grid-dots {
    gap: 0;
  }

  /* One column now that the thumbnail rail is gone. */
  .gfl-grid-detail {
    flex-direction: column;
  }

  /* --- Section 4/5: Change #6 — plain white detail panel, no pattern ----- */

  /* The textured background (Change #5, .gfl-grid-detail above) is a desktop flourish behind a
     wide two-column panel. On mobile the rail is gone (single column, see .gfl-grid-detail
     above in this media query) and the panel is narrow enough that the pattern and its fade
     gradient read as visual noise rather than texture, so it's dropped entirely in favour of
     plain white — `none` clears both background-image layers at once, and background-color
     supplies the flat white paint underneath. */
  .gfl-grid-detail {
    background-image: none;
    background-color: #FFFFFF;                                                      /* non-brand: plain panel fill, no --gfl-* token represents it */
  }

  /* --- Section 5: typography block, narrow-viewport inset --------------- */

  /* Desktop's 48px/60px (Section 5 above) is deliberate for a ~1340px card
     and must stay untouched there — do not unify these two values. At
     390px it left only 253px usable inside a 373px card (35% gutter), so
     this is a distinct narrow-viewport inset, not a scaled-down version of
     the same number. 20px sides is a standard mobile inset; vertical drops
     to 28px (from 48px) in proportion so the card doesn't read bottom-heavy
     once the sides tighten. No other ancestor (.gfl-grid-body-inner,
     .gfl-grid-detail, .gfl-grid-hero-wrap) adds horizontal padding of its
     own, so this is the only value governing usable width here — nothing
     compounds with it. */
  .gfl-grid-content {
    padding: 28px 20px;
  }

  /* --- Section 7: CTAs, full width and stacked -------------------------- */

  /* DOM order (see templates/public/grid-template.php) already puts Call
     Now before Visit Facility Website. That stays as-is on mobile — no
     order: property here — because tap-to-call is the higher-intent action
     on the device that can actually place the call. The mobile mockup drew
     the reverse; that was a deliberate override during design, not
     something this stylesheet should undo. */
  .gfl-grid-ctas {
    flex-direction: column;
  }

  a.gfl-grid-cta {
    width: 100%;
  }

  /* --- Section 5: typography block --------------------------------------- */

  .gfl-grid-finepoint {
    text-align: center;
  }
}

/* =============================================================================
   10. REDUCED MOTION
   guardian-facility-locator-public.css carries a global `prefers-reduced-motion:
   reduce` block (neutralising all animation/transition duration), but a
   grid-only page deliberately never loads that stylesheet — enqueue_grid_assets()
   is not a superset of the map's enqueue_assets(). Without an equivalent block
   here, .gfl-grid-card's box-shadow transition, .gfl-grid-chevron's
   rotate transition, the Section 2/3 hero-grow/drawer transitions, and the
   Section 8 entrance stagger would all animate for users who have explicitly
   asked not to see motion. Scoped to .gfl-grid and its descendants, not a bare
   `*`, so this file never reaches outside its own component into host page
   content.

   The blanket transition-duration/animation-duration override below reduces every
   transition this file declares — height, object-position, opacity, grid-template-rows,
   box-shadow, transform — to ~0, which on its own satisfies "the drawer
   [becomes] an instant toggle": grid.js's open()/close() logic is unchanged, it just
   finishes each transition immediately. The stagger gets an *additional*, more direct rule
   below rather than relying on the duration override alone: with a near-zero duration a card
   would still render `opacity: 0` for one frame until grid.js's IntersectionObserver callback
   adds `.is-inview` (typically the next frame, but not guaranteed on every browser/timing), and
   if that callback is ever skipped — a JS error earlier in the file, a script that fails to
   load — a reduced-motion user must not be the one left staring at a permanently invisible
   card. Setting opacity/transform directly removes that dependency on JS timing entirely.
   ============================================================================= */

@media (prefers-reduced-motion: reduce) {
  .gfl-grid,
  .gfl-grid * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  /* Belt-and-suspenders for the stagger: skip the opacity/transform start state outright
     instead of only shortening its transition, so a reduced-motion user is never even
     momentarily dependent on grid.js's observer callback to become visible. */
  .gfl-grid.gfl-anim .gfl-grid-card {
    opacity: 1;
    transform: none;
    transition-delay: 0s !important;
  }
}
