.br-read-more {
    margin-left: 4px;
    text-decoration: underline;
    cursor: pointer;
    display: inline;
}

/*
 * Enough to make a fresh paste look like a grid, and no more.
 *
 * Everything here is a single-class selector with no !important, so the site's
 * own stylesheet overrides all of it by being loaded later or by being one
 * degree more specific. Nothing sets a colour, a font or a border — those
 * belong to the site, and a plugin guessing at them is how feeds end up
 * looking bolted on.
 *
 * Column count is a custom property rather than a class so it can be changed at
 * any breakpoint without a shortcode attribute per screen size:
 *
 *   @media (max-width: 600px) { .fh-instagram { --fh-columns: 2 } }
 */

.fh-grid {
    display: grid;
    grid-template-columns: repeat(var(--fh-columns, 3), minmax(0, 1fr));
    gap: var(--fh-gap, 1rem);
}

/*
 * The two breakpoints read their own variable rather than redefining
 * --fh-columns. That is the whole trick: a site writing its own
 * `@media (...) { .fh-instagram { --fh-columns: 5 } }` overrides the base rule
 * and these queries never contradict it, because they are looking at a
 * different name. Redefining --fh-columns here would have quietly won instead.
 *
 * 900 and 550 rather than the usual 768/480 because the breakpoint that matters
 * for a grid is where the tiles get too narrow to read, not where a device
 * category begins.
 */
@media (max-width: 900px) {
    .fh-grid {
        grid-template-columns: repeat(var(--fh-columns-tablet, 3), minmax(0, 1fr));
    }
}

@media (max-width: 550px) {
    .fh-grid {
        grid-template-columns: repeat(var(--fh-columns-mobile, 2), minmax(0, 1fr));
    }
}

/* ---- Instagram ---- */

.fh-post {
    position: relative;
    display: block;
    overflow: hidden;
    aspect-ratio: 1 / 1;
}

.fh-post__image {
    width: 100%;
    height: 100%;
    /* Instagram covers are a mix of square, portrait and landscape. Cropping to
       a square tile is what makes a grid read as a grid; the full image is one
       click away on Instagram. */
    object-fit: cover;
    display: block;
}

/* The "there is more here" badge. White with a drop shadow rather than a
   colour, because it sits on an arbitrary photograph and has to stay legible
   over both a snowfield and a night shot. */
.fh-post__badge {
    position: absolute;
    top: .5rem;
    right: .5rem;
    width: 1.25rem;
    height: 1.25rem;
    color: #fff;
    pointer-events: none;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .55));
}

.fh-post__badge svg {
    width: 100%;
    height: 100%;
    display: block;
}

.fh-post--openable {
    cursor: zoom-in;
}

/* Caption and counts share one scrim that appears on hover — Smash Balloon's
   `hoverdisplay: caption,likes` over `hovercolor: #0000007D`, which is 49%
   black. `focus-within` is not decoration: without it the overlay is
   unreachable for anyone tabbing through the feed. */
.fh-post__hover {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    padding: .75rem;
    color: #fff;
    text-align: center;
    background: rgba(0, 0, 0, .49);
    opacity: 0;
    transition: opacity .15s ease-in-out;
    pointer-events: none;
}

.fh-post:hover .fh-post__hover,
.fh-post:focus-within .fh-post__hover {
    opacity: 1;
}

@media (hover: none) {
    /* Nothing hovers on a touchscreen, so the scrim would never appear and the
       caption would simply not exist. A gradient along the bottom instead. */
    .fh-post__hover {
        inset: auto 0 0 0;
        opacity: 1;
        text-align: left;
        align-items: flex-start;
        background: linear-gradient(transparent, rgba(0, 0, 0, .65));
    }
}

.fh-post__caption {
    font-size: .8125rem;
    line-height: 1.35;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.fh-post__stats {
    display: flex;
    gap: 1rem;
}

.fh-post__stat {
    display: inline-flex;
    align-items: center;
    gap: .3em;
    font-size: .875rem;
    line-height: 1;
}

.fh-icon {
    width: 1em;
    height: 1em;
    display: block;
}

/* ---- Feed actions ---- */

.fh-feed__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: .75rem;
    margin-top: 1rem;
}

/* Same two shapes Smash Balloon renders on this site: a quiet grey "load more"
   and a solid follow button. Colours are theirs (#F3F4F5 / #2C324C) so the
   swap is not visible as a colour change, and both are single-class selectors
   the site can repaint without a fight. */
.fh-feed__more,
.fh-feed__follow {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .5em;
    padding: .6rem 1.25rem;
    border: 0;
    border-radius: var(--fh-button-radius, 0);
    font: inherit;
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
}

.fh-feed__more {
    background: var(--fh-more-bg, #f3f4f5);
    color: var(--fh-more-fg, #2c324c);
}

.fh-feed__more:hover:not(:disabled) { background: var(--fh-more-bg-hover, #e8e8eb); }
.fh-feed__more:disabled { cursor: default; }
.fh-feed__more.is-loading { opacity: .6; }

.fh-feed__follow {
    background: var(--fh-follow-bg, #000);
    color: var(--fh-follow-fg, #fff);
}

/* ---- Lightbox ---- */

.fh-lb[hidden] { display: none; }

.fh-lb {
    position: fixed;
    inset: 0;
    z-index: 99999;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}

.fh-lb__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, .8);
}

/* Scrolling the page behind an open dialog is the single most common way a
   lightbox feels broken on a phone. */
body.fh-lb-open { overflow: hidden; }

.fh-lb__panel {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    gap: 0;
    width: min(1000px, 100%);
    max-height: 90vh;
    background: #fff;
    color: #111;
    overflow: hidden;
}

@media (max-width: 700px) {
    .fh-lb__panel {
        grid-template-columns: minmax(0, 1fr);
        overflow-y: auto;
    }
}

/* The stage is a fixed box and the image letterboxes inside it.
   Sizing the stage to the image instead means the panel resizes on every
   arrow press — a carousel mixing a portrait and a landscape shot makes the
   whole dialog jump, and the arrow you just clicked moves out from under the
   cursor. 640px because that is the longest edge we store, so nothing is ever
   scaled up. */
.fh-lb__stage {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #000;
    min-height: 0;
    height: min(80vh, 640px);
}

.fh-lb__image {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: contain;
}
/* `display: block` above beats the [hidden] attribute, so hiding the poster in
   JS left it occupying full width as a black rectangle beside the video. */
.fh-lb__image[hidden] { display: none; }

@media (max-width: 700px) {
    .fh-lb__stage { height: min(55vh, 420px); }
}

.fh-lb__nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    border: 0;
    background: rgba(0, 0, 0, .45);
    color: #fff;
    font-size: 1.75rem;
    line-height: 1;
    width: 2.25rem;
    height: 2.75rem;
    cursor: pointer;
}

.fh-lb__nav[hidden] { display: none; }
.fh-lb__nav--prev { left: 0; }
.fh-lb__nav--next { right: 0; }

.fh-lb__dots {
    position: absolute;
    bottom: .625rem;
    left: 0;
    right: 0;
    display: flex;
    justify-content: center;
    gap: .375rem;
}

.fh-lb__dot {
    width: .4rem;
    height: .4rem;
    border-radius: 50%;
    background: rgba(255, 255, 255, .45);
    cursor: pointer;
}

.fh-lb__dot.is-current { background: #fff; }

.fh-lb__side {
    display: flex;
    flex-direction: column;
    gap: .75rem;
    padding: 1.25rem;
    overflow-y: auto;
}

.fh-lb__meta {
    display: flex;
    gap: .5rem;
    font-size: .8125rem;
    opacity: .7;
}

.fh-lb__caption { white-space: pre-line; }

.fh-lb__comments {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .5rem;
    font-size: .9375rem;
}

.fh-lb__comment-author {
    font-weight: 600;
    margin-right: .35em;
}

.fh-lb__link { margin-top: auto; }
.fh-lb__link[hidden] { display: none; }

.fh-lb__close {
    position: absolute;
    top: .25rem;
    right: .5rem;
    z-index: 1;
    border: 0;
    background: none;
    color: inherit;
    font-size: 1.75rem;
    line-height: 1;
    cursor: pointer;
}

/* ---- Reviews ---- */

.fh-review {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.fh-review__head {
    display: flex;
    align-items: center;
    gap: .625rem;
}

.fh-review__avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
    flex: 0 0 auto;
}

.fh-review__avatar--initial {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-transform: uppercase;
}

.fh-review__meta {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.fh-review__author {
    /* A long name must not push the platform marker off the card. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.fh-review__source {
    margin-left: auto;
    flex: 0 0 auto;
}

/* normalise_text() stores review copy as plain text with real newlines, so a
   reviewer who wrote paragraphs still reads as paragraphs. HTML collapses
   newlines, so honouring them is CSS's job. The lightbox caption already did
   this; the review cards were missed, which turned every multi-paragraph
   review into one run-on block. Neither class had a rule at all before this. */
.fh-review__text,
.fh-review__response-text { white-space: pre-line; }

.fh-review__response {
    /* Visibly the owner talking, not the reviewer. The indent is the only
       structural cue; the site can style it however it likes. */
    margin-left: 1rem;
}

.fh-review__stars {
    display: flex;
    align-items: center;
    gap: 2px;
}

.br-star-icon {
    width: 1em;
    height: 1em;
    display: block;
}

/* ── Lightbox: video ──────────────────────────────────────────────────────
   Video opens Instagram's embed player. The .fh-lb--video class existed with no
   rules at all, so a video used to open as a still with no play affordance.

   Everything here keys off that class rather than the [hidden] attribute: an
   attribute selector has to win a specificity race against whatever else styles
   the element, and it was losing, which left the poster occupying the stage as
   a black rectangle beside the player. */
.fh-lb--video .fh-lb__image { display: none !important; }

/* The player fills the stage and letterboxes inside it, so a portrait reel and
   a landscape clip both sit in the same box without resizing the dialog.

   Absolutely positioned rather than laid out as a flex item: as a flex item the
   element kept its intrinsic 360px width and sat outside the stage's left edge,
   because the theme's `img, svg, video { height: auto }` reset fights the
   percentage sizing. Pinning it to the stage's padding box sidesteps that
   entirely. */
.fh-lb__video {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  background: #000;
}
.fh-lb--video .fh-lb__video:not([hidden]) { display: block; }

/* The stage paints black behind a contained photo. Behind a player that has its
   own chrome and its own aspect ratio, that reads as a broken letterbox. */
.fh-lb.fh-lb--video .fh-lb__stage {
  background: #0f0f0f;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}

.fh-lb__frame {
  display: none;
  border: 0;
  background: #000;
  border-radius: 8px;
}
.fh-lb--video .fh-lb__frame {
  display: block;
  /* Fit the stage rather than overflow it: the embed's natural height is taller
     than the stage, and the excess was being clipped off the bottom. */
  height: 100%;
  width: 100%;
  max-width: 400px;
}

/* The stage column narrows to the clip's own aspect (set by the script once the
   metadata loads), so a 9:16 reel opens as a portrait dialog instead of a
   pillarboxed strip. Falls back to the photo width before metadata arrives. */
.fh-lb--video .fh-lb__panel {
  width: auto;
  max-width: min(1000px, 100%);
  grid-template-columns: minmax(0, var(--fh-stage-w, 600px)) minmax(0, 400px);
}

@media (max-width: 700px) {
  .fh-lb--video .fh-lb__panel {
    width: min(1000px, 100%);
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Paging arrows and dots mean nothing while a single video is playing. */
.fh-lb--video .fh-lb__nav,
.fh-lb--video .fh-lb__dots { display: none; }
