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

/*
 * Stop iOS Safari resizing type on its own.
 *
 * Safari's text autosizing inflates the type inside any block it decides is wide
 * relative to the screen, and it decides per block — so one column of a table comes
 * out noticeably larger than the rest of the page while every font-size in the
 * stylesheet still says 16px. It is invisible in a desktop browser, which does not
 * do this at all, and it got worse once the results tables became block boxes.
 *
 * 100% means "honour the size I asked for". It does not stop the reader zooming,
 * and it changes nothing on a desktop.
 */
html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

body {
    font-family: -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
    color: #222;
    background: #fafafa;
    line-height: 1.5;
}

.container {
    /* Wide enough for the search results and gear tables to breathe. Ordinary
       forms stay narrow via .form-section, so only the wide layouts grow. */
    max-width: 1500px;
    margin: 0 auto;
    padding: 0 20px;
}

.logo {
    color: #fff;
    font-weight: 700;
    font-size: 1.25rem;
    text-decoration: none;
}

/* Site name + avatar live in the nav strip, ahead of the links */
/*
 * The two image links opt out of the underline the text links carry. That rule adds
 * 2px of padding and a 2px transparent border for the hover underline to live in, and
 * on the brand that was four stray pixels under the logo — the bar came out 60px tall
 * around a 56px image, so it never quite reached the bottom edge.
 */
.site-nav .nav-brand,
.site-nav .nav-avatar {
    padding-bottom: 0;
    border-bottom: 0;
}

/*
 * Hard against the left edge, and centred between the top and bottom of the bar.
 *
 * It spans both rows rather than sitting in the first one: the bar is two rows tall,
 * so anything living in row one alone is centred on that row and rides high against
 * the bar as a whole. Spanning both and centring in the span puts it on the bar's own
 * middle, level with the gap between the logo and the menu.
 */
.nav-avatar {
    grid-row: 1 / -1;
    grid-column: 1;
    display: flex;
    align-items: center;
    text-decoration: none;
    justify-self: start;
    align-self: center;
}

/* Centred on the bar, between the avatar and the greeting. */
.nav-brand {
    grid-row: 1;
    grid-column: 2;
    display: flex;
    align-items: center;
    text-decoration: none;
    justify-self: center;
}

/*
 * The wordmark image that stands in for the typed site name.
 *
 * Sized by height so it always matches the bar, with width following the crop, and
 * the shadow lifts it off the wallpaper — it is artwork on artwork, and it needs an
 * edge of its own.
 */
.nav-brand .logo-mark {
    display: block;
    height: 63px;
    width: auto;
    /* A floor under the whole thing: whatever the height resolves the width to, it
       never exceeds the bar. Without this the 701-760px band — past the phone rules
       below, still on the desktop height — pushed the page 130px wider than itself. */
    max-width: 100%;
    box-shadow: 0 0 17px rgba(0, 0, 0, 0.45);
}

/*
 * The mark at the left of the bar. Square, no frame — the tile is its own shape and
 * a ring drawn round it was a frame around a frame.
 *
 * 124px, which is where it settled: half again on the 103 it replaced, then a fifth
 * back off. The cell is the tallest thing in the row, so the bar's height follows it
 * — the wordmark and menu simply sit further apart inside a taller strip.
 *
 * The picture is drawn with a transparent margin round the tile, so at its own size
 * the tile only reaches about 70% of the cell and reads small. 1.2 takes that back.
 * It crops, but only into that margin: the artwork including its glow ends about 8%
 * in from the edge of the file, which is exactly what 1.2 trims. Any more would cut
 * the glow, and a soft glow ending at a hard square edge looks like a mistake.
 */
.site-nav .nav-avatar {
    /*
     * Drawn 142px so it stops 1px short of the bar top and bottom, but it still
     * OCCUPIES 136 — the negative margins give back the 3px it gained at each end.
     *
     * That split is the whole trick. The bar is 144 tall: 4px of padding, a 136px
     * content band, 4px of padding. Sizing the mark to 142 outright would push the
     * band to 142 and the bar to 150. Letting the layout keep seeing 136 holds the
     * bar exactly where it is while the picture spills 3px into the padding at each
     * end, which is the 1px margin asked for.
     */
    width: 142px;
    height: 142px;
    margin-top: -3px;
    margin-bottom: -3px;
    overflow: hidden;
    flex: none;
}

.nav-logo-badge {
    width: 100%;
    height: 100%;
    object-fit: cover;
    /*
     * No scale. The 1.2 here was for the old neon tile, whose artwork stopped
     * about 8% in from the edge of the file — the zoom took back that transparent
     * margin. The sticker that replaced it fills its frame and is round, so the
     * same zoom cropped the curve off all four corners and turned a circle into a
     * rounded square. Sized as drawn, the round edge stays round.
     */
    transform: none;
}


/* Nav now sits on the page below the banner, centred, rather than inside it */
/*
 * The bar at the top of every page: the wallpaper, tiled, every other tile mirrored.
 *
 * The tile is images/wallpaper-strip.png — the picture beside its own reflection,
 * built by tools/make_wallpaper_strip.py. Mirroring is what makes it tile: each seam
 * meets its own reflection, so there is no edge to line up and no repeating join
 * marching across the bar.
 *
 * Left at its natural size rather than scaled to the bar's height. The wallpaper is
 * 236x702, so height: 100% would squeeze a tile down to about 40px wide and turn the
 * mandalas into noise; at full size the bar is a crop through artwork that still reads
 * as artwork, and it repeats every 236px instead of every 40.
 */
.site-nav {
    position: relative;
    /* Behind the image while it loads, and behind the transparent edges of a PNG. */
    background-color: #000;
    background-image: url('../images/wallpaper-strip.png');
    background-repeat: repeat-x;
    background-position: center;
}

/*
 * The bar carries white text and a white logo, and the wallpaper is bright in places.
 * This holds the contrast: dark enough that the links stay legible over the lightest
 * part of the pattern, light enough that the pattern is still visibly there.
 *
 * Lightened from 0.58 to let more of the colour through. The text shadow on the bar's
 * container is what makes that affordable — without it the greeting is the first
 * thing to go.
 */
.site-nav::before {
    content: '';
    position: absolute;
    inset: 0;
    /* Darkest across the middle, where the links and the greeting sit, and nearly
       clear at the top and bottom edges. The pattern is at close to full strength in
       the strips above and below the text, which is most of what the eye reads as
       "brighter", without giving up the contrast where it is actually needed. */
    background: linear-gradient(
        180deg,
        rgba(0, 0, 0, 0.10) 0%,
        rgba(0, 0, 0, 0.52) 34%,
        rgba(0, 0, 0, 0.52) 66%,
        rgba(0, 0, 0, 0.10) 100%
    );
}

/*
 * Four parts, not one row: wordmark, links, greeting, avatar.
 *
 * A grid rather than a flex row, so each part holds its own place instead of the
 * whole lot being pushed around by whichever item happens to be widest. The wordmark
 * takes the left edge, the links take the slack between it and the pair on the right,
 * and the avatar and the greeting close the bar off — the face, then the name of who
 * is signed in, hard against the edge.
 */
.site-nav .container {
    /* Above the scrim rather than under it. */
    position: relative;
    /* Every piece of text in the bar carries its own shadow, so legibility does not
       depend on which part of the pattern happens to sit behind it — and a brighter
       wallpaper dropped in later does not quietly break the greeting, which is the
       palest thing up here. */
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.75);
    display: grid;
    /*
     * Two rows. The top one is avatar | wordmark | greeting, and the links have the
     * second to themselves across the full width.
     *
     * Three columns, and the outer two are equal: that is what centres the wordmark on
     * the bar rather than merely between its neighbours. An avatar and a greeting are
     * nothing like the same width, so left to themselves they would push the logo off
     * centre by half the difference.
     *
     * The second row is also what takes wrapping off the table for good. On one row
     * the menu was always competing with the logo for width, and every time the logo
     * grew the menu lost a line — with a row of its own it has the whole 1460px to lay
     * out in, which is twice what it needs.
     */
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    grid-template-rows: auto auto;
    align-items: center;
    column-gap: 26px;
    row-gap: 4px;
    /* No fixed height: two rows of content decide it, which is exactly the "just
       enough" — the bar is as tall as the logo row plus the menu row and no taller.
       The padding is split evenly top and bottom so the avatar, which is centred
       across both rows, has the same clearance at each end. */
    padding-top: 4px;
    padding-bottom: 4px;
    /* The bar kept its height when the avatar stopped setting it. Centring the avatar
       across both rows took it out of the row-height sum, and without this the whole
       bar closed up by 30px — which was never part of the ask. Tracks the avatar:
       raised from 124 to 136 when the mark grew by a tenth. */
    min-height: 136px;
}

/*
 * The whole of the second row, centred across the bar.
 *
 * Pulled up so it sits closer to the logo than to the edge of the bar. The row above
 * is as tall as the avatar, which leaves the logo's baseline well clear of the menu —
 * the negative margin closes half that distance, tying the two lines together as one
 * block rather than two separate strips.
 */
.nav-links {
    grid-row: 2;
    grid-column: 1 / -1;
    margin-top: -11px;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 7px 18px;
}

.site-nav a {
    color: #fff;
    text-decoration: none;
    /*
     * Sized to the room the links actually have rather than to the rest of the bar.
     * A step down from 1.28. Width is no longer what decides this — the menu has a row
     * to itself and uses less than half of it — so the size is now about how loudly the
     * options should speak next to the logo above them, and one notch quieter is
     * enough.
     */
    font-size: 1.14rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    padding-bottom: 2px;
    border-bottom: 2px solid transparent;
}

.site-nav a:hover {
    color: #5eead4;
    border-bottom-color: #5eead4;
}

.nav-greeting {
    grid-row: 1;
    grid-column: 3;
    /* Its column is the mirror of the brand's, so the right edge is held without an
       auto margin doing the pushing. */
    justify-self: end;
    /* Brightened from #b9b9c6. It was the palest thing in the bar and the first to
       struggle once the wallpaper went in behind it; near-white sits it with the
       links instead of behind them. */
    color: #f2f2f7;
    /* A step down. It is a greeting, not a heading — it should not be the largest type
       in the bar. */
    font-size: 1.2rem;
    /*
     * Allowed to run to a second line rather than growing sideways. Its column is one
     * of the two that centre the logo between them, so a long nickname stretching this
     * out would drag the logo off centre; wrapping keeps that from happening no matter
     * who is signed in.
     */
    max-width: 190px;
    text-align: right;
    line-height: 1.25;
}

/*
 * The bar on a phone.
 *
 * This block used to sit above the rules it overrides. At equal specificity the
 * later rule wins, so ".site-nav .container { grid-template-columns: 1fr }" lost to
 * the three-column base rule below — while "> * { grid-column: 1 }" (one class more
 * specific) still won. Half the mobile layout applied and half did not: the links
 * were pushed into column one, 141px wide, so all six stacked one per line down the
 * screen. Moved below the base rules, both halves land and the menu wraps.
 *
 * 760 to match the form breakpoint. At 700 there was a 60px band where the form had
 * already stacked but the bar was still in its desktop three-column layout.
 */
@media (max-width: 760px) {
    /*
     * Sized by width here, not height. The wordmark is a long picture, so
     * "height: 51px; width: auto" resolves to about 600px of width — far wider
     * than a phone — and the ends simply fall off the screen. On a narrow bar the
     * width is the constrained dimension, so it leads and the height follows.
     */
    .site-nav .nav-brand {
        justify-self: stretch;
        width: 100%;
        min-width: 0;
    }

    .nav-brand .logo-mark {
        width: 100%;
        max-width: 100%;
        height: auto;
    }

    /*
     * The EM badge goes on a phone.
     *
     * Stacked, it is not a badge beside the wordmark any more — it is a row of its
     * own, 64px of tile plus the gap either side, repeated at the top of every
     * page. The bar already says the name twice over: the wordmark spells it out
     * and the tab carries the icon. Its only other job was linking home, and Home
     * is the first item in the menu directly above it.
     *
     * display:none rather than a shorter bar with the tile clipped: a logo sliced
     * through the middle reads as a rendering fault, not as a design.
     */
    .site-nav .nav-avatar {
        display: none;
    }

    /* One column, and no floor under the height: on a phone the bar should be as
       tall as what is in it, not the 124px the avatar used to set.

       The gaps are tight on purpose. Three rows with 10px between them and 10px
       top and bottom spent 40 of the bar's 154px on nothing, on every page. The
       wordmark, the menu and the greeting are one block, not three sections. */
    .site-nav .container {
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: auto;
        justify-items: center;
        gap: 4px;
        padding-top: 5px;
        padding-bottom: 5px;
        min-height: 0;
    }

    .site-nav .container > * {
        grid-column: 1;
        grid-row: auto;
    }

    /* Now that it has the full width, the menu flows across it instead of down. */
    .site-nav .nav-links {
        width: 100%;
        margin-top: 0;
        gap: 3px 14px;
    }

    /* A notch down fits three links to a row rather than two, which is the
       difference between a menu three lines deep and one six lines deep.

       line-height 1.25 rather than the inherited 1.5: at two rows the leading was
       adding 12px of empty bar, and these are single words, not prose. */
    .site-nav .nav-links a {
        font-size: 1rem;
        line-height: 1.25;
        padding-bottom: 1px;
    }

    /* It was the largest type in the bar at 1.2rem — bigger than the links it sits
       under — and carried a full line of leading for two words. */
    .nav-greeting {
        justify-self: center;
        max-width: 100%;
        text-align: center;
        font-size: 1rem;
        line-height: 1.2;
    }
}

.hero {
    text-align: center;
    /* Half the old 80px gap under the photo strip — the images and the headline
       read as one block rather than two. */
    padding: 40px 0 60px;
}

.hero h1 {
    font-size: 2.5rem;
    margin-bottom: 12px;
}

.hero p {
    font-size: 1.15rem;
    color: #555;
    margin-bottom: 28px;
}

/*
 * The line that tells you the picture is a way in.
 *
 * Colour: #a32f24 rather than the #c0392b used elsewhere on the site. The same
 * red on this page's pink ground (#f2d4d9) measures 3.9:1, which passes only
 * because the text is large; darkened to this it measures 5.1:1 and passes at
 * any size, which matters on a line that is the first instruction a stranger
 * reads. It still belongs to the site's red family — it is that red with the
 * lights down, not a new colour.
 *
 * Bold and a size up from body text, because it has to carry across a wide
 * screen and compete with a very busy illustration directly beneath it. It is
 * not a heading: there is no <h1> on this page by design (the artwork carries
 * the name), and making this one would put a heading above the picture saying
 * something the picture does not say.
 */
/* Selected as .hero .frontpage-cue, not .frontpage-cue on its own: `.hero p`
   above sets colour #555 and size 1.15rem, and one element plus one class beats
   one class. The bare selector lost silently — the line rendered grey at body
   size and looked deliberate. */
/*
 * The cue sits in a band of its own, with the same air above it as below it.
 *
 * One number, --cue-gap, set on the hero and used twice: as the hero's own top
 * padding (the space between the site banner and this line) and as the cue's
 * bottom margin (the space between this line and the picture). They were 40 and
 * 18, which read as the line belonging to the banner above rather than to the
 * picture it is talking about. Driving both from one custom property is not
 * tidiness — it is the only way two numbers that must stay equal actually do,
 * including at the phone breakpoint below, where changing the variable moves
 * both at once.
 *
 * The hero's 60px bottom padding is untouched: that is the gap to the sign-in
 * row, a different join entirely.
 */
body.home-page .hero {
    --cue-gap: 18px;
    padding-top: var(--cue-gap);
}

.hero .frontpage-cue {
    color: #a32f24;
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    /* The hero is already centred; this is here so the rule survives being
       moved or the hero's alignment changing under it. */
    text-align: center;
    /*
     * -0.33em on the top, and it is not a fudge.
     *
     * Equal padding above and below a line of text does not look equal, because
     * a line box is not the letters. Above the capitals sits the font's ascent —
     * room reserved for accents and tall letters this sentence does not use —
     * while below the baseline only the descenders of j, g and y reach down, and
     * they nearly fill their half. Measured here: 10px of empty box above the
     * ink, 2px below it. So 18 and 18 as boxes renders as 28 and 20 as letters,
     * and the eye reads the line as belonging to the banner above rather than to
     * the picture it is pointing at.
     *
     * This pulls the top back by the difference. In em rather than px so it is
     * the difference at whatever size the line is: the phone breakpoint drops
     * the text to 1.15rem and this correction shrinks with it, with no second
     * number to keep in step.
     */
    margin: -0.33em auto var(--cue-gap, 18px);
    max-width: 100%;
}

/* At phone width the line wraps to two, and 1.4rem made those two lines taller
   than the gap above the picture could carry. */
@media (max-width: 620px) {
    /* Both gaps move together, because they are the same number. */
    body.home-page .hero {
        --cue-gap: 14px;
    }

    .hero .frontpage-cue {
        font-size: 1.15rem;
    }
}

/* The smallest phones still in use are 320px wide, which leaves 265px inside the
   container's padding — fourteen short of what this line needs at 1.15rem. One
   step down holds it on a single line there too, which is the whole point of
   shortening the sentence. */
@media (max-width: 340px) {
    .hero .frontpage-cue {
        font-size: 1.02rem;
    }
}

/*
 * The home page's picture, in place of the blurb that used to be here.
 *
 * Its own width, capped by the page: 1376 is what the file is, and blowing it up
 * past that would only soften it. Below that it takes whatever room there is,
 * which on a phone is the screen less the container's padding.
 *
 * height: auto with the file's own ratio stated, so the page does not jump when
 * the image finishes loading — the space it will occupy is known from the first
 * layout rather than from the download.
 */
/*
 * The front page picture, in four clickable quarters.
 *
 * The artwork is drawn as four quadrants with a caption ribbon in each, so the
 * hotspots are simply halves of each axis — no measured coordinates to keep in
 * step with the image, and nothing to redo if the artwork is redrawn at another
 * size. Percentages, so they hold at every width the picture is shown at.
 */
.frontpage-map {
    position: relative;
    max-width: 1376px;
    margin: 0 auto 28px;
}

.frontpage-quad {
    position: absolute;
    width: 50%;
    height: 50%;
    /* Invisible until wanted: the picture is the design, and four boxes ruled over
       it would be the site drawing on its own artwork. */
    background: transparent;
    border: 0;
    border-radius: 8px;
    cursor: pointer;
    padding: 0;
}

.frontpage-quad-musicians { top: 0;   left: 0;   }
.frontpage-quad-gear      { top: 0;   left: 50%; }
.frontpage-quad-venues    { top: 50%; left: 0;   }
.frontpage-quad-patrons   { top: 50%; left: 50%; }

/*
 * On hover or keyboard focus the quarter lifts out of the picture: a wash of
 * white to separate it, and a ring to say where it ends. Both on the button
 * itself rather than an overlay element, so there is nothing to mis-position.
 */
.frontpage-quad:hover,
.frontpage-quad:focus-visible {
    background: rgba(255, 255, 255, 0.18);
    box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0.85);
    outline: none;
}

/*
 * "What is this?" — the only thing that tells a reader the picture does anything.
 *
 * Hidden until the quarter is hovered or focused on a pointer device. On a
 * touchscreen there is no hover to reveal it, so it is shown from the start (see
 * the touch rule below) — otherwise the whole feature would be invisible on a
 * phone, which is where most people meet the page.
 */
.frontpage-quad-hint {
    background: rgba(20, 20, 30, 0.82);
    border-radius: 999px;
    color: #fff;
    display: inline-block;
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    left: 50%;
    opacity: 0;
    padding: 7px 16px;
    position: absolute;
    top: 50%;
    transform: translate(-50%, -50%);
    transition: opacity 0.15s ease;
    white-space: nowrap;
}

.frontpage-quad:hover .frontpage-quad-hint,
.frontpage-quad:focus-visible .frontpage-quad-hint {
    opacity: 1;
}

/* No hover to discover it with: say so from the start, quietly. */
@media (hover: none) {
    .frontpage-quad-hint {
        opacity: 0.9;
    }
}

/* A phone shows the picture at about a quarter of its drawn size, so the pill has
   to come down with it or four of them cover the artwork. */
@media (max-width: 620px) {
    .frontpage-quad-hint {
        font-size: 0.7rem;
        padding: 4px 10px;
    }
}

/* A quarter that navigates is an <a>, and an anchor does not inherit the button
   reset above it. */
a.frontpage-quad {
    display: block;
    text-decoration: none;
}

/*
 * The "?" badge.
 *
 * Once a quarter becomes a link the whole of it navigates, which leaves nowhere
 * to put the paragraph that used to be the quarter's only job. This is that
 * door — and it is deliberately small, because the quarter's job is now to be
 * clicked, and the explanation is for the minority who want it before they go.
 *
 * In the quarter's OUTER corner rather than its inner one: four of these meeting
 * in the middle of the picture would read as a control panel bolted onto the
 * artwork, and the middle is where the eye lands first.
 *
 * z-index lifts it over the link it sits on top of. Without it the badge is in
 * the picture but the anchor takes the click.
 */
.frontpage-quad-help {
    position: absolute;
    z-index: 2;
    width: 34px;
    height: 34px;
    padding: 0;
    border: 0;
    border-radius: 999px;
    background: rgba(20, 20, 30, 0.82);
    color: #fff;
    font-family: inherit;
    font-size: 1.05rem;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    display: grid;
    place-items: center;
    transition: background 0.15s ease, transform 0.15s ease;
}

.frontpage-quad-help:hover,
.frontpage-quad-help:focus-visible {
    background: rgba(20, 20, 30, 0.95);
    transform: scale(1.08);
    outline: 2px solid rgba(255, 255, 255, 0.9);
    outline-offset: 2px;
}

.frontpage-quad-help-musicians { top: 14px;    left: 14px;  }
.frontpage-quad-help-gear      { top: 14px;    right: 14px; }
.frontpage-quad-help-venues    { bottom: 14px; left: 14px;  }
.frontpage-quad-help-patrons   { bottom: 14px; right: 14px; }

/* Same reason the pill shrinks: at phone size a 34px disc is a large coin on a
   small picture. 28px is still above the 24px minimum touch target. */
@media (max-width: 620px) {
    .frontpage-quad-help {
        width: 28px;
        height: 28px;
        font-size: 0.85rem;
    }

    .frontpage-quad-help-musicians { top: 8px;    left: 8px;  }
    .frontpage-quad-help-gear      { top: 8px;    right: 8px; }
    .frontpage-quad-help-venues    { bottom: 8px; left: 8px;  }
    .frontpage-quad-help-patrons   { bottom: 8px; right: 8px; }
}

/* The explanation behind a quarter. Sized for reading rather than for a form:
   narrower than the compose window, and never taller than the screen. */
.quad-dialog {
    /* .hero centres everything in it, and a dialog inside .hero inherits that.
       Three paragraphs of centred prose is a poster, not something anyone reads,
       so it is set back to the left here. */
    text-align: left;
    background: #fff;
    border: 1px solid #ccc;
    border-radius: 8px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
    /*
     * Anchored near the top of the window rather than centred in it.
     *
     * "margin: auto" is what centres a modal, and centring meant the heading
     * started a third of the way down the screen and moved with the length of the
     * text — a short panel opened low, a long one opened high, and the reader's eye
     * had to find the first line each time. Pinned to the top, the title is always
     * in the same place and the reading starts where reading starts.
     */
    margin: 24px auto auto;
    /*
     * svh, not vh — and the vh above it only as a fallback for a browser too old
     * to know the newer unit.
     *
     * On a phone 100vh is the LARGE viewport: the height the page would have if the
     * browser's own toolbars were hidden. They are not hidden when the panel opens,
     * so a dialog sized to 100vh is taller than what is actually on screen, and the
     * last inch of it — which is where Close lives — sits underneath the address
     * bar. Scrolling to the bottom appeared to slide the button away.
     *
     * svh is the SMALL viewport: the height with the toolbars showing, which is the
     * one measurement that is always visible. dvh would also fit, but it changes as
     * the toolbars slide in and out, and a dialog that resizes while you scroll it
     * is its own problem.
     */
    max-height: calc(100vh - 48px);
    max-height: calc(100svh - 48px);
    max-width: 560px;
    overflow-y: auto;
    padding: 22px 26px 18px;
    width: calc(100% - 32px);
}

/*
 * Close stays on screen instead of waiting at the bottom of the text.
 *
 * Sticky inside the panel's own scroll, so it sits on the last line of whatever is
 * visible rather than after the last line of everything. On the panels short enough
 * not to scroll it simply sits where it always did.
 *
 * The negative margins pull it out to the panel's edges so the white behind it
 * spans the full width and the text scrolls under it cleanly rather than up its
 * side; the padding puts the inset back on the button itself.
 */
.quad-dialog .form-actions {
    background: #fff;
    border-top: 1px solid #eee;
    bottom: -18px;
    margin: 6px -26px -18px;
    padding: 12px 26px;
    position: sticky;
}

.quad-dialog::backdrop {
    background: rgba(10, 10, 18, 0.55);
}

.quad-dialog .subheading {
    margin-top: 0;
}

.quad-dialog p {
    line-height: 1.5;
    margin: 0 0 12px;
}

/* The caption off the artwork, said again as the dialog's own opening line. */
.quad-lead {
    color: #0B7C76;
    font-weight: 600;
}

.home-frontpage {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 1376 / 768;
    border-radius: 8px;
}

/*
 * Four paragraphs of prose need a readable measure and a left edge to run down;
 * centred, they would be hard to read. The heading and the buttons above and
 * below stay centred, so the block still sits in the middle of the page.
 */
.hero-intro {
    margin: 0 auto 28px;
    /* Wider than a book column but well short of the full page — past roughly
       this width the eye starts losing its place returning to the next line. */
    max-width: 1000px;
    text-align: left;
}

.hero-intro p {
    /* Tighter than the site's 1.5 default — this is a block of running prose, not
       a form label, so the lines can sit closer together. */
    line-height: 1.20;
    margin-bottom: 10px;
    /* Flush on both edges, as Word does. No hyphenation, which is also Word's
       default; the measure is wide enough that the word spacing stays even. */
    text-align: justify;
}

.hero-intro p:last-child {
    margin-bottom: 0;
}

/* Yes/no and the distance sit together: they are one answer, not two. */
.last-minute-row {
    align-items: center;
    display: inline-flex;
    flex-wrap: wrap;
    gap: 8px;
}

.last-minute-row .inline-label {
    font-weight: 600;
    margin: 0 0 0 6px;
}

/*
 * Open to Bookings and the sentence about the phone number, on one line.
 *
 * Same idea as .last-minute-row, but this one does NOT go nowrap: it carries a
 * sentence rather than the word "miles", and on a narrow window a sentence that
 * refuses to wrap pushes the form wider than the page. The dropdown holds its size
 * and the words take what is left, wrapping under themselves if they have to.
 */
.bookings-row {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

.bookings-row select {
    flex: 0 0 auto;
}

/* .form-note is a paragraph style with margins of its own; beside a control it is
   just words on the line. */
.bookings-row .form-note {
    margin: 0;
}

/*
 * Two fields sharing one half of a row. Narrower labels than usual, because the
 * space they are splitting is already half a column.
 */
/*
 * On the two profile forms the columns are a fixed 570px rather than "half of
 * whatever is going", so widening the page adds room on the right instead of
 * stretching every field. That is what lets Profile Status sit in the same
 * column as Preferred Contact while Visibility hangs off the end.
 * They can still shrink, so a narrow window degrades rather than overflows.
 */
/*
 * Fixed columns rather than "half of whatever is going". Every row on these two
 * forms starts its second column at the same x, so Profile Status lands in the
 * same place as Preferred Contact above it, and the row that needs a third
 * column can add one without shifting the first two.
 */
/*
 * The heading row on Edit Profile. Only an anchor now — the photograph is taken out
 * of the flow and hung off the right of it, so the form starts directly under the
 * heading instead of below a 220px portrait.
 */
.edit-heading {
    position: relative;
}

/*
 * The photograph sits out in the page's right margin, not in the form's.
 *
 * The form is capped at 1090px inside a container capped at 1500, so on a wide
 * screen there is a couple of hundred pixels of unused page down each side. This
 * measures that gap and pushes the picture into it, which is space the layout was
 * wasting and puts the portrait clear of the fields rather than over them.
 *
 * 60px rather than 40 comes off the viewport because 100vw counts the scrollbar and
 * the container's padding does not; the difference would push the picture a few
 * pixels past the edge and put a horizontal scrollbar on the page. Below about
 * 1130px wide the gap is gone, max() clamps the shift to zero, and the picture
 * simply sits at the form's own right edge.
 *
 * Square, and much larger than the 84px round thumbnail used in listings: round is
 * right in a list, where a photograph is a face beside a name, but here it is the
 * photograph itself — the thing this page edits — so it is shown as uploaded,
 * corners and all. object-fit still crops it square, centring a portrait shot
 * rather than squashing it.
 *
 * Two classes deep on purpose: .banner-thumb is defined further down the file and
 * would otherwise win on source order and put back its 84px circle.
 */
/* 1.5in square. Written in inches rather than the 144px it comes to, because that
   is the size asked for: CSS resolves an inch at 96px on screen, and prints it as a
   real one. */
.edit-heading .edit-avatar {
    width: 1.5in;
    height: 1.5in;
    border-radius: 0;
    flex: none;
}

/*
 * Wide screens only: out into the margin.
 *
 * The gap each side is (container - form) / 2, and the container is 1500px capped
 * with 20px of padding, so the gap only reaches 1.5in at about 1420px of viewport.
 * Above that the picture shifts its full width and clears the form completely;
 * below it there is nowhere to move to, so it stays in the heading row where it
 * cannot land on top of a field. The breakpoint is the width the margin appears at,
 * not a guess at a device.
 */
@media (min-width: 1420px) {
    .edit-heading .edit-avatar {
        position: absolute;
        top: 0;
        right: -1.5in;
    }
}

/* Narrower than that, the picture is back in the flow and shares the heading row. */
@media (max-width: 1419px) {
    .edit-heading {
        display: flex;
        align-items: flex-start;
        justify-content: space-between;
        gap: 20px;
    }
}

.form-section-edit .field-row {
    display: grid;
    grid-template-columns: 570px 500px;
    gap: 20px;
    align-items: start;
}

/*
 * The row carrying two fields in its second column: that column takes only the
 * width the pair needs, so Visibility sits right beside Profile Status instead
 * of being pushed out to the far edge.
 */
.form-section-edit .field-row:has(.field-pair) {
    grid-template-columns: 570px auto;
}

/* The one thing allowed to give: the about-you box just follows the new width. */
.form-section-edit .input-full {
    width: 100%;
}

/* Side by side, each only as wide as its own label and control. */
.field-pair {
    align-items: start;
    display: flex;
    gap: 20px;
}

/* Scoped to the section so it outranks ".field-row .field { flex: 1 }", which
   appears later in this file and would otherwise stretch both to equal halves. */
.form-section-edit .field-pair > .field {
    flex: 0 0 auto;
}

@media (max-width: 1150px) {
    /* Below this the 570 + 500 columns no longer fit, so share what there is.
       Pitched at the form's own width, not the old full-container width, or it
       would rearrange the page while there was still room to spare. */
    /* The pair variant is restated here for the same reason it is restated in the
       phone block: ":has(.field-pair)" is a class more specific than the selector
       above, so without naming it the 570px column survived this breakpoint too and
       overflowed every window between 761 and 1150px. */
    .form-section-edit .field-row,
    .form-section-edit .field-row:has(.field-pair) {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    }

    .field-pair {
        flex-wrap: wrap;
    }
}

/* Inside the pair the second label only needs its own width, and neither control
   column should be wider than the control it holds. */
.labels-left .field-pair > .field {
    grid-template-columns: 185px auto;
}

.labels-left .field-pair > .field + .field {
    grid-template-columns: auto auto;
}

.btn {
    display: inline-block;
    padding: 12px 28px;
    background: #0B9891;
    color: #fff;
    text-decoration: none;
    /* A transparent border of the same width the tinted variants use, so a plain
       button and a bordered one are the same height. */
    border: 1px solid transparent;
    border-radius: 6px;
    /* A <button> does not inherit the page font the way an <a> does, so without
       this a button and a link-styled button next to each other come out
       different sizes. */
    font-family: inherit;
    font-size: 1rem;
    line-height: 1.55;
    font-weight: 600;
    /* Short labels ("Save", "Reset") still fill the same footprint as longer
       ones, so a row of buttons lines up. */
    min-width: 120px;
    text-align: center;
}

.btn:hover {
    background: #09827C;
}

/* Age filter: two small number boxes reading "from to" on one line */
.age-range {
    display: flex;
    align-items: center;
    gap: 8px;
}

.age-range input {
    width: 5.5em;
    padding: 10px 8px;
    border: 1px solid #ccc;
    border-radius: 6px;
    font-size: 1rem;
}

.age-range span {
    color: #666;
    font-size: 0.9rem;
}

/* Zip is a short value everywhere — let its size="15" set the width rather than
   stretching to fill whatever column it happens to sit in. */
.labels-left #zip_code {
    width: auto;
}

/*
 * The way in, at the foot of the home page: two doors at opposite ends of one line.
 *
 * Existing Users hard left with their boxes following it, New Users hard right with
 * the button after it. space-between rather than a centred group, so the row reads
 * as two separate offers rather than one control with an alternative bolted on —
 * which is what it was when an OR sat in the middle doing the explaining.
 *
 * .hero centres its text; these are set back to the left inside each half so a
 * wrapped line starts where the eye is already looking.
 */
/*
 * Both halves packed to the left, not pushed to opposite ends.
 *
 * space-between put Sign Up hard against the right margin, which on a wide
 * screen left about four hundred pixels of nothing in the middle of the line.
 * A gap that size stops reading as spacing and starts reading as absence — the
 * eye finishing the sign-in boxes had nowhere to go and no reason to travel to
 * the far corner, so the one control on the page aimed at somebody who has
 * never been here was the easiest thing on it to miss.
 *
 * Side by side, the line reads the way it is meant to be read: here is the way
 * in if you have never been here, and here is the way in if you have. The 24px
 * column gap is enough to keep them two things rather than one.
 */
.home-login-row {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 14px 24px;
    justify-content: flex-start;
    margin-top: 26px;
    text-align: left;
}

.home-login {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-start;
    align-items: center;
    gap: 10px;
}

/* Sign Up and the words that say who it is for, kept together and leading the
   line. Which of the two comes first is decided by the markup in index.php, not
   from here. */
.home-signup {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    justify-content: flex-start;
}

/* "Existing Users" and "New Users". Heavier and darker than the field labels
   beside them: these name the two audiences, the labels only name the boxes. */
.home-login-side {
    color: #222;
    font-size: 1.05rem;
    font-weight: 700;
    white-space: nowrap;
}

/*
 * A phone puts them one above the other rather than side by side: four boxes and
 * two buttons do not fit across 390px. Both halves still start at the left
 * margin, and the buttons take the width so neither is a thumb-sized target on a
 * line of its own.
 */
@media (max-width: 760px) {
    .home-login-row {
        flex-direction: column;
        align-items: stretch;
        gap: 20px;
    }

    /*
     * Each label over its own box rather than beside it. Wrapped on one line the
     * pairs came apart — Password ended up level with the User ID box, naming the
     * field above it instead of the one below. Stacked, the order is the order it
     * is read in.
     */
    .home-login {
        align-items: stretch;
        flex-direction: column;
        gap: 6px;
    }

    /* The 220px floor is a desktop number; on a 320px screen it is what makes the
       box wider than the page. */
    .home-login input {
        min-width: 0;
        width: 100%;
    }

    /* Go and Sign Up take the width they are given: a button on its own line that
       is only as wide as its word is a small target on the device with the least
       precise pointer. */
    .home-login .btn,
    .home-signup .btn {
        margin-top: 6px;
        width: 100%;
    }

    .home-signup {
        align-items: stretch;
        flex-direction: column;
        gap: 6px;
        justify-content: flex-start;
    }
}

.home-login-label {
    color: #444;
    font-weight: 600;
    white-space: nowrap;
}

.home-login input {
    padding: 12px 14px;
    border: 1px solid #ccc;
    border-radius: 6px;
    font-size: 1rem;
    min-width: 220px;
}

.home-login-alert {
    max-width: 420px;
    margin: 18px auto 0;
}

.form-section {
    max-width: 900px;
    margin: 0 auto;
    padding: 24px 0 40px;
}

/*
 * The two gear forms get a little more room than the other forms.
 *
 * At the shared 900px the photo usage note beside the file picker ran to three
 * lines; it drops to two at 940, so this leaves a small margin on top of that
 * rather than sitting exactly on the boundary where a different font would tip
 * it back over.
 */
/* Wider than the other forms because the gear rows carry four fields rather than
   two: at 960px a quarter of the row could not hold a label and its control. */
.form-section-gear {
    max-width: 1340px;
}

.field-row {
    display: flex;
    gap: 20px;
}

.field-row .field {
    flex: 1;
    min-width: 0;
}

@media (max-width: 900px) {
    .field-row {
        flex-direction: column;
        gap: 0;
    }
}

.form-section h1 {
    margin-bottom: 14px;
}

.field {
    margin-bottom: 10px;
    display: flex;
    flex-direction: column;
}

.field label {
    font-weight: 600;
    margin-bottom: 3px;
    font-size: 0.9rem;
}

.field input,
.field select {
    padding: 10px 12px;
    border: 1px solid #ccc;
    border-radius: 6px;
    font-size: 1rem;
}

/*
 * Textareas were falling back to the browser's 2px default, so typing started
 * hard against the border. A little more room than the single-line inputs get,
 * since a block of text needs breathing space on all four sides.
 */
.field textarea {
    padding: 6px 7px;
    border: 1px solid #ccc;
    border-radius: 6px;
    font-family: inherit;
    font-size: 1rem;
    line-height: 1.45;
}

.field-error {
    color: #c0392b;
    /* Bigger and bolder than the label it sits under: if something is wrong, it
       should be the first thing you see, not the smallest. */
    font-size: 1rem;
    font-weight: 700;
    margin-top: 4px;
}

.input-short {
    max-width: 200px;
}

.input-full {
    width: 100%;
    resize: vertical;
    font-family: inherit;
    font-size: 1rem;
    padding: 10px 12px;
    border: 1px solid #ccc;
    border-radius: 6px;
}

/* Labels beside their inputs (profile & sign-up forms).
   A two-column grid, so the control is ALWAYS in column 2 next to its label and can
   never wrap onto a line of its own the way a flex row does when space runs short. */
.labels-left .field {
    display: grid;
    grid-template-columns: 185px minmax(0, 1fr);
    align-items: center;
    gap: 4px 10px;
}

/* Labels stay on one line by default; .label-wrap opts out for the long ones. */
.labels-left .field > label.label-wrap {
    white-space: normal;
}

.labels-left .field > label {
    margin-bottom: 0;
    text-align: right;
    white-space: nowrap;
}

/* Inputs/selects size to their content — a select is as wide as its longest option,
   an input follows its `size` attribute — capped at the column so they never overflow. */
.labels-left .field > input,
.labels-left .field > select,
.labels-left .field > .radio-row,
.labels-left .field > .age-range {
    justify-self: start;
    width: auto;
    min-width: 0;
    max-width: 100%;
}

/* The comments textarea fills the remaining width beside its label. */
.labels-left .field > textarea {
    width: 100%;
    min-width: 0;
}

/* Errors, stacked checkboxes and a field's own footnote drop to their own line,
   aligned under the input rather than under the label. */
.labels-left .field > .field-error,
.labels-left .field > .form-note,
.labels-left .field > .checkbox-option {
    grid-column: 2;
}

/* A note under one field is a caption, not a paragraph — the 16px top margin
   .form-note carries for standalone use leaves it floating a line clear of the
   box it belongs to. */
.labels-left .field > .form-note {
    margin-top: 4px;
}

/*
 * Between the two-column breakpoint and the phone one, the label columns have to be
 * allowed to give.
 *
 * At 1150 the edit form drops to two equal columns, each about 350px on an 800px
 * window — but every field inside still reserved a flat 185px for its label, so a
 * field whose control needed 170 spilled 12px past its column. It went unnoticed
 * while each column held one field; putting Solo Bookings and Preferred Contact in
 * the same one tipped it over, and the page grew a horizontal scrollbar at 761-900.
 *
 * minmax keeps the 185 wherever it fits and lets it shrink where it does not, which
 * is the same thing the phone block below does more aggressively.
 */
@media (max-width: 1150px) {
    .labels-left .field {
        grid-template-columns: minmax(0, 185px) minmax(0, 1fr);
    }

    .labels-left .field-pair > .field,
    .labels-left .field-pair > .field + .field {
        grid-template-columns: minmax(0, auto) minmax(0, 1fr);
    }
}

/*
 * Phone width: one column, and labels that share rather than reserve.
 *
 * Two bugs met here. The 1150 breakpoint above relaxes ".form-section-edit
 * .field-row", but the pair variant at line ~543 carries ":has(.field-pair)" and
 * is therefore MORE specific — so its fixed 570px column outlived the breakpoint
 * and kept the form 570px wide on a 371px screen. Both selectors are restated
 * below so the override actually lands.
 *
 * The second is the 185px label column: on a phone that leaves the control about
 * 150px, which is the sliver-sized boxes. A share of the row instead of a fixed
 * width keeps the label beside its box — never above it — while letting the box
 * grow as the screen does.
 */
@media (max-width: 760px) {
    .form-section-edit .field-row,
    .form-section-edit .field-row:has(.field-pair) {
        grid-template-columns: minmax(0, 1fr);
    }

    /*
     * On a phone the label goes above its box rather than beside it.
     *
     * Everywhere else the rule is labels-beside — a share of the row was the first
     * attempt here too, but 40% of 375px leaves the control about 200px, and a
     * postal address or an email in a 200px box is not a form anyone wants to fill
     * in on a phone. Stacked, every control gets the full width of the screen.
     * This is the only place the beside-rule is relaxed, and only under 760px.
     */
    .labels-left .field,
    .labels-left .field-row .field,
    .labels-left .field-pair > .field,
    .labels-left .field-pair > .field + .field {
        grid-template-columns: minmax(0, 1fr);
        align-items: start;
    }

    .labels-left .field > label {
        text-align: left;
        white-space: normal;
    }

    /* Beside its label a control sized to its content; stacked it has a whole row
       to itself, so it takes it. */
    .labels-left .field > input,
    .labels-left .field > select,
    .labels-left .field > textarea,
    .labels-left .field > .radio-row,
    .labels-left .field > .age-range {
        justify-self: stretch;
        width: 100%;
        max-width: 100%;
    }

    /* These were parked in column 2, which no longer exists. */
    .labels-left .field > .field-error,
    .labels-left .field > .form-note,
    .labels-left .field > .checkbox-option {
        grid-column: 1;
    }

    /*
     * Results tables scroll sideways on a phone rather than stretching the page.
     *
     * display:block is what makes a table scrollable at all — as a table box it
     * refuses to be narrower than its columns, so overflow on it does nothing and
     * the width lands on the page instead. As a block it takes the width it is
     * given and the columns run off inside it, where the scroll can catch them.
     *
     * nowrap on the cells is the other half: without it the browser rescues the
     * fit by wrapping every cell to one word per line, and a table that technically
     * fits is not the same as a table anyone can read.
     *
     * Phone only. Above 760px the tables are untouched table boxes as before.
     */
    .data-table {
        display: block;
        max-width: 100%;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }

    .data-table th,
    .data-table td {
        white-space: nowrap;
    }

    /* One answer made of several controls: let it wrap inside its own column
       instead of setting the width of the row. */
    .last-minute-row,
    .bookings-row {
        display: flex;
        max-width: 100%;
        min-width: 0;
    }

    .last-minute-row select,
    .bookings-row select {
        max-width: 100%;
    }
}

.radio-row {
    display: flex;
    gap: 24px;
    flex-wrap: wrap;
}

.radio-option,
.checkbox-option {
    display: flex;
    align-items: center;
    gap: 6px;
    font-weight: 400;
    cursor: pointer;
}

.radio-option input,
.checkbox-option input {
    width: auto;
}

.checkbox-option {
    margin-top: 6px;
}

.alert {
    padding: 12px 16px;
    border-radius: 6px;
    margin-bottom: 20px;
}

.alert-success {
    background: #e6f6ec;
    color: #1e7a3d;
    border: 1px solid #b7e3c5;
}

.alert-error {
    background: #fbe9e7;
    color: #c0392b;
    border: 1px solid #f3c1bb;
    font-size: 1.1rem;
    font-weight: 700;
}

.alert-warning {
    background: #fdf3e0;
    color: #8a5a12;
    border: 1px solid #f3ddb3;
}

/*
 * The one alert meant to be impossible to walk past.
 *
 * The pale alerts above are the right weight for "that saved" or "that field is
 * wrong" — this one has to stop someone who is scrolling to the bottom of a long
 * form, so it is solid red with white text rather than dark text on a tint, and
 * set several sizes up. White on #C62828 clears the 4.5:1 contrast floor, which a
 * brighter red would not: pure red takes white down to about 4:1 and would have
 * been harder to read for the sake of looking louder.
 */
.alert-shout {
    background: #C62828;
    color: #fff;
    border: 1px solid #A81E1E;
    font-size: 1.6rem;
    font-weight: 700;
    line-height: 1.3;
    padding: 18px 22px;
    text-align: center;
    /* The message is an instruction, not a label, so it keeps the sentence case it
       was written in and gets its emphasis from size and colour instead. */
    letter-spacing: 0.01em;
}

/*
 * A settled value, stated rather than offered: the profile type on Edit Profile.
 *
 * Deliberately not a disabled input. A greyed-out control reads as "you cannot
 * change this yet", which invites a hunt for the thing that would unlock it; plain
 * text reads as "this is what it is". Sized and weighted like the values in the
 * inputs beside it so the row still scans as part of the form.
 */
.field-static {
    margin: 0;
    padding: 6px 0;
    font-weight: 600;
    color: #333;
}

.form-note {
    margin-top: 16px;
    font-size: 0.9rem;
    color: #555;
}

/* No border. A member's own photograph, cropped to a circle, is a picture and not
   a component — a ring round it reads as chrome the site has added to their face. */
.banner-thumb {
    width: 84px;
    height: 84px;
    object-fit: cover;
    border-radius: 50%;
}

.subheading {
    margin-top: 32px;
    margin-bottom: 4px;
    font-size: 1.1rem;
}

.data-table {
    width: 100%;
    border-collapse: collapse;
    margin: 16px 0;
}

.data-table th,
.data-table td {
    text-align: left;
    padding: 8px 12px;
    border-bottom: 1px solid #e5e5e5;
}

/*
 * A row on the public search.
 *
 * Nothing in it is a link — the profile behind it is what joining is for — but
 * the row does do something, so it has to look like it does. A pointer and a
 * hover tint say "this responds" without promising a page, which is what an
 * underlined name would have promised.
 */
.public-row {
    cursor: pointer;
}

.public-row:hover,
.public-row:focus-within {
    background: #F1F6F6;
}

/*
 * The line on the public search that asks for the sign-up.
 *
 * It is the only sentence on that page doing any selling, and .form-note is the
 * site's grey small print — right for a footnote, wrong for the one thing a
 * visitor is meant to read. Bigger, bold, and in the same red the alerts use
 * rather than a new one invented for this.
 *
 * The links inside it take the colour too. A blue link inside a red bold line
 * reads as two different voices; the underline is what still says they are
 * links, so nothing is lost by letting them match.
 */
.search-public-intro {
    color: #c0392b;
    font-size: 1.05rem;
    font-weight: 700;
}

.search-public-intro a {
    color: inherit;
    text-decoration: underline;
}

/* Your Instruments and Your Genres sit side by side, each in its own panel.
   align-items: start so the shorter panel doesn't stretch to match the taller one. */
.panel-row {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 32px;
    align-items: start;
}

/*
 * A panel may be narrower than what is inside it.
 *
 * A grid item's floor is its content by default, so the Instruments panel took the
 * width of its own table — which on a phone is a nowrap table with a scroll of its
 * own and is meant to be allowed to overflow inside the panel, not to widen it.
 * Without this the panel stood 25px wider than the page at 320, and took the page
 * with it. minmax(0, 1fr) above says the same thing about the column; this says it
 * about the item sitting in it.
 */
.panel {
    min-width: 0;
}

/* Three when there is a third: a tribute act gets Instruments, Genres and Tributes
   on one set of rows rather than two panels and an orphan underneath. The gap comes
   in because thirds of the same page are narrower than halves and the space between
   them was sized for halves. */
.panel-row-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 22px;
}

/* The controls give up the room the third panel needs. Every panel's heading row
   carries a dropdown and a button, and Instruments carries two dropdowns — at a
   third of the width those wrap onto three lines each unless they shrink. Smaller
   here only: the two-panel layout has the room and keeps its own sizes. */
.panel-row-3 .panel-heading-row {
    gap: 6px 8px;
}

.panel-row-3 .panel-heading-row select,
.panel-row-3 .panel .inline-form-row select {
    max-width: 104px;
    font-size: 0.82rem;
    padding: 5px 2px 5px 6px;
}

.panel-row-3 .panel-heading-row .btn,
.panel-row-3 .panel .inline-form-row .btn {
    padding: 6px 10px;
    font-size: 0.82rem;
}

/* The Remove buttons down the lists, which are the widest thing in a narrow panel
   after the name itself. */
.panel-row-3 .panel .data-table .btn-small {
    padding: 3px 8px;
    font-size: 0.76rem;
}

.panel-row-3 .panel .data-table {
    font-size: 0.82rem;
}

.panel-row-3 .panel .data-table th,
.panel-row-3 .panel .data-table td {
    padding: 3px 6px;
}

/* Thirds need the width. Below this they are narrower than the controls in them,
   so the row goes back to halves and the third panel sits under the first two —
   which is the old behaviour, and fine on a screen that cannot hold three. */
@media (max-width: 1180px) {
    .panel-row-3 {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 32px;
    }
}

@media (max-width: 760px) {
    .panel-row,
    .panel-row-3 {
        grid-template-columns: 1fr;
        gap: 8px;
    }
}

.panel .subheading {
    margin-top: 0;
}

/* Compact rows inside the panels — these lists are reference, not the main event */
.panel .data-table {
    margin: 10px 0;
    font-size: 0.88rem;
}

.panel .data-table th,
.panel .data-table td {
    padding: 4px 8px;
}

.panel .inline-form-row select {
    padding: 6px 8px;
    font-size: 0.88rem;
}

.panel .inline-form-row .btn {
    padding: 8px 16px;
}

.inline-form {
    display: inline;
}

.inline-form-row {
    display: flex;
    gap: 10px;
    align-items: center;
    flex-wrap: wrap;
}

.inline-form-row select {
    padding: 8px 10px;
    border: 1px solid #ccc;
    border-radius: 6px;
}

/* Primary + secondary form buttons sit side by side on one row */
.form-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    margin-top: 6px;
}

/* Search form's buttons sit centred under the filter grid */
.form-actions-center {
    justify-content: center;
    margin-top: 14px;
}

/*
 * The quiet button: Back, and the Previous/Next pager.
 *
 * A filled pale grey rather than the white it used to be. Every button on the site
 * carries a coloured background — white on white reads as an outline drawn round
 * nothing, and next to the tinted buttons it looked unfinished rather than quiet.
 * Same construction as the soft blue and soft green below it: pale fill, dark text,
 * a mid-tone border half a step darker than the fill.
 */
.btn-secondary {
    background: #E8EBEE;
    color: #2F363C;
    border: 1px solid #CFD5DB;
}

.btn-secondary:hover {
    background: #D8DDE3;
}

/* Tinted secondary buttons. Dark text on a pale fill rather than white on a solid
   one — white on mid-tone blue or orange lands well under the 4.5:1 contrast floor. */
.btn-lightblue {
    background: #CFE6F8;
    color: #0D3A52;
    border: 1px solid #A8CEEA;
}

.btn-lightblue:hover {
    background: #B7D9F2;
}

/* Reset buttons. The class covers link-styled ones; the attribute selectors mean a
   real <button type="reset"> picks the colour up without being told. */
.btn-lightred,
.btn[type="reset"],
button[type="reset"],
input[type="reset"] {
    /* Solid red rather than the old pale pink. #C62828 is dark enough to carry
       white text at better than the 4.5:1 contrast floor. */
    background: #C62828;
    color: #fff;
    border: 1px solid #A81E1E;
}

.btn-lightred:hover,
.btn[type="reset"]:hover,
button[type="reset"]:hover,
input[type="reset"]:hover {
    background: #A81E1E;
}

.btn-orange {
    background: #F7A44A;
    color: #4A2600;
    border: 1px solid #E08C2E;
}

.btn-orange:hover {
    background: #F29331;
}

dialog {
    border: none;
    border-radius: 10px;
    padding: 24px;
    max-width: 420px;
    width: 90%;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}

dialog::backdrop {
    background: rgba(0, 0, 0, 0.5);
}

.dialog-actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 20px;
}

/* .btn-link is gone: a button with `background: none` was the one style on the
   site that broke the rule that a button always carries a coloured background.
   Nothing used it. A control that should read as a link should be a link. */

/* The search page carries wide filter rows and a results table, so it uses the
   full container. Left here rather than deleted so the intent is obvious: it was
   capped at 900px back when the filters were two narrow boxes. */
.search-section {
    max-width: none;
    /* The results sit directly beneath, so the usual bottom padding is trimmed —
       the two panels read as one page rather than two stacked forms. */
    padding-bottom: 16px;
}

/* Profile gallery: pictures and videos on the Photos & Videos page */
/*
 * Pictures on the left, videos on the right.
 *
 * Two equal halves of the same row rather than two sections down the page. minmax
 * (0, 1fr) rather than a bare 1fr: a grid column sizes to its content by default,
 * and a wide photo would otherwise push its half past the halfway line and shove
 * the videos off the row this exists to keep them on.
 */
/* Heading on the left, the way out hard against the right, on one line — the same
   shape Edit Profile and the media window both use. */
.media-heading {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
}

.media-heading-back {
    flex: none;
}

/*
 * Three equal tracks, shared by the add line and the galleries under it: pictures,
 * videos, audio.
 *
 * There used to be a fourth, a fixed 170px holding the Back button, which pushed the
 * three that matter into two thirds of the page and left the reserved column empty
 * below the fold. The button moved up to the heading and the width came back to the
 * content.
 */
.media-columns,
.media-add-row {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 34px;
    align-items: start;
}

.media-add-row {
    align-items: center;
    margin-bottom: 8px;
}

/* Label above its control and the pair centred, so each one sits over its own
   list rather than beside a label out in the margin. */
.media-add-cell {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

.media-add-cell label {
    font-weight: 600;
}

/*
 * The gap between the add line and the two headings, closed up.
 *
 * It was 104px, built out of four separate pieces that each looked reasonable on
 * their own: 8px under the add row, 40px of section padding below, 24px above, and
 * a 32px margin on the headings. Between two halves of the same thing it read as a
 * missing element rather than as breathing room. Trimmed to about 20px, and scoped
 * to this page so .form-section keeps its spacing everywhere else.
 */
/*
 * The gallery page takes the full container rather than the 900px reading width.
 *
 * .form-section is sized for forms — a column of labelled fields is unreadable when
 * it runs the width of a wide screen. This page is not that: it is three columns of
 * pictures and players, and every pixel the cap was holding back went straight into
 * how big they are. At 900px the three columns were about 280px each, which is a
 * thumbnail; at full width they are about 450px, which is something you can judge a
 * photograph by.
 */
.media-add-section,
.media-galleries {
    max-width: none;
}

.media-add-section {
    padding-bottom: 12px;
}

.media-galleries {
    padding-top: 0;
}

.media-galleries .subheading {
    margin-top: 0;
}

/* A second way back, for when five of each has pushed the first one off screen —
   and the audio column has no ceiling at all, so it may be a long way off. */
.media-back-bottom {
    margin-top: 26px;
}

/*
 * An audio card is a row of controls, not a picture, so it does not take the 300px
 * box the other two share — it is only as tall as the player. Full width of its
 * column, because a player narrower than its own scrub bar is unusable.
 */
.media-card-audio audio,
.audio-item audio {
    width: 100%;
    display: block;
}

.media-card-audio {
    padding-bottom: 8px;
}

/*
 * On a profile, audio is a list rather than a grid.
 *
 * Two columns on a wide screen so a long discography does not run the page off the
 * bottom, one on anything narrower. Each track is its name over its player: the name
 * above rather than beside, because a filename can be long and a player cannot be
 * squeezed.
 */
.audio-list {
    display: grid;
    /* min() on the floor, not a bare 340px: below a 340px column the track would
       hold its width and push the page out sideways instead of narrowing. The
       smallest phones are under that, and a player is one of the few things that
       will happily be 280px wide. */
    grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr));
    gap: 14px 22px;
    margin-top: 12px;
}

.audio-item {
    background: #fff;
    border: 1px solid #e5e5e5;
    border-radius: 10px;
    padding: 10px 12px;
    min-width: 0;
}

.audio-title {
    font-size: 0.88rem;
    font-weight: 600;
    margin-bottom: 6px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.media-column {
    min-width: 0;
}

/*
 * Within a column, one per line rather than a wall of thumbnails.
 *
 * These are the pictures and clips a member is deciding whether to keep — worth
 * actually looking at, which four-across at 220px never allowed.
 */
.media-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 18px;
    margin-top: 12px;
}

.media-card {
    background: #fff;
    border: 1px solid #e5e5e5;
    border-radius: 10px;
    padding: 10px;
}

/*
 * Contained rather than cropped, now that there is room to be.
 *
 * At 160px in a narrow column, `cover` was the right call — a cropped thumbnail
 * reads better than a letterboxed one. At this size it is the wrong one: a portrait
 * photo or a phone video would have its top and bottom cut off, and the point of
 * the bigger box is to see what you actually uploaded. The tinted backdrop is what
 * fills the space either side of a tall one.
 */
.media-card img,
.media-card video {
    width: 100%;
    /* Grown with the page. The columns are about 450px across now the 900px cap has
       gone, and a 300px-tall box in a 450px-wide column wastes the width on a
       portrait shot without helping a landscape one. */
    height: 400px;
    object-fit: contain;
    border-radius: 6px;
    background: #f0f0f5;
    display: block;
}

/* Too narrow for two halves: everything stacks, the reserved button track goes,
   and each item takes the width back. */
/* Three columns need room. Between these widths they drop to two across rather
   than being squeezed to the point where none of the three is usable. */
@media (max-width: 1180px) and (min-width: 761px) {
    .media-columns,
    .media-add-row {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 760px) {
    .media-columns,
    .media-add-row {
        grid-template-columns: minmax(0, 1fr);
        gap: 14px;
    }

    .media-card img,
    .media-card video {
        height: 260px;
    }
}

/*
 * A clip in a gallery is a still with a play badge on it, and the whole still is the
 * link that opens it in a window.
 *
 * The badge is drawn rather than an image file: it is two shapes, it has to sit on
 * top of whatever frame happens to be underneath it, and a PNG would need a second
 * one for the hover state.
 */
.media-open {
    position: relative;
    display: block;
    cursor: pointer;
}

.media-play {
    position: absolute;
    inset: 0;
    margin: auto;
    width: 62px;
    height: 62px;
    border-radius: 50%;
    /* Dark disc rather than light: the frame behind it is unknown, and a dark disc
       with a white triangle reads on both a bright stage shot and a black one. */
    background: rgba(0, 0, 0, 0.55);
    border: 2px solid rgba(255, 255, 255, 0.9);
    transition: background 0.12s ease, transform 0.12s ease;
    pointer-events: none;
}

/* The triangle, made from a border. Nudged right by 3px because a triangle looks
   off-centre in a circle when its bounding box is centred — the eye centres on the
   shape's weight, which sits left of its middle. */
.media-play::after {
    content: '';
    position: absolute;
    inset: 0;
    margin: auto;
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 11px 0 11px 18px;
    border-color: transparent transparent transparent #fff;
    transform: translateX(3px);
}

.media-open:hover .media-play,
.media-open:focus-visible .media-play {
    background: rgba(0, 0, 0, 0.75);
    transform: scale(1.06);
}

/* The still is not a player, so it takes no clicks of its own — every one of them
   belongs to the link around it. */
.media-open video {
    pointer-events: none;
}

.media-card figcaption {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-top: 8px;
    font-size: 0.82rem;
}

.media-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The results table wants the same full width as the filters above it. */
.results-section {
    max-width: none;
    /* Matches the trim above: the count heading starts close under the buttons
       instead of a screen's worth of empty space away. */
    padding-top: 8px;
}

/* The section's own padding already provides the separation. */
.results-section > .subheading:first-child {
    margin-top: 0;
}

.result-thumb-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    background: #f0f0f5;
    color: #9a9ab0;
    font-size: 1.2rem;
}

/* The profile page stacks several sections, so the default 24/40 padding adds up
   to a lot of dead space. Scoped to this page only. */
.profile-section {
    padding: 10px 0 14px;
}

.profile-section h1 {
    margin-bottom: 2px;
}

.profile-section .subheading {
    margin-top: 14px;
}

/* Second and later headings inside a panel still need a little air above them */
.profile-section .panel .subheading:not(:first-child) {
    margin-top: 18px;
}

.profile-section .form-note {
    margin-bottom: 2px;
}

.profile-section .data-table {
    margin: 8px 0;
}

.profile-section .media-grid {
    margin-top: 8px;
}

/* Public profile page */
/*
 * The head and the Contact button share one grid cell: the head spans both
 * columns so the name line never wraps, and the button sits in column two
 * bottom-aligned, which puts it on the Last Active line with its left edge on
 * the Genres heading below. Nothing here depends on a measured offset.
 */
.profile-top {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 32px;
}

.profile-top > .profile-head {
    grid-area: 1 / 1 / 2 / 3;
}

.profile-top > .profile-contact {
    grid-area: 1 / 2 / 2 / 3;
    align-self: end;
}

.profile-top > .profile-contact p {
    margin: 0;
}

@media (max-width: 760px) {
    /* One column: let the button fall underneath instead of overlapping. */
    .profile-top {
        grid-template-columns: 1fr;
        gap: 12px;
    }

    .profile-top > .profile-head,
    .profile-top > .profile-contact {
        grid-area: auto;
    }
}

.profile-head {
    display: flex;
    gap: 18px;
    align-items: flex-start;
}

/* Name and the "Band · location" line share one baseline. */
.profile-name-row {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 4px 14px;
}

.profile-name-row h1,
.profile-name-row .form-note {
    margin: 0;
}

/* Borderless, the same as every other place a member's photograph is shown. */
.profile-avatar {
    width: 110px;
    height: 110px;
    object-fit: cover;
    border-radius: 50%;
    flex-shrink: 0;
}

.profile-avatar-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    background: #f0f0f5;
    color: #9a9ab0;
    font-size: 2.4rem;
}

.profile-about {
    /* full width of the section rather than a narrow measure */
    width: 100%;
    line-height: 1.6;
}

.result-genres {
    font-size: 0.8rem;
    color: #2f7d5c;
    margin-bottom: 4px;
    line-height: 1.35;
}

.muted {
    color: #999;
}

.site-footer {
    text-align: center;
    padding: 24px 0;
    color: #888;
    font-size: 0.85rem;
    border-top: 1px solid #e5e5e5;
}

/* `display: grid`/`flex` on these beats the browser's built-in [hidden] rule, so a
   field or row toggled off in JavaScript needs this to actually disappear. */
.labels-left .field[hidden],
.field-row .field[hidden],
.field-row[hidden] {
    display: none;
}

/* Two fields sharing one column, only one of them shown at a time. The wrapper
   holds the slot either way, so its neighbour never slides across the form. */
.field-swap {
    flex: 1;
    min-width: 0;
}

/* A hidden panel must actually collapse: .panel sets its own display. */
.panel[hidden] {
    display: none;
}

/* ---- Gear Marketplace ---------------------------------------------------- */

/* Forms read better narrow, but a table of listings wants the whole container. */
/*
 * The two profile forms run wider than the rest of the site: Edit Profile so
 * "Your Instruments" can keep its heading, both dropdowns and the Add button on
 * one line, and both of them so Last Minute, Profile Status and Visibility fit
 * across a single row. Every section on those pages carries it, so the blocks
 * still line up down the left edge.
 */
.form-section-edit {
    /* 570 for the first column, 20 gap, 500 for the second — the widest thing
       column two has to hold is 498px. Centred in the container, as .form-section
       has always been. */
    max-width: 1090px;
}

.form-section-wide {
    max-width: none;
}

/* Let the long columns take the slack rather than padding every column evenly.
   Not 40% any more: Brand and Model arrived beside it and were being squeezed onto
   two lines each while the Headline sat on half-empty rows. */
.gear-table .gear-col-description {
    width: 26%;
}

/* Brand and Model are short, and reading them is the point of putting them next to
   each other — wrapping "Twin Reverb" over two lines defeats that. They get the
   width they need before the Headline takes the rest. */
.gear-table th:nth-child(3),
.gear-table td:nth-child(3),
.gear-table th:nth-child(4),
.gear-table td:nth-child(4) {
    white-space: nowrap;
}

/* Heading, Add Gear and the Active/Inactive filter all on one line. */
.gear-heading {
    display: flex;
    align-items: center;
    gap: 20px;
    flex-wrap: wrap;
    margin-bottom: 20px;
}

.gear-heading h1 {
    margin: 0;
}

/*
 * The status word inside "My Active Gear" / "My Inactive Gear", coloured so the
 * heading carries the state as well as saying it.
 *
 * Both colours are the ones the page already uses for these two ideas — the
 * success-alert green and the Deactivate-button red — rather than new ones. Both
 * clear 4.5:1 on the page background, so the colour is reinforcement here and not
 * the only thing carrying the meaning.
 */
.gear-heading-status.is-active {
    color: #1e7a3d;
}

.gear-heading-status.is-inactive {
    color: #C62828;
}

/* Sits a fixed 2in right of the Add Gear button. Deliberately not tied to a table
   column: with no listings there is no table, and the filter would fly to the far
   right just when you need it to switch back. */
.gear-filter {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-left: 2in;
}

.gear-filter-label {
    font-weight: 600;
}

.gear-table th,
.gear-table td {
    /* The row's height is set by the buttons and the photo thumbnail, both taller
       than a line of text. Centring every cell puts the text on the same
       centre-line as the buttons instead of floating above them. */
    vertical-align: middle;
}

/*
 * Written as .data-table .gear-price rather than .gear-price alone: the base
 * ".data-table th, .data-table td { text-align: left }" rule is two classes deep
 * and would otherwise win.
 */
.data-table th.gear-price,
.data-table td.gear-price {
    white-space: nowrap;
    text-align: right;
}

.gear-thumbs {
    display: flex;
    gap: 6px;
}

.gear-thumb {
    width: 56px;
    height: 56px;
    object-fit: cover;
    border-radius: 4px;
    border: 1px solid #ddd;
    display: block;
}

/* A table cell is no place for a full-size button. */
.btn-small {
    padding: 6px 14px;
    font-size: 0.9rem;
    /* Row-level buttons sit inside a table cell, so they use a narrower common
       width than the full-size ones — wide enough that Modify and Deactivate
       still match each other. */
    min-width: 100px;
}

/* The two date columns sit at the end of the row; keep each on one line. */
.gear-date {
    white-space: nowrap;
}

/* Modify and Delete share the first cell, side by side. */
.gear-actions {
    white-space: nowrap;
}

.gear-actions .btn {
    margin-right: 6px;
}

/* Editing a listing: each photo with its own Remove tick box. */
.gear-photo-edit {
    display: flex;
    gap: 14px;
    flex-wrap: wrap;
}

.gear-photo-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    font-size: 0.9rem;
    /* The Main badge is positioned against this card. */
    position: relative;
}

/*
 * Position 1 is the photo that stands in for the whole listing wherever there is
 * only room for one, so it is called out rather than left to be inferred from
 * being first in the row.
 */
.photo-main-badge {
    position: absolute;
    top: 4px;
    left: 4px;
    z-index: 1;
    padding: 1px 6px;
    border-radius: 4px;
    background: #0B7C76;
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    /* Photos are of anything, so the badge needs to hold up over a light one. */
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}

.gear-photo-card.is-main .gear-thumb-large {
    outline: 2px solid #0B7C76;
    outline-offset: 1px;
}

/* Picking the main photo. The outline above shows the saved state; this is the
   control that changes it, and it takes effect on Save Changes like every other
   field on the form. */
.photo-main-pick {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 0.82rem;
    font-weight: 600;
    color: #0B7C76;
    cursor: pointer;
}

.photo-main-pick input {
    width: auto;
    margin: 0;
    cursor: pointer;
}

/* Same idea in the seller's small thumbnail strip, where a badge would not fit. */
.gear-thumb-link.is-main .gear-thumb {
    outline: 2px solid #0B7C76;
    outline-offset: 1px;
}

/*
 * Equipment is square wherever it appears.
 *
 * The buying results borrow .result-thumb-small from Find Musicians, where a
 * circle is right because the subject is a face and the corners hold nothing. A
 * circle applied to a speaker cabinet or a pedalboard crops off the ends of the
 * thing you are trying to identify, so gear takes the same lightly-rounded square
 * as the thumbnails on My Gear. Covers the music-note placeholder too, since it
 * carries the same class.
 */
.gear-result-table .result-thumb-small {
    border-radius: 4px;
    border: 1px solid #ddd;
}

/* "+2" beside the main thumbnail: how many more are on the listing. Quiet, because
   it is a count and not a control — the photos themselves are on the edit page. */
.gear-thumb-count {
    font-size: 0.78rem;
    font-weight: 600;
    white-space: nowrap;
}

.gear-thumb-large {
    width: 120px;
    height: 120px;
    object-fit: cover;
    border-radius: 6px;
    border: 1px solid #ddd;
    display: block;
}

/* A dollar sign sitting against the amount box. */
.money-input {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.money-symbol {
    color: #555;
    font-weight: 600;
}

/*
 * Amounts read right to left: the units column should always land in the same
 * place, whether the figure is typed into a box or printed in a table.
 */
.money-input input,
.price-range input {
    text-align: right;
}

/*
 * Asking Price: the $ sits inside the box, and the box lines up with the
 * Description field below it.
 *
 * In flow ahead of the input, the symbol and its gap pushed the box about 13px
 * right of everything else in the column, so the price field alone sat proud of
 * its neighbours. Inside, the wrapper's left edge is the input's left edge, which
 * is the column edge, and nothing else has to move to accommodate it.
 *
 * Scoped to .field so the Price range on the buying filters, which pairs two
 * inputs around a "to" and has no column to line up with, is left alone.
 */
.field > .money-input {
    position: relative;
}

.field > .money-input > .money-symbol {
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    /* Clicks land in the input, not on the symbol sitting over it. */
    pointer-events: none;
}

/* Asking Price: wide enough for 99,999.99 with the $ inside it. 9ch is the
   digits, 26px the input's own padding and border, and the last 13px is the room
   the symbol needs — so the number still has exactly the space it had when the $
   was outside the box. */
.money-input input.money-input-max {
    /* 9ch is the digits, 25px the room the $ needs on the left, 14px the input's
       own right padding and borders, and the last 4px is slack — at the exact
       figure the text sat a sub-pixel over the edge and clipped. */
    width: calc(9ch + 43px);
    padding-left: 25px;
}

/* Filename under a to-be-uploaded thumbnail. */
.gear-photo-name {
    max-width: 120px;
    font-size: 0.8rem;
    color: #555;
    overflow-wrap: anywhere;
    text-align: center;
}

/* File picker with its usage note alongside rather than on the next line. */
.file-with-note {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    /* Deliberately not wrapping. With wrap on, a note long enough to run out of
       room dropped the whole thing onto the next line, under the picker — the one
       place this rule exists to stop it going. It wraps inside its own column
       instead, which is what min-width: 0 below allows. */
}

/* The picker keeps its natural width; the note takes what is left. */
.file-with-note > input[type="file"] {
    flex: 0 0 auto;
}

.file-with-note .form-note {
    margin: 0;
    min-width: 0;
}

/* Buying / Selling mode switch, pinned to the right of the heading line. */
.gear-modes {
    display: flex;
    gap: 12px;
    margin-left: auto;
}

/* Soft fills with dark text, matching the tinted buttons elsewhere. */
.btn-soft-blue {
    background: #DCE9F7;
    color: #143C5C;
    border: 1px solid #BBD4EC;
}

.btn-soft-blue:hover {
    background: #C9DDF2;
}

.btn-soft-green {
    background: #DFF1E4;
    color: #14512C;
    border: 1px solid #BADFC6;
}

.btn-soft-green:hover {
    background: #CDE9D6;
}

/* Which mode you are in, without changing the button's colour family. */
.gear-modes .is-current {
    box-shadow: inset 0 -3px 0 rgba(0, 0, 0, 0.35);
    font-weight: 700;
}

/* Marketplace disclaimer above the copyright line: deliberately small and quiet,
   readable rather than hidden. Left-aligned because centred paragraphs of this
   length are hard to read. */
.footer-disclaimer {
    /* No narrow cap: the footer already sits inside .container, so this runs the
       full page width and takes fewer lines. */
    margin: 0 0 16px;
    padding-bottom: 16px;
    border-bottom: 1px solid #ececec;
    font-size: 0.72rem;
    line-height: 1.5;
    color: #999;
    text-align: left;
}

.footer-disclaimer-heading {
    font-weight: 600;
    color: #777;
}

/* Buying filters: a price from/to pair on one line. */
.price-range {
    display: flex;
    align-items: center;
    gap: 6px;
}

.price-range input {
    min-width: 0;
}

/* Search and Reset centred under the buying filters. */
.gear-search-actions {
    justify-content: center;
}

/* The Back button under the results, on its own line at the left where the page's
   own content starts rather than centred under a table it does not belong to. */
.gear-back-actions {
    justify-content: flex-start;
    margin-top: 22px;
}

/* "Within [25 miles] of zip [_____]" on one line. */
.radius-range {
    display: flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}

/* Stop the flex row squeezing the dropdown down to "An…". */
.radius-range select {
    flex: 0 0 auto;
    min-width: 86px;
    width: auto;
}

.radius-range input {
    flex: 0 0 auto;
    width: 68px;
    min-width: 0;
}

/* Year is a number in a column of words: right-aligned so the digits line up,
   and no wider than it needs to be. */
.gear-result-table th.gear-year,
.gear-result-table td.gear-year {
    text-align: right;
    white-space: nowrap;
    width: 1%;
}

/*
 * The buying filters, held in from the full page width.
 *
 * .form-section-wide runs to 1500px so the results table has room, but the four
 * filter columns do not need it: stretched that far, a right-aligned label ends
 * up a long way from the control it names.
 */
.gear-buy-filters {
    /* 40px more than the 1120 it held before. Within moved off the wide first
       column onto the wide third one, and between the three narrow columns and
       the gaps there were 7px too few for it. Still far short of the 1500 the
       results table below runs to. */
    max-width: 1160px;
}

/*
 * More air between the buying filters than the shared four-column row gives.
 *
 * Within is not a single control but three — a select, the words "miles of zip",
 * and an input — so it runs wider than its column and its neighbour's label ends
 * up sitting against the zip box. Widening the column gap and the label gutter
 * separates them without moving anything onto another line.
 */
.gear-buy-filters .field-row-4 {
    gap: 12px 28px;
    /* Not four equal columns. Within is three controls in a trench coat — a
       select, the words "miles of zip", and an input — and at an even quarter of
       the row it overflowed into the next field's label. The third column is the
       wide one because that is where Within sits; Brand shares it on the row
       above, and the two line up. */
    grid-template-columns: 1fr 1fr 1.33fr 1fr;
}

.gear-buy-filters .field-row-4 .field {
    column-gap: 12px;
}

/* Zip box with the state it resolves to sitting beside it. */
.zip-with-state {
    display: inline-flex;
    align-items: center;
    gap: 10px;
}

.zip-state {
    font-size: 0.9rem;
    color: #0B7A74;
    font-weight: 600;
}

.zip-state-unknown {
    color: #c0392b;
    font-weight: 400;
}

/* Four filters across, for the wider search layout. */
/* Grid rather than flex: four columns that stay exactly equal whatever their
   contents, so this row's controls line up with the row above it. A flex item
   grows past its basis when its content will not shrink, which is what knocked
   the distance filter out of column. */
.field-row-4 {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
}

.field-row-4 .field {
    min-width: 0;
}

/*
 * Four across on the gear form.
 *
 * Not four equal columns: the Item dropdown is as wide as the longest name in the
 * catalogue, and "In-Ear Monitor (IEM) Transmitter" needs about 270px where an
 * even quarter of the row leaves 177. The second column, Item's, takes the extra.
 * The fourth carries Model and Headline, both of which wanted about 20px more
 * than an equal share, so it takes a slightly larger one. Columns one and three
 * split what is left. All four are fixed shares, so the second row's controls
 * line up under the first row's, and one even gutter runs between them.
 */
.gear-field-row-4 {
    display: grid;
    grid-template-columns: 1fr 1.35fr 1fr 1.07fr;
    gap: 4px 16px;
    align-items: start;
}

.gear-field-row-4 .field {
    min-width: 0;
}

/*
 * The gear form's label column, narrower than the 185px the profile forms use.
 *
 * Four controls to a row is what this is for: at 185px the label would take most
 * of a column before its control started, and the Item dropdown alone needs 200px
 * to show its longest option. 112px is what the longest label in the form needs —
 * "What's Included", not one of the four-across ones — and the labels stay
 * right-aligned, so every control in the form, the four-across rows and the full
 * width fields below them alike, starts on the same left edge.
 */
.labels-left.gear-form .field {
    grid-template-columns: 112px minmax(0, 1fr);
    gap: 4px 8px;
}

/*
 * Headline and What's Included, two to a row.
 *
 * Even halves, and each box fills its half, so the pair reaches exactly the same
 * right-hand edge as the Description box below them. Both hold a phrase rather
 * than a word — a list of what comes in the box, a line about the service history
 * — and the 50-character box they were sized to ran out long before the fields do.
 */
.gear-field-row-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4px 16px;
    align-items: start;
}

.gear-field-row-2 .field {
    min-width: 0;
}

.labels-left.gear-form .gear-field-row-2 .field > input {
    width: 100%;
}

/* Nudge the middle two columns right for a little breathing room after the
   distance control. Applied to the column, so the row beneath moves with it and
   Age stays under Skill Level, Last Active under Genre. */
.field-row-4 .field:nth-child(2),
.field-row-4 .field:nth-child(3) {
    padding-left: 12px;
}

/* Rows that only use two of the four columns: the pair sits together on the left,
   so give the second column a wider gap than the packed four-across rows need. */
.field-row-pair .field:nth-child(2) {
    padding-left: 48px;
}

/* A narrower label column buys the distance control enough room to sit inside its
   cell, which is what keeps the columns of the two rows lined up. */
.labels-left .field-row-4 .field {
    grid-template-columns: 80px minmax(0, 1fr);
    gap: 4px 8px;
}

/* Deliberately NOT a narrower label here: keeping 110px like its neighbours is
   what puts the distance dropdown directly under Instrument. */
.labels-left .field-row-4 .field:has(.radius-range) .radius-range {
    gap: 4px;
}

.labels-left .field-row-4 .radius-range select {
    min-width: 0;
    width: auto;
}

/* Trim the generous side padding rather than the box, so five digits still show. */
.labels-left .field-row-4 .radius-range input {
    width: 58px;
    padding-left: 6px;
    padding-right: 6px;
    text-align: center;
}

/* One row, four groups of very different natural widths (a short dropdown, the
   two-part Within control, and two longer dropdowns). An equal four-column grid
   leaves them looking ragged, so this sizes each group to its own content and
   shares the leftover space out evenly between them. Within counts as one group
   because its parts sit inside a single .field. */
.field-row-even {
    display: flex;
    flex-wrap: wrap;
    /* Grouped in the middle rather than pushed out to the edges. Spread across the
       full width, three filters read as three unrelated corners of the form; sized
       to their content and centred, they read as one line of controls. The 40px is
       enough to keep each label with its own control and not the one before it. */
    gap: 12px 40px;
    justify-content: center;
}

/* The row also carries .field-row, whose `flex: 1` was stretching each group to
   an equal share of the width — which is why centring did nothing: three groups
   filling the line have no spare space to be centred in. Sized to their content,
   they gather in the middle. */
.field-row-even > .field {
    flex: 0 0 auto;
}

.labels-left .field-row-even .field {
    display: grid;
    gap: 4px 8px;
    /* auto rather than the shared 80px label column: each group stands on its
       own here, so the label sits directly against its control. */
    grid-template-columns: auto auto;
    padding-left: 0;
}

/* Band Status and the About box directly beneath it share one label column, so
   their controls start at the same point down the page. Fixed rather than auto
   because the two rows use different layouts — an auto column would settle at a
   different width in each. */
.labels-left .field-row-4 .field-anchor-left {
    grid-template-columns: 143px auto;
}

@media (max-width: 1100px) {
    .field-row-4 {
        flex-wrap: wrap;
    }

    .field-row-4 .field {
        flex: 1 1 40%;
    }
}

/*
 * Panel heading with its add controls underneath.
 *
 * Both panels use this, and they used to disagree: the instruments form carries two
 * dropdowns and a button, so it wrapped below its heading, while the shorter genre
 * form fitted beside its own — two panels side by side, built the same way, looking
 * like two different components. Giving the heading the full row settles it in the
 * one direction that holds however long the controls get.
 */
.panel-heading-row {
    display: flex;
    align-items: center;
    gap: 10px 14px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}

.panel-heading-row .subheading {
    margin: 0;
    flex: 1 0 100%;
}

.panel-heading-row .inline-form-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
}

.panel-heading-row .form-note {
    margin: 0;
}

/* Two dropdowns plus a button have to share the line with the heading, so cap
   them rather than letting the row wrap. */
.panel-heading-row select {
    max-width: 122px;
    padding-right: 4px;
}

.panel-heading-row .btn {
    padding: 8px 14px;
    white-space: nowrap;
}

/* Search results as rows rather than cards. */
.result-table th,
.result-table td {
    vertical-align: middle;
}

/*
 * A results table that will not fit scrolls sideways rather than taking the page
 * with it.
 *
 * Find Musicians and the gear marketplace both carry a lot of columns, and below
 * about 1150px their narrowest honest width is wider than the column they are
 * given — 827 in 745 on Find Musicians, 1117 in 860 on the marketplace. There is
 * no arranging around that: the columns are all wanted, and the browser has
 * already wrapped everything it can. So the table gets its own scroll and the
 * page keeps its edges, instead of every page on the site sliding sideways
 * because one table underneath it is too wide.
 *
 * The phone block does the same thing at 760 for every .data-table, and adds
 * nowrap because a phone-width table wraps to one word per line otherwise. Here
 * the cells are left to wrap: at these widths wrapping usually gets the table
 * inside the box on its own, and the scroll is only there for when it cannot.
 *
 * Above 1150 nothing changes — the tables fit, and an overflow container with
 * nothing overflowing shows no scrollbar anyway.
 */
@media (max-width: 1150px) {
    /* .gear-table is the selling list, which is not a .result-table but is the
       widest table on the site — eleven columns, and the one the marketplace opens
       on. Same treatment. */
    .result-table,
    .gear-table {
        display: block;
        max-width: 100%;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }
}

.result-avatar-cell {
    width: 66px;
}

.result-thumb-small {
    width: 53px;
    height: 53px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
}

/* Placeholder for anyone without a picture. */
div.result-thumb-small {
    display: flex;
    align-items: center;
    justify-content: center;
    background: #eee;
    color: #999;
}

.result-name-link {
    font-weight: 600;
    white-space: nowrap;
}

/* Gigs, sat in front of the band name in the Find Bands results. Small and quiet
   enough that the name is still what the eye lands on, and nowrap so the button and
   the name never end up on separate lines with the button orphaned. */
.result-band-cell {
    white-space: nowrap;
}

.result-gigs-btn {
    padding: 2px 10px;
    font-size: 0.74rem;
    margin-right: 8px;
    vertical-align: middle;
    /* .btn carries min-width: 100px so buttons on a form line match each other.
       In a table row that is four times the width of the word, and it pushes the
       band name away from the edge it should be reading from. */
    min-width: 0;
}

/*
 * A phone number that dials, without looking like a link.
 *
 * It is an <a href="tel:"> so that a phone rings it, but underlined and coloured it
 * read as somewhere to navigate to — and on a desktop, where tel: does nothing much,
 * a number is something you read off the screen and dial yourself. So it keeps the
 * behaviour and drops the costume, and only shows its hand on hover.
 */
.phone-link {
    color: inherit;
    text-decoration: none;
    white-space: nowrap;
}

.phone-link:hover,
.phone-link:focus-visible {
    text-decoration: underline;
}

/* A quiet marker sitting after a name in the results. Small and tinted rather
   than loud: it should read as a note on the row, not outshine the name it
   follows. data-tip on the span gives it the same hover help the filters use. */
.result-badge {
    background: #e6f4f3;
    border: 1px solid #9ed3d0;
    border-radius: 4px;
    color: #0B7C76;
    display: inline-block;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    line-height: 1.7;
    margin-left: 6px;
    /* letter-spacing puts its space AFTER the last letter as well as between, so
       an evenly padded pill has its lettering sitting a hair left of centre. The
       right padding gives that much back. */
    padding: 0 calc(5px - 0.04em) 0 5px;
    vertical-align: middle;
}

/* Used as a word inside a sentence rather than as a tag after a name.
   .result-badge's single left margin is right when it trails a band name in a
   results row; in prose it leaves the badge touching the word after it and a gap
   before it, which reads as belonging to the wrong side. Even margins here. */
.page-heading-note .result-badge {
    margin-right: 6px;
}

/*
 * The LM badge's help bubble opens leftwards.
 *
 * The bubble is positioned, not displayed: hidden, it still takes part in layout
 * and counts towards the page's scroll width. Hanging off the badge's LEFT corner,
 * from a badge sitting two thirds of the way across a results row, that put 415px
 * of bubble past the right edge of the page and gave Find Musicians a horizontal
 * scrollbar between 761 and 800px — on a page with nothing visibly wrong with it.
 *
 * Anchored to the badge's right edge instead, it opens back across the row, which
 * is the side with room. The same fix the Open to Bookings field got, and the same
 * one .tip-right exists for; done here in CSS rather than by adding a class in
 * find_musicians.php and find_bands.php, so every badge is covered wherever one
 * turns up.
 *
 * Narrower than the standard 420 as well. These tips are one sentence, and 300px
 * of bubble has somewhere to go from anywhere on the row.
 */
.result-badge[data-tip]::after {
    left: auto;
    right: 0;
    max-width: min(300px, calc(100vw - 48px));
}

.result-badge[data-tip]::before {
    left: auto;
    right: 22px;
}

.data-table th.result-distance-cell,
.data-table td.result-distance-cell {
    white-space: nowrap;
    text-align: right;
}

/* A date is a fixed width — keeping it on one line stops it from stretching the
   two wide list columns to its left. */
.data-table th.result-active-cell,
.data-table td.result-active-cell {
    text-align: center;
    white-space: nowrap;
}

/* Two digits: centred under its heading and never wider than it needs to be. */
.data-table th.result-age-cell,
.data-table td.result-age-cell {
    text-align: center;
    white-space: nowrap;
    width: 1%;
}

/* Shown site-wide only while DEV_SKIP_PASSWORD is on in config.php. */
.dev-warning {
    background: #ffe8b3;
    border-bottom: 2px solid #d99a00;
    color: #6b4a00;
    font-size: 0.85rem;
    padding: 8px 24px;
    text-align: center;
}

.dev-warning code {
    background: rgba(0, 0, 0, 0.07);
    border-radius: 3px;
    padding: 1px 4px;
}

/*
 * A gear photo at full size, in a window over the listing.
 *
 * The window is the size of the picture, not a fixed box the picture sits inside:
 * a set 75% left a wide photo with empty white to either side, which read as a
 * page with a picture on it rather than as the picture. The cap below is what
 * keeps the listing visible around the edges, so it still reads as a window.
 *
 * Sizing runs the other way round to the usual: the img carries the limits in
 * viewport units, and the dialog takes its width from whatever the img settles
 * at. That is also why there is no display or grid handling here — the dialog
 * needs no layout of its own to wrap a single child.
 */
.photo-dialog {
    width: max-content;
    max-width: 90vw;
    /* Undoes the global margin reset, which would otherwise stop showModal()
       centring it — the same fix .contact-dialog needs. */
    margin: auto;
    /* Enough that the picture is not flush against the border, and that the close
       button has somewhere of its own to sit. */
    padding: 12px;
    border: 1px solid #ccc;
    border-radius: 8px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
}

.photo-dialog::backdrop {
    /* Darker than the compose window's: there is nothing to read behind a photo,
       and the contrast is what makes the picture read as lit. */
    background: rgba(0, 0, 0, 0.6);
}

/* Fitted, never cropped, and never blown up past the file's own size — 24px of
   padding and 2px of border come off the viewport cap so the border is not what
   goes off screen. */
.photo-dialog img {
    display: block;
    max-width: calc(90vw - 26px);
    max-height: calc(90vh - 26px);
}

/* Over the top-right corner of the picture. White on a dark disc so it stays
   visible whatever the photo underneath it happens to be. */
.photo-dialog-close {
    position: absolute;
    top: 10px;
    right: 12px;
    width: 34px;
    height: 34px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
}

.photo-dialog-close:hover,
.photo-dialog-close:focus-visible {
    background: rgba(0, 0, 0, 0.8);
}

/* A photo opens in a window, so say so on the way in. */
.gear-photo-open {
    cursor: zoom-in;
}

/*
 * Saved searches: the strip above the filters, and the window behind Save Search.
 *
 * The strip sits above the filter form rather than inside it because these are two
 * forms going to two different places, and a form cannot be nested in another.
 */
/*
 * The open saved search, named beside the heading.
 *
 * A sibling of the h1 rather than part of it: the Musicians/Bands switch rewrites
 * the heading's text content, and anything nested inside would go with it. Baseline
 * aligned so it reads as a continuation of the title rather than a label stuck on.
 */
.search-heading-row {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 12px;
}

/*
 * The open saved search, reading as the second half of the title.
 *
 * It was a tinted pill, which put a rounded box with a border at the top of the page
 * next to the heading — every visual cue of a button, on something there is nothing
 * to press. So the box is gone and it is simply the title continued: same face, same
 * size, same weight, in the forest green this site already uses to mean "this is
 * about the feature, not the page". 2em rather than a rem figure because that is
 * what the h1 beside it resolves to, from the same parent font size.
 */
.search-saved-name {
    font-size: 2em;
    font-weight: bold;
    color: #228B22;
}

/*
 * The one word in the Edit Profile heading that changes with the account: Personal,
 * Band or Venue.
 *
 * #228B22 rather than a new green — the same forest green .search-saved-name uses,
 * which on this site already means "this word is about your thing, not the page's".
 * A second green a shade off would read as a mistake rather than a distinction.
 *
 * inherit for everything else: it is the heading continued, not a label pasted into
 * it, so it keeps the h1's size and weight and only the colour differs.
 */
.heading-kind {
    color: #228B22;
}

/* The noun after the name, in the heading's own colour: green is doing the work of
   marking which words are the name someone chose, so the word that is not part of
   that name should not be wearing it. */
.search-saved-word {
    color: #1F2933;
}

/*
 * A ticked filter, made obvious.
 *
 * The boxes were always ticked correctly when a saved search loaded — but two ticks
 * among forty instruments and twenty genres is something you have to hunt for, which
 * in practice is the same as not seeing them at all. This tints whichever are on.
 *
 * The tint is carried on a spread box-shadow rather than padding, and the font
 * weight is deliberately left alone: either would change how much room a label
 * needs, and re-flowing a grid of sixty items because one of them got ticked would
 * be a worse problem than the one being fixed. Colour costs no space.
 */
.check-panel .checkbox-option:has(input:checked) {
    background: #DCE9F7;
    color: #143C5C;
    border-radius: 4px;
    box-shadow: 0 0 0 3px #DCE9F7;
}

/*
 * The intro line, with the saved-search dropdown sitting at the end of it.
 *
 * space-between rather than a gap: the sentence keeps the left, the dropdown keeps
 * the right, and the line looks deliberate whether or not there is anything saved
 * to put on it.
 */
.search-intro-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px 20px;
}

.search-intro-row .form-note {
    margin: 0;
}

.saved-search-load {
    display: flex;
    align-items: center;
    gap: 8px;
}

.saved-search-load label {
    font-weight: 600;
}

/* Wider than the 420px dialog default: the list of saved names needs room to sit
   two or three across rather than running down the screen. */
.saved-search-dialog {
    max-width: 560px;
    width: 92%;
    /* An inch down from the top, matching the compose window, and the auto sides
       undo the global margin reset that would otherwise stop showModal() centring
       it at all. */
    margin: 1in auto auto;
    max-height: calc(100vh - 1in - 12px);
}

.saved-search-dialog .subheading {
    margin-top: 0;
}

/* A rule between naming and tidying: two errands in one window, and without it the
   tick boxes read as part of the form above them. */
.saved-search-remove {
    margin-top: 22px;
    padding-top: 18px;
    border-top: 1px solid #e0e0e0;
}

.saved-search-list {
    max-height: 190px;
    overflow-y: auto;
}

/* The saved-search strip: dropdown, Manage and the help button on one line.
   Wrapping rather than shrinking, because the dropdown holds names somebody chose
   and the last thing it should do is clip them. */
.saved-search-tools {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

/*
 * The strip carries help, so it is marked the way every other helped field is:
 * the green dotted lead. The site's rule is `[data-tip] > label`, and this lead is
 * a grandchild — inside the dropdown's own form, or a bare span when there is no
 * dropdown to label — so it is spelled out here rather than left to miss.
 */
.saved-search-tools[data-tip] .saved-search-load label,
.saved-search-tools[data-tip] .saved-search-lead {
    color: #228B22;
    font-weight: 600;
    text-decoration: underline dotted #228B22 1px;
    text-underline-offset: 3px;
}

/* Which list a name belongs to, inside the remove panel. A heading rather than a
   second panel: two scrolling boxes to tidy one list of ten would be absurd. */
.saved-search-group {
    margin: 10px 0 4px;
    font-size: 0.85rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #5A646E;
}

.saved-search-group:first-child {
    margin-top: 0;
}

/* Phones: the strip stacks, and the dialog takes the screen rather than a slice of
   it — an inch of offset is a lot of a short screen. */
@media (max-width: 700px) {
    /* The dropdown drops under the sentence rather than squeezing beside it. */
    .search-intro-row {
        align-items: stretch;
        flex-direction: column;
    }

    .saved-search-dialog {
        margin: 12px auto auto;
        max-height: calc(100vh - 24px);
        width: 96%;
    }

    /* The select takes the width it is given rather than the width of the longest
       name, so the strip cannot push the page sideways on a narrow screen. */
    .saved-search-load {
        flex: 1 1 100%;
    }

    .saved-search-load select {
        flex: 1 1 auto;
        min-width: 0;
    }

    /* Stacked, the strip starts at the left margin — so the bubble goes back to
       hanging off its left corner, which is now the side with room. */
    .tip-right[data-tip]::after {
        left: 0;
        right: auto;
    }

    .tip-right[data-tip]::before {
        left: 22px;
        right: auto;
    }
}

/* The Contact compose window. */
.contact-dialog {
    border: 1px solid #ccc;
    border-radius: 8px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
    /* Sits an inch below the top of the window, horizontally centred. The auto
       side margins also undo a global margin reset that would otherwise stop
       showModal() centring it at all. */
    margin: 1in auto auto;
    /* Everything below the 1in offset, less a small gap at the bottom, so the
       whole form fits without a scrollbar of its own. */
    max-height: calc(100vh - 1in - 12px);
    /* Wider than the rest of the site's forms on purpose: a long sentence that
       fits on one line costs no vertical space, and vertical space is what this
       window is short of. */
    max-width: 780px;
    overflow: auto;
    padding: 12px 28px 16px;
    width: 92%;
}

/* Trimmed spacing so the form clears the bottom of a short window. */
.contact-dialog .form-note {
    margin: 2px 0 6px;
}

.contact-dialog .field {
    margin-bottom: 8px;
}

.contact-dialog::backdrop {
    background: rgba(0, 0, 0, 0.45);
}

.contact-dialog .subheading {
    margin-top: 0;
}

.contact-dialog textarea {
    font-family: inherit;
    font-size: 0.95rem;
    /* Tightened just enough that the message — now fourteen lines — still fits
       between the 1in offset and the bottom of a short window. */
    line-height: 1.28;
    /* The message shouldn't start hard against the border. */
    padding: 10px 12px;
    resize: vertical;
    width: 100%;
}

.contact-dialog .form-actions {
    margin-top: 8px;
}

/*
 * Hover help. Any element carrying data-tip shows its text in a bubble above
 * itself, on hover and on keyboard focus — keyboard focus matters, or the help is
 * invisible to anyone tabbing through the form. No JavaScript: the text comes
 * straight from the attribute, and PHP fills that in from FIELD_HELP.
 */
[data-tip] {
    cursor: help;
    position: relative;
}

[data-tip]::after {
    background: #23232b;
    border-radius: 6px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
    bottom: calc(100% + 10px);
    color: #fff;
    content: attr(data-tip);
    font-size: 0.85rem;
    font-weight: 400;
    left: 0;
    line-height: 1.45;
    /*
     * 420 is the reading width the bubble wants, but it is positioned, not
     * displayed, so even hidden it still takes part in layout and stretches the
     * page's scroll width. On a phone that alone pushed every page with a tip on
     * it 90px wider than the screen. Never wider than the viewport, then.
     */
    max-width: min(420px, calc(100vw - 48px));
    padding: 10px 13px;
    text-align: left;
    white-space: normal;
    width: max-content;
}

/* The little pointer underneath. */
[data-tip]::before {
    border: 7px solid transparent;
    border-top-color: #23232b;
    bottom: calc(100% + 3px);
    content: "";
    left: 22px;
}

[data-tip]::after,
[data-tip]::before {
    opacity: 0;
    pointer-events: none;
    position: absolute;
    transition: opacity 0.12s ease;
    visibility: hidden;
    z-index: 60;
}

/*
 * Shown on hover, and on keyboard focus — but not on a tap.
 *
 * This was :hover + :focus-within, which is right on a desktop and a trap on a
 * phone. Ticking a checkbox inside a tipped label focuses it, :focus-within
 * matches, and the bubble opens and stays open — there is no pointer to move away
 * and nothing else on the row to focus. The only way to shift focus was to tap the
 * box again, which unticks the filter and re-runs the search: the help could not be
 * dismissed without undoing the thing it was helping with.
 *
 * :focus-visible is the same idea minus that failure. It matches when focus arrived
 * from the keyboard and not when it arrived from a tap or a click, so tabbing still
 * brings the help up and ticking a box no longer does. :has() carries it to the
 * wrapper, since the focused control is the input inside the tipped label.
 *
 * Hover is gated on a device that has one, so a tap cannot leave a phone in a
 * latched hover state — the other half of the same bug.
 */
@media (hover: hover) {
    [data-tip]:hover::after,
    [data-tip]:hover::before {
        opacity: 1;
        visibility: visible;
    }
}

[data-tip]:focus-visible::after,
[data-tip]:focus-visible::before,
[data-tip]:has(:focus-visible)::after,
[data-tip]:has(:focus-visible)::before {
    opacity: 1;
    visibility: visible;
}

/*
 * A tip whose line breaks mean something.
 *
 * The default bubble wraps on width alone, which is right for a paragraph of help.
 * Where the text is a short list of separate things — one line each — the breaks are
 * the structure, and losing them turns the list back into a paragraph.
 */
.tip-lines[data-tip]::after {
    white-space: pre-line;
}

/*
 * A tip on something near the top of the page, dropping instead of rising.
 *
 * The bubble sits above its element by default, which works everywhere it is used
 * on a form — there is always page above a field. The saved-search strip is on the
 * second line of the page, so upwards is the site header and then nothing: several
 * lines of help would be cut off by the top of the window. This turns it over,
 * pointer and all.
 */
.tip-below[data-tip]::after {
    bottom: auto;
    top: calc(100% + 10px);
}

.tip-below[data-tip]::before {
    border-top-color: transparent;
    border-bottom-color: #23232b;
    bottom: auto;
    top: calc(100% + 3px);
}

/*
 * A tip on something at the right-hand edge, hanging back into the page.
 *
 * The bubble is pinned to its element's left corner by default, which is right up
 * until the element is itself near the right margin — then 420px of help goes off
 * the side of the screen. This flips the anchor, and the little pointer with it, so
 * the bubble grows inwards instead.
 */
.tip-right[data-tip]::after {
    left: auto;
    right: 0;
}

.tip-right[data-tip]::before {
    left: auto;
    right: 22px;
}

/*
 * A label in forest green means "there is help here". Consistent across the
 * site: any field given help text is marked the same way, so the colour becomes
 * a signal rather than decoration.
 */
[data-tip] > label {
    color: #228B22;
    text-decoration: underline dotted #228B22 1px;
    text-underline-offset: 3px;
}

/* Two tick-boxes stacked inside one filter slot. */
/* Two short checks that read as one line of small print, so they sit on one row
   rather than stacking and stretching the row taller than the fields beside it. */
.filter-checks {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 18px;
}

.filter-checks .checkbox-option {
    align-items: center;
    display: flex;
    font-size: 0.9rem;
    font-weight: 400;
    gap: 6px;
    white-space: nowrap;
}

/*
 * The instrument and genre pickers: a scrollable panel of checkboxes laid out in
 * columns. Checkboxes rather than a multi-select, so picking three things doesn't
 * depend on knowing about ctrl-click, and what's ticked is visible at a glance.
 */
.check-grid {
    background: #fff;
    border: 1px solid #ccc;
    border-radius: 6px;
    /* A grid rather than CSS columns: columns inside a fixed height flow
       sideways and produce a horizontal scrollbar, where a grid wraps downwards
       and scrolls the way you expect. */
    display: grid;
    /* Tight: the panel is a list to scan, not a form to fill in. */
    gap: 1px 12px;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    /* Tall enough that the full instrument list fits on a wide screen without
       scrolling; narrower windows wrap to more rows and scroll instead. */
    max-height: 152px;
    overflow-y: auto;
    padding: 3px 8px;
}

.check-grid .checkbox-option {
    align-items: center;
    display: flex;
    font-size: 0.86rem;
    font-weight: 400;
    gap: 5px;
    /* Rows sit close together: the line box is only just taller than the
       checkbox itself, so the list reads as a block rather than a form. */
    line-height: 1.1;
    /* The general checkbox rule adds 6px above and 3px below, which is what was
       really spacing these rows out. Inside the panel the grid handles it. */
    margin: 0;
    min-width: 0;
}

.check-grid input[type="checkbox"] {
    margin: 0;
}


/* Instrument and Genre name a whole panel of checkboxes, not a single row, so
   centring the label against it left the word reading level with the middle of
   the list. Pin it to the top instead; the nudge drops it onto the first row. */
.labels-left .field:has(> .check-panel) > label {
    align-self: start;
    padding-top: 3px;
}

/* A filter wide enough to need two of the row's four slots. */
.field-row-4 > .field-span-2 {
    grid-column: span 2;
}

/* And one that takes the whole row. */
.field-row-4 > .field-span-full {
    grid-column: 1 / -1;
}

/* Paging under the results. */
.pager {
    align-items: center;
    display: flex;
    gap: 16px;
    justify-content: center;
    margin: 22px 0 4px;
}

.pager-position {
    color: #555;
    font-size: 0.9rem;
}

.btn.is-disabled {
    cursor: default;
    opacity: 0.45;
}

/* ---------------------------------------------------------------- Terms of Service */

/* Long-form prose, so it is capped for readability rather than run to the full
   width of the page the way the search results do. */
.terms-section {
    max-width: 820px;
}

.terms-section p {
    margin-bottom: 14px;
}

/*
 * Justified, the way a Word document is: both edges flush rather than ragged on
 * the right. It is an agreement, and an agreement set as a block of even type
 * reads as a document rather than as a web page.
 *
 * Not the version line, which is a caption and would have its two words stretched
 * across the whole column, and not the acknowledgement's lead, which is a single
 * short sentence that justifies badly.
 */
.terms-section p:not(.terms-meta):not(.terms-ack-lead),
.terms-list li {
    text-align: justify;
    /* Word's own setting: space goes between the words, never inside them. */
    text-justify: inter-word;
}

/*
 * Hyphenation, on a phone only.
 *
 * Word leaves hyphenation off, and at 820px so does this: the lines are long
 * enough that the spaces it opens up are not noticeable. At 390px they are - a
 * column that narrow with a long word in it stretches one line into a row of
 * islands, the "rivers" that justified narrow type is known for. Letting the
 * browser break a word at the margin closes them up.
 *
 * lang="en" is already on the <html> tag in header.php, which is what tells the
 * browser which language's rules to break by. Without it this does nothing.
 */
@media (max-width: 760px) {
    .terms-section p:not(.terms-meta):not(.terms-ack-lead),
    .terms-list li {
        hyphens: auto;
        -webkit-hyphens: auto;
    }
}

.terms-section h2.subheading {
    margin-top: 26px;
}

.terms-meta {
    font-style: italic;
}

.terms-list {
    margin: 0 0 14px 22px;
}

.terms-list li {
    margin-bottom: 6px;
}

/* The acknowledgement is set apart from the clauses above it: it is the part that
   asks something of the reader rather than telling them something. */
.terms-ack {
    background: #f2f8f8;
    border: 1px solid #9ed3d0;
    border-radius: 6px;
    margin-top: 28px;
    padding: 18px 20px;
}

.terms-ack-lead {
    font-weight: 700;
    margin-bottom: 8px;
}

/* The acknowledgement checkbox: the text runs to a second line at narrow widths,
   so the box aligns to the first line rather than to the middle of the block. */
/* Was a checkbox and its wording on one line; it is now the sentence that says
   what pressing the button means, so it is a paragraph rather than a control. */
.terms-ack-check {
    font-weight: 400;
    line-height: 1.5;
    margin: 0 0 16px;
    max-width: 70ch;
}

/* On the registration form the agreement is already given, so what is left is a
   quiet note rather than a control. */
.terms-agreed-note {
    margin: 18px 0 4px;
}

/* --- One listing, read-only (gear_view.php) -------------------------------- */

/*
 * The View column sits ahead of everything on the buying table. It holds the only
 * control in the row, so it takes the width of the button and no more — the data
 * columns need every pixel they can get.
 */
.gear-result-table th.gear-view-cell,
.gear-result-table td.gear-view-cell {
    width: 1%;
    white-space: nowrap;
}

/* .btn-small carries a 100px floor so Modify and Deactivate line up with each
   other on My Gear. One four-letter word on its own does not need it. */
.gear-view-cell .btn-small {
    min-width: 0;
}

/*
 * Title and price on one line, the price immediately after the title.
 *
 * It used to be pinned to the right margin, which on a wide screen put half a
 * screen of nothing between the thing and what it costs and read as two unrelated
 * lines. Baseline-aligned so the two sit on the same footing despite the size gap.
 */
.gear-view-title {
    display: flex;
    align-items: baseline;
    gap: 18px;
    flex-wrap: wrap;
}

.gear-view-title h1 {
    margin: 0;
}

.gear-view-price {
    font-size: 1.6rem;
    font-weight: 700;
    color: #0B7C76;
    white-space: nowrap;
}

/* Category and item, directly under the title they qualify. */
.gear-view-cat {
    margin: 4px 0 20px;
}

/* Photos left, facts right, until the screen is too narrow to hold both. */
.gear-view-body {
    display: flex;
    gap: 32px;
    flex-wrap: wrap;
    align-items: flex-start;
}

.gear-view-photos {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.gear-view-details {
    flex: 1 1 320px;
    min-width: 280px;
}

/* Square, like equipment is everywhere else on the site.
 *
 * 320 wide, which is also the width of the narrowest phone still in use — so a
 * stated 320 plus the page's own padding stood 20px wider than the screen and gave
 * the listing a sideways scroll. max-width caps it at whatever room there is, and
 * the aspect-ratio keeps it square as it shrinks rather than letting a fixed
 * height crop a narrower picture. */
.gear-view-main {
    width: 320px;
    max-width: 100%;
    aspect-ratio: 1 / 1;
    height: auto;
    object-fit: cover;
    border-radius: 6px;
    border: 1px solid #ddd;
    display: block;
}

.gear-view-main-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    background: #f0f0f5;
    color: #9a9ab0;
    font-size: 3rem;
}

/*
 * The facts, one to a line.
 *
 * Two columns rather than one, only so that Listed and Ends can share a line —
 * they are one thought, the window the listing is open for. Everything else
 * spans both columns and has its line to itself. The first column takes just the
 * width the date pair needs, which keeps Ends next to Listed rather than out at
 * the half-way mark.
 */
.gear-view-facts {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: 10px 28px;
    margin: 0 0 22px;
}

.gear-fact-full {
    grid-column: 1 / -1;
}

/* Label beside its value, not above it. Baseline-aligned so the two sit on one
   line, and the label holds its own line whatever the value does. */
.gear-fact {
    display: flex;
    align-items: baseline;
    gap: 10px;
}

.gear-view-facts dt {
    font-weight: 700;
    color: #333;
    white-space: nowrap;
}

.gear-view-facts dd {
    margin: 0;
}

/* The facts are the top of this column, so nothing above them to clear. */
.gear-view-details > .subheading:first-child {
    margin-top: 0;
}

/* Too narrow to keep the two dates side by side, so they stack like the rest. */
@media (max-width: 640px) {
    .gear-view-facts {
        grid-template-columns: minmax(0, 1fr);
    }

    .gear-fact-full {
        grid-column: auto;
    }
}

/* Prose wants a line length, not the width of the page. */
.gear-view-description {
    max-width: 70ch;
    line-height: 1.55;
}

/*
 * Who is selling it, across the top of a listing.
 *
 * The buying table has no Seller column any more — a name on its own said very
 * little and cost a column that the item details needed. Here there is room to say
 * band or musician, where they are, how far that is and when they were last around,
 * which is what actually decides whether the listing is worth a message.
 */
.gear-seller {
    display: flex;
    align-items: center;
    gap: 16px;
    padding-bottom: 18px;
    margin-bottom: 18px;
    border-bottom: 1px solid #e3e3ea;
}

.gear-seller-avatar {
    width: 64px;
    height: 64px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
    flex: none;
}

.gear-seller-avatar-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    background: #f0f0f5;
    color: #9a9ab0;
    font-size: 1.6rem;
}

.gear-seller-who p {
    margin: 2px 0 0;
}

.gear-seller-name {
    font-size: 1.15rem;
    font-weight: 700;
}

/* Pinned right, opposite the name. */
.gear-seller-contact {
    margin-left: auto;
}

/*
 * Sortable column headings, on the Buying table and on Find Musicians.
 *
 * The Sort by dropdown is gone, so the heading is the control — and nothing about
 * a bold word says "click me" on its own. They are underlined to say so, but keep
 * the heading's own colour and weight: link blue across a whole header row reads as
 * navigation out of the page, which is not what these do. The underline is set
 * below the text rather than through the descenders, and thickens on hover.
 */
.sortable-headings th .sort-link {
    color: inherit;
    font-weight: inherit;
    white-space: nowrap;
    cursor: pointer;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
}

.sortable-headings th .sort-link:hover,
.sortable-headings th .sort-link:focus-visible {
    color: #0B7C76;
    text-decoration-thickness: 2px;
}

/* The column currently in charge of the order. */
.sortable-headings th .sort-link.is-sorted {
    color: #0B7C76;
    text-decoration-thickness: 2px;
}

.sort-arrow {
    display: inline-block;
    text-decoration: none;
    font-size: 0.7em;
    /* Sits with the word rather than on the baseline under it. */
    position: relative;
    top: -1px;
    margin-left: 2px;
}

/*
 * Search line on Find Musicians: buttons centred, tick boxes right.
 *
 * The two "Only show" boxes used to have a filter row to themselves, which cost a
 * whole line of the form to hold two words each. Three columns with the middle one
 * auto-sized keeps Search centred on the grid above it no matter how wide the tick
 * boxes get — a plain flex row with the boxes pushed right would drag the buttons
 * off centre.
 */
.search-actions {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 12px;
    margin-top: 14px;
}

.search-actions-buttons {
    grid-column: 2;
    display: flex;
    gap: 12px;
}

.search-actions-checks {
    grid-column: 3;
    justify-self: end;
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
}

/* "Only show" was reading 3px high against the two options beside it. Both boxes
   are the same 24px height, but the general checkbox rule puts 6px of margin above
   each option, and centring works on the outer margin box — so the option's text
   sat half that margin lower. The row spaces itself with its own gap here, so the
   stray margin can go. */
.search-actions-checks .checkbox-option {
    margin: 0;
}

/* Narrow screens: the three columns collapse and everything stacks centred. */
@media (max-width: 820px) {
    .search-actions {
        grid-template-columns: 1fr;
        justify-items: center;
    }

    .search-actions-buttons,
    .search-actions-checks {
        grid-column: 1;
        justify-self: center;
    }
}

/*
 * Five filters on one line (the Musicians view).
 *
 * .field-row-even already sizes each group to its content and centres the lot;
 * five groups rather than three just need the gap pulled in so they still fit
 * before wrapping. The About box gives up the most, because a phrase to search
 * for needs far less room than a phrase to read.
 */
.filter-line {
    /* The gap is the minimum; space-between shares out whatever is left over, so
       the five groups sit at even intervals rather than bunched at one end. */
    gap: 12px 22px;
    /* Spanning the panels above: Skill Level's box starts on their left edge (the
       80px label column below puts it there) and About's box ends on their right,
       so the line reads as the same width as the two grids it sits under. */
    justify-content: space-between;
}

/* Skill Level's box lines up with the left edge of the Instrument and Genre
   panels: 80px of label plus the 8px gap puts it exactly where they start. The
   other four groups keep their label snug against their own control. */
.labels-left .filter-line > .field:first-child {
    grid-template-columns: 80px auto;
}

/* Sized from what the row had spare: the five groups came to 1231 of 1460, so the
   slack goes where it shows — the free-text box first, then the boxes whose
   placeholder was being clipped. */
/* Back to the width the box actually needs for a word or two. Stretching it to
   reach the right-hand edge worked, but it bought that alignment by making one
   box twice the size of the rest — space-between reaches the edge anyway, and
   spends the leftover on the gaps instead, which is where it shows. */
.labels-left .filter-line #about {
    width: 250px;
}

.labels-left .filter-line .age-range input {
    width: 74px;
}

.labels-left .filter-line .radius-range input {
    width: 78px;
}

/*
 * Age: two plain boxes, no spinners.
 *
 * The little up/down arrows are worth having when a value is nudged one step at a
 * time; an age is typed, not stepped, and the arrows were eating width that the
 * digits needed. The input stays type="number", so phones still bring up a keypad
 * and the min/max still hold — only the stepper goes.
 */
.age-range input[type="number"] {
    appearance: textfield;
    -moz-appearance: textfield;
}

.age-range input[type="number"]::-webkit-outer-spin-button,
.age-range input[type="number"]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* ---------------------------------------------------------------------------
 * Blog: the list, one post, and the comment thread.
 * --------------------------------------------------------------------------- */

/*
 * Narrower than the gear pages on purpose. This is prose, and a paragraph running
 * the full width of a desktop monitor is measured in the effort it takes to find
 * the start of the next line — the usual advice is 60-80 characters, which is about
 * where 820px lands at this font size.
 */
.blog-read,
.blog-edit {
    max-width: 820px;
}

/* Title, Write a Post, and the filters on one line, with the filters pushed to the
   far end so the button sits beside the heading it belongs to rather than drifting. */
.blog-heading {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 16px;
    margin-bottom: 8px;
}

.blog-heading h1 {
    margin: 0;
}

/*
 * The filters and their disclaimer, stacked as one block at the end of the heading
 * row.
 *
 * align-self: start rather than the row's centre, and pulled up a few pixels on top
 * of that: centred against a heading and a button, the block hung level with the
 * middle of them and its small print pushed the whole row taller. Starting it high
 * lines the controls up with the top of the heading and leaves the disclaimer sitting
 * in space the row already had.
 */
.blog-filter-block {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 6px;
    /* Everything before this hugs the heading; the filters take the slack. */
    margin-left: auto;
    align-self: start;
    margin-top: -6px;
}

.blog-filter {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
}

/* Small print, and it should read as small print: quiet, one line, and not competing
   with the post titles underneath it. */
.blog-disclaimer {
    margin: 0;
    font-size: 0.85rem;
    color: #666;
    font-style: italic;
}

.blog-filter-label {
    font-weight: 600;
}

/* The checkbox carries its own label text, so it does not need the gap the
   labels-left rows use between a label and its control. */
.blog-filter-mine {
    margin: 0;
}

.blog-list {
    display: flex;
    flex-direction: column;
    gap: 16px;
    margin-top: 20px;
}

/* One card per post. Same pale panel construction as the gear cards, so a list of
   posts and a list of listings read as the same kind of thing. */
.blog-card {
    padding: 16px 20px;
    border: 1px solid #e0e4e8;
    border-left: 4px solid #0f8a6d;
    border-radius: 6px;
    background: #fcfdfd;
}

/*
 * A draft is marked down the left edge rather than by tinting the whole card.
 *
 * The list is mostly other people's published posts with your own drafts among
 * them; a full-card tint made the drafts louder than the blog. The stripe is enough
 * to find them by and the flag in the meta line says what it means.
 */
.blog-card.is-draft {
    border-left-color: #d68910;
    background: #fffdf8;
}

.blog-card-title {
    margin: 0 0 6px;
    font-size: 1.25rem;
}

.blog-card-title a {
    color: #1a1a1a;
    text-decoration: none;
}

.blog-card-title a:hover {
    text-decoration: underline;
}

/* Category, author, date, comment count — one line that wraps as a whole rather
   than breaking a name away from what it belongs to. */
.blog-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin: 0 0 10px;
    font-size: 0.9rem;
}

.blog-author {
    font-weight: 600;
    color: #333;
}

.blog-draft-flag {
    padding: 2px 8px;
    border-radius: 10px;
    background: #fdf0d5;
    border: 1px solid #f0d9a8;
    color: #8a5a00;
    font-size: 0.85rem;
    font-weight: 600;
}

.blog-excerpt {
    margin: 0 0 12px;
    color: #444;
    line-height: 1.6;
}

.blog-card-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin: 0;
}

.blog-post-head h1 {
    margin-bottom: 8px;
}

/*
 * The post itself. line-height and a cap on paragraph width are doing the same job
 * here as on the home page blurb: this is the only place on the site somebody reads
 * more than a few sentences in a row.
 */
.blog-body {
    margin: 20px 0 28px;
    font-size: 1.05rem;
    line-height: 1.7;
    color: #2b2b2b;
    /* Long unbroken strings — a pasted URL, mostly — wrap instead of pushing the
       card wider than the page. */
    overflow-wrap: break-word;
}

.blog-post-actions {
    margin-top: 24px;
}

.blog-comments {
    display: flex;
    flex-direction: column;
    gap: 14px;
    margin: 16px 0 28px;
}

/* Comments are indented under the post by a left rule rather than by margin: at
   phone width a margin would eat the text, and the rule reads the same at both. */
.blog-comment {
    padding: 12px 16px;
    border-left: 3px solid #dfe4e8;
    background: #fafbfc;
    border-radius: 0 6px 6px 0;
}

.blog-comment-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin: 0 0 6px;
    font-size: 0.85rem;
}

.blog-comment-body {
    margin: 0;
    line-height: 1.6;
    color: #333;
    overflow-wrap: break-word;
}

/* The comment box is the width of the post above it — a full-width box under a
   measured column reads as belonging to the page rather than to the thread. */
.blog-comment-form textarea {
    width: 100%;
}

.blog-edit textarea {
    width: 100%;
    /* Tall enough to see a few paragraphs at once: a post written through a
       four-line window is written blind. */
    min-height: 320px;
}

/*
 * Phones. The heading stacks, the filters give up their right-hand alignment, and
 * the buttons inside a card go full width so they can be hit with a thumb.
 */
@media (max-width: 700px) {
    .blog-heading {
        gap: 12px;
    }

    /*
     * The filter block stops being a row and becomes a form.
     *
     * Five controls sharing a wrapping flex row at phone width came out mangled:
     * the selects were squeezed to "All au" and "Newe" and the Category box was
     * clipped off the right edge altogether. A two-column grid gives each control
     * a line of its own with its label beside it — which is where labels go on
     * this site — and the controls all take the same width, so the block reads as
     * one form rather than four stray pairs.
     */
    /* No longer pushed to the far end — on one column there is no far end, and the
       auto margin left the filters hanging off the right edge. Nor pulled upwards:
       stacked, there is no taller neighbour to line up with. */
    .blog-filter-block {
        margin-left: 0;
        margin-top: 0;
        align-items: stretch;
        width: 100%;
    }

    /* Left with the controls above it rather than hugging the right edge. */
    .blog-disclaimer {
        text-align: left;
    }

    .blog-filter {
        display: grid;
        width: 100%;
        grid-template-columns: max-content minmax(0, 1fr);
        gap: 8px 10px;
        align-items: center;
    }

    .blog-filter select,
    .blog-filter-words {
        width: 100%;
        min-width: 0;
    }

    /* The checkbox carries its own text, so it needs no label in column one — it
       lines up under the controls instead. */
    .blog-filter-mine {
        grid-column: 2;
    }

    /* Full width, like Write a Post above it. */
    .blog-filter .btn {
        grid-column: 1 / -1;
        width: 100%;
    }

    .blog-heading > .btn {
        width: 100%;
        text-align: center;
    }

    .blog-card {
        padding: 14px 16px;
    }

    .blog-card-actions .btn {
        flex: 1;
    }

    .blog-body {
        font-size: 1rem;
    }

    /* Back / Edit / Delete stack rather than squeezing onto one line. */
    .blog-post-actions .btn {
        width: 100%;
        text-align: center;
    }
}

/*
 * Two corrections to labels-left for the post editor.
 *
 * The shared rule sizes an input to its `size` attribute and centres a label
 * against its control. Both are right for a profile field and wrong here: a title
 * is as long as the post needs and should have the width of the box below it, and a
 * label centred against a 320px textarea floats halfway down the page beside
 * nothing. The label goes to the top line of the box it names, where it reads as
 * belonging to it.
 */
.blog-edit .field > input[type="text"] {
    width: 100%;
}

.blog-section .field:has(> textarea) {
    align-items: start;
}

.blog-section .field:has(> textarea) > label {
    /* Matches the textarea's own top padding, so label and first line of text sit
       on the same baseline rather than a few pixels apart. */
    padding-top: 10px;
}

/*
 * Phones: the label column narrows, it does not move.
 *
 * labels-left reserves 185px for the label at every width. On a 390px screen that
 * left the comment box and the post box about 190px wide — a textarea narrower than
 * the sentence being typed into it. Labels stay beside their boxes, which is the
 * rule everywhere on this site; they just stop taking half the screen to do it.
 */
@media (max-width: 700px) {
    .blog-section .labels-left .field {
        grid-template-columns: 100px minmax(0, 1fr);
    }

    /* At 100px "Your comment" needs two lines, so it has to be allowed to wrap. */
    .blog-section .labels-left .field > label {
        white-space: normal;
    }

    .blog-section .labels-left .field > textarea {
        min-height: 200px;
    }
}

/*
 * Find Bands: Within second, Words third.
 *
 * The shared .gear-buy-filters row makes its THIRD column the wide one because that
 * is where Within sits on the gear form. Here the two have traded places, so the
 * wide column trades with them — otherwise Within, which is three controls and not
 * one, spills out of an even quarter and lands against the next field's label. Only
 * the widths move; the gaps and the label gutter still come from the shared rule.
 */
/* Four filters, four columns, one row. Status is a short dropdown; Within is three
   controls in a trench coat and needs the widest share; Tribute to is a long list
   whose names have to be readable closed; Words is a phrase box. Sized to what each
   actually holds rather than into equal quarters, which is what made the row wrap. */
/*
 * Five now, not four: Size joined Status, Within, Tribute and Words. The class name
 * is the shared four-across layout it borrows from, not a count of what is in it.
 *
 * The fractions are sized to the longest option each dropdown holds, not shared out
 * evenly. Splitting a four-column row five ways took Status down to 112px against
 * the 118 "Just for Fun" needs, and Size to 75 against the 102 "Full Band" needs —
 * both dropdowns were cutting their own longest answer in half. Within keeps the
 * largest share because it is three controls in one field.
 */
.band-search-filters .field-row-4 {
    /*
     * Each filter takes the width it needs, with half an inch between one field's
     * box and the next field's label.
     *
     * Equal columns gave every filter the same share whether or not it wanted it,
     * which left Status and Size floating in space they had no use for while Within
     * had to break onto a second line. Sized to content, the gap between them is the
     * only thing being set — and it is set once, so the spacing reads as deliberate
     * rather than as whatever the fractions happened to leave over.
     */
    /* The three on the left take what their contents need — a dropdown should be as
       wide as its longest option and no wider. The two on the right share what is
       left: Tribute holds names as long as "Gloria Estefan / Miami Sound Machine"
       and Words is a free-text box, so both would swallow the row if allowed to. */
    grid-template-columns: auto auto auto minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 0.5in;
}

/*
 * Below about 1150 there is no honest way to fit five of these across: at 1110 the
 * row gave Status 103px against the 118 it needs and Size 79 against 102, so both
 * went back to clipping their longest option. Three across, wrapping to a second
 * line, gives every dropdown more than it needs and costs one line of height.
 * (Under 760 the phone rules take over and stack them one per line.)
 */
@media (max-width: 1365px) {
    .band-search-filters .field-row-4 {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    /* Within is a select, the words "miles of zip" and a zip box, all held on one
       nowrap line. Three across gives each column about a third of the row, which
       is less than that line wants — so it is allowed to break here rather than
       carry the page sideways. */
    .band-search-filters .radius-range {
        flex-wrap: wrap;
        white-space: normal;
        min-width: 0;
        max-width: 100%;
    }
}

/* The tribute list closed shows one name, so it takes the cell it is given rather
   than the 380px the Gigs picker wants. */
.band-search-filters .field-row-4 .tribute-pick {
    width: 100%;
    max-width: none;
    min-width: 0;
}


/*
 * The genre grid starts where Status starts.
 *
 * Genres sits outside the four-column row, so it was inheriting the page-wide 185px
 * label gutter while every control on the row above used the row's 80px one — the
 * checkboxes began about 110px to the right of the Status dropdown and the two
 * blocks read as belonging to different forms. Same gutter, same left edge.
 */
.band-search-filters > .field {
    grid-template-columns: 80px minmax(0, 1fr);
    gap: 4px 8px;
}

/*
 * Find Bands: room for the widened Words box.
 *
 * Words now spans two of the four slots, which only helps if the slots themselves
 * have somewhere to go — inside the shared 1160px the extra width would have come
 * straight out of Status and Within, and Within is the control that overflows when
 * it is squeezed. 1300 gives Words about three times the box it had and leaves its
 * two neighbours slightly wider than before rather than tighter. Still well inside
 * the results table below.
 */
/*
 * Full width of its section rather than a 1300px cap.
 *
 * The cap dated from the four-filter row, when 1300 was more than enough and the
 * spare 160px at the right read as margin. With five filters and a genre panel
 * underneath it reads as waste: the same 160 gives the genre list an eighth
 * column and every dropdown on the line above a little more room.
 */
.band-search-filters {
    max-width: 100%;
}

/* Fill the spanned cell. The shared rule sizes an input to its `size` attribute and
   caps it at the cell; a 54-character box would otherwise be trimmed back to
   whatever the cell happened to be rather than setting the width itself. */
.band-search-filters .field-row-4 > .field-span-2 > input[type="text"] {
    width: 100%;
}

/*
 * The blog's search box: wide enough for a phrase rather than a word.
 *
 * A fixed basis, not flex: 1. Growing to fill the slack sounds harmless and is not —
 * the filter block sizes itself to its content, that measurement is taken before the
 * box grows, and the width it then helped itself to came out of the Search button,
 * which wrapped onto a line of its own.
 */
.blog-filter-words {
    flex: 0 0 240px;
    min-width: 0;
}

/* On one column there is no slack to fight over, so it takes the width instead. */
@media (max-width: 700px) {
    .blog-filter-words {
        flex: 1 1 auto;
    }
}

/*
 * Public profile: the picture, and nothing but the picture.
 *
 * media.php gives every item the same 900x320 box on purpose — that page is a
 * gallery you are deciding what to keep in, and a uniform frame with a tinted
 * backdrop is what lets you compare one against another. A profile somebody has
 * arrived at from a search is not that. There the frame was the problem: a 4:3 photo
 * rendered 400px of picture inside an 878px box, so most of what a visitor saw was
 * grey, and a portrait shot showed 225px of picture in the same box.
 *
 * So here the figure hugs its image instead: no frame, no padding, no backdrop, and
 * a height cap rather than a fixed height, so the box is the size of the photo. Once
 * each one is only as wide as it needs to be, a row fits two or three of them, which
 * the flex wrap does on its own — no breakpoint decides how many, the pictures do.
 */
.profile-section .media-grid {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 12px;
}

.profile-section .media-card {
    background: none;
    border: 0;
    border-radius: 0;
    padding: 0;
    /* Shrink to the image. Without this the figure is a flex item sized to the row
       and the frame comes back in all but name. */
    max-width: 100%;
}

.profile-section .media-card img,
.profile-section .media-card video {
    /* Natural proportions, capped on the long edge. 300px keeps a landscape photo
       at about 400 wide and a portrait at about 225, so two or three sit on a row
       either way. */
    width: auto;
    height: auto;
    max-height: 300px;
    max-width: 100%;
    background: none;
    object-fit: initial;
}

/*
 * A video keeps a frame. The picture rule above does not suit it: a video's
 * proportions are not known until its metadata arrives, so width: auto would lay the
 * page out at the 300x150 default and then shuffle everything sideways a moment
 * later. A stated 4:3 box at the same 300px height sits beside the pictures, holds
 * still, and keeps the tinted backdrop — which a player wants anyway, since its
 * controls need somewhere to sit.
 */
/*
 * Three clips to a row on a full-width gallery, two on a narrower one.
 *
 * A share of the row rather than a stated width, so the clips always reach the right
 * edge instead of leaving a ragged strip of empty page beside them. Both numbers
 * subtract their share of the 12px gaps: two gaps across three items, one across two.
 */
.profile-section .media-card:has(video) {
    flex: 0 0 calc(33.333% - 8px);
    max-width: calc(33.333% - 8px);
}

/* Below the full-width layout a third of the row is too small to see a stage on. */
@media (max-width: 1100px) {
    .profile-section .media-card:has(video) {
        flex: 0 0 calc(50% - 6px);
        max-width: calc(50% - 6px);
    }
}

.profile-section .media-card video {
    width: 100%;
    /* 16:9 rather than a stated height. Nearly everything a member uploads from a
       phone or a camera is 16:9, and at that shape the clip fills the frame instead
       of sitting in the grey bands a 4:3 box gave it. A portrait clip still
       letterboxes, which is the one case there is no arranging around. */
    height: auto;
    aspect-ratio: 16 / 9;
    max-height: none;
    object-fit: contain;
    background: #f0f0f5;
}

/* Phones: shorter, so a picture does not fill the screen on its own, and the row
   fits two portrait shots side by side rather than one. */
@media (max-width: 760px) {
    .profile-section .media-card img,
    .profile-section .media-card video {
        max-height: 220px;
    }

    /* One clip per row: half of a phone screen is not a video, it is a stamp. */
    .profile-section .media-card:has(video) {
        flex: 0 0 100%;
        max-width: 100%;
    }
}


/*
 * Gigs
 * ----
 * The page is a form and a list, and the list is read far more often than the form
 * is used — including by people who cannot use it at all, since a visitor sees the
 * same page with the form and the row buttons gone. So the list is what the styling
 * is built around; the form is a single quiet row above it.
 */

/* Wider than a normal form section, and wider again now the form is one line: the
   date, two full-size boxes and the button come to more than the 900px a form
   section gives, and the list underneath wants the same width or the two stop
   reading as one page. Short of .container's 1500 so it does not run edge to edge
   on a big screen. */
.gigs-section {
    max-width: 1280px;
}

/* Date, Venue and Details on one line. Each group keeps its own label snug against
   its own control rather than using labels-left's 185px label column — that column
   is for a stack of fields reading downwards, and this is a row. */
.gig-form-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px 26px;
    margin-bottom: 14px;
}

/* Scoped to the row, not to the form: the Calendar's filter row is the same kind of
   row and was left on the 185px label column, which made each group 352px wide and
   pushed the buttons onto a line of their own. */
.labels-left .gig-form-row .field {
    grid-template-columns: auto minmax(0, 1fr);
}

.labels-left .gig-form-row .field > .field-error {
    grid-column: 2;
}

/* The venue list and the distance that decides its contents, on one line. */
.venue-pick {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

/* The list is the widest control on the row and the one being read, so it gets the
   room — enough of it that no venue on the site is shown cut in half. 380px is the
   longest name on file (Huske Hardware House Restaurant & Brewery, 331px at 16px
   Arial) plus the field's own padding and the dropdown arrow.

   A stated width rather than fit-content: the closed control would otherwise change
   size every time a different venue was picked, which moves the Details field
   beside it about the page. */
.venue-pick #venue_id {
    max-width: 380px;
    min-width: 0;
}

/* A venue the site did not put there: one member's word for one room, taken from a
   spreadsheet rather than read off an address. Red because it is the one thing in a
   gig list that has not been checked, and somebody reading the Calendar should be
   able to see that at a glance. The title attribute carries the same fact for anyone
   the colour does not reach. */
.venue-unverified {
    color: #C62828;
}

/* The same red on the option in the dropdown, so following a red venue into the
   form does not land on something that looks perfectly fine. Not every browser
   honours a colour on an option, which is why the word "not checked" is in the
   text as well and is the part doing the work. */
#venue_id option.venue-unverified {
    color: #C62828;
}

/* Red, because it is the same fact as the red in the list: this venue has not been
   checked. Sat where a field error sits, because until it is fixed the field is
   holding something that is not quite an answer. */
.venue-unverified-note {
    font-weight: 400;
}

/* Not an error the member has made — an explanation of why the list looks the way
   it does — so it borrows the error slot's position but not its alarm. */
.venue-gap-note {
    color: #7a5c00;
    font-weight: 400;
}

/* The distance is quieter than the list it governs — it is a setting for the
   control beside it, not a field of the gig. */
.venue-radius {
    padding: 6px 8px;
    font-size: 0.85rem;
    color: #555;
    width: auto;
}

/* The date picker sizes itself to its own text, which browsers do inconsistently;
   a stated width keeps it from being the one control that jumps about. */
.labels-left .gig-form-row #gig_date {
    width: 168px;
}

/* Details matches Venue rather than being sized to its own 50-character cap. The
   two boxes sit next to each other, and a pair of unequal boxes on one row reads as
   an accident before it reads as a limit — maxlength still stops the typing. */
.labels-left .gig-form-row #details {
    width: auto;
}

/* Hard right, on the same line as the fields. margin-left:auto rather than
   justify-content on the row: the fields keep their own even spacing at the left
   and only the leftover goes here, so the button does not drift when a field
   error appears and makes a group taller. */
.gig-form-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-left: auto;
}

/* Future and Previous, coloured. The same greens and reds the alerts already use,
   so the page has one vocabulary for "fine" and "gone" rather than two. The word
   does the telling; the colour only makes it quicker, which is why nothing here is
   colour alone. */
.gig-tense-future {
    color: #1e7a3d;
}

.gig-tense-previous {
    color: #C62828;
}

/* The heading and the Earlier Gigs link share a line, the link hard right.
   baseline alignment rather than centre: the two are both text, and sitting the
   small line on the heading's baseline is what makes them read as one row instead
   of as a heading with something floating beside it. */
.gigs-heading-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px 24px;
}

.gigs-heading-row h1 {
    margin-bottom: 0;
}

.gig-earlier {
    margin: 0;
    font-size: 0.95rem;
    text-align: right;
}

.gig-earlier a {
    font-weight: 600;
}

/* Phones: the heading takes the full width and the link drops under it, left
   aligned — a lone right-aligned line under a left-aligned heading on a narrow
   screen reads as a mistake. */
@media (max-width: 760px) {
    .gigs-heading-row {
        gap: 4px;
    }

    .gig-earlier {
        text-align: left;
    }
}

/* The upload form lives off-screen, not display:none. A label opens the file input
   it points at, and a display:none input is not reliably openable that way; moved
   out of the viewport it still is, and the Import button inside stays reachable by
   keyboard for anyone the auto-submit script does not run for. */
.gig-upload-form {
    position: absolute;
    left: -10000px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* Focus brings it back: a keyboard user tabbing to the Import button must be able to
   see what they have landed on. */
.gig-upload-form:focus-within {
    position: static;
    width: auto;
    height: auto;
    display: flex;
    gap: 10px;
    align-items: center;
    margin-top: 12px;
}

/* A label is not a button, so it needs the bits of one a <button> gets free. */
.gig-upload-label {
    cursor: pointer;
    display: inline-flex;
    align-items: center;
}

/* The buttons and the note that explains one of them, on one line: note filling the
   space at the left, buttons hard right where they were. Wraps to two lines on a
   narrow window rather than squeezing the text into a column. */
.gig-action-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 10px 20px;
    margin-top: 12px;
}

/* Takes whatever the buttons leave, down to a sensible minimum before it wraps. */
.gig-upload-note {
    flex: 1 1 380px;
    margin: 0;
    font-size: 0.88rem;
}

/* Inside the action row the buttons no longer need to push themselves right — the
   note ahead of them does it. */
.gig-action-row .gig-form-actions {
    margin-left: 0;
    flex: 0 0 auto;
}

/* The import report is a list of reasons, so it is a list — an error paragraph
   holding six sentences is read as one and acted on as none. */
.gig-import-report p {
    margin: 0 0 6px;
}

.gig-import-report ul {
    margin: 0;
    padding-left: 20px;
}

.gig-import-report li + li {
    margin-top: 3px;
}

/* A button that reads as a link. It does something rather than going somewhere, so
   it has to be a <button> for a keyboard and a screen reader — but it sits inside a
   row of form controls where a third solid button would look like a third choice
   of equal weight, which it is not. */
.link-button {
    padding: 0;
    border: 0;
    background: none;
    color: #0B6E8F;
    font: inherit;
    font-size: 0.9rem;
    text-decoration: underline;
    cursor: pointer;
    white-space: nowrap;
}

.link-button:hover {
    color: #084F67;
}

.link-button:disabled {
    color: #888;
    cursor: default;
}

/* Whatever came of pressing it, beside it rather than under: the row is already a
   sentence and this is the end of it. */
.location-status {
    font-size: 0.85rem;
    white-space: nowrap;
}

/* The bare bar, on the Calendar: the logo alone. The 124px minimum exists to hold
   an avatar and two rows of links, neither of which is here, so it goes — otherwise
   the page opens on a band of wallpaper with a logo floating in it. */
.site-nav.nav-bare .container {
    min-height: 0;
    padding-top: 10px;
    padding-bottom: 10px;
}

/*
 * The home page in rose, edge to edge.
 *
 * On the body rather than on a panel round the text: the container is capped at
 * 1500px, so colouring anything inside it left a stripe of the page's own grey
 * down both sides of a wide window. The nav bar and the photo strip have grounds
 * of their own and sit on top of this, so what shows is everything below the
 * images — which is the whole of the page a reader is actually reading.
 *
 * #f2d4d9 is a rose rather than a blush: deep enough to read as a colour instead
 * of a printing error, light enough that #222 body text still comes in around 11:1
 * against it and the muted #555 around 6:1. The greys underneath were chosen for a
 * near-white page, so the ones that carry meaning are warmed to match rather than
 * left looking like a different design showing through.
 */
.home-page {
    background: #f2d4d9;
}

/* Borders and rules drawn for an off-white page look like scratches on a coloured
   one. Deepened by about the same amount the ground was. */
.home-page .site-footer {
    border-top-color: #e0bcc3;
}

/* The intro text keeps its own ground — white would be a panel again, so it simply
   sits on the rose with the width it always had. */
.hero-intro > :first-child {
    margin-top: 0;
}

.hero-intro > :last-child {
    margin-bottom: 0;
}

/*
 * Functionality
 * -------------
 * A reference page, so it is set for looking things up rather than for reading
 * through: an entry per page, each one headed by what it is, who can use it and
 * which file it is, with the capabilities as a plain list underneath.
 */
.functionality-section {
    max-width: 1100px;
}

.functionality-lead {
    margin: 0 0 18px;
    font-size: 1rem;
}

/* The contents list wraps rather than scrolls: every entry has to be reachable
   without hunting, and there are few enough to show all of them. */
.functionality-contents {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 18px;
    padding: 14px 18px;
    margin-bottom: 28px;
    background: #f2f2f7;
    border-radius: 8px;
}

.functionality-contents a {
    font-weight: 600;
}

.functionality-entry {
    padding-top: 22px;
    border-top: 1px solid #e5e5e5;
    margin-bottom: 22px;
}

/* Title, filename and audience share a baseline: they are three answers to the
   same question and reading them as one line is the point. */
.functionality-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 12px;
}

.functionality-head .subheading {
    margin: 0;
}

/* The filename is for whoever is working on the site rather than using it, so it
   is set as code and kept quiet. */
.functionality-file {
    font-family: ui-monospace, Consolas, monospace;
    font-size: 0.82rem;
    color: #666;
}

/* Who can use it, said as a badge — it is the first thing that makes an entry
   irrelevant to the reader, so it should be seen before the list is read. */
.functionality-who {
    margin-left: auto;
    padding: 2px 10px;
    border-radius: 999px;
    background: #e6f6ec;
    color: #1e7a3d;
    font-size: 0.8rem;
    font-weight: 600;
    white-space: nowrap;
}

.functionality-lead-line {
    margin: 6px 0 10px;
    color: #444;
}

.functionality-list {
    margin: 0;
    padding-left: 20px;
}

.functionality-list li {
    margin-bottom: 5px;
}

/* Footer links sit above the copyright, separated rather than run together. */
.footer-links {
    margin: 0 0 6px;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 4px 20px;
}

@media (max-width: 760px) {
    .functionality-who {
        margin-left: 0;
    }
}

/*
 * The Calendar's own ground, set on the body by header.php so it runs the full
 * width of the window under the banner rather than stopping at the 1500px the
 * content container is capped to.
 *
 * #f4ead6 is a cream: far enough from the near-white the rest of the site sits on
 * to read as a deliberate colour, and pale enough that #222 body text still comes
 * in around 13:1 against it and the muted #555 around 6:1 — the same margins the
 * rose on the home page was picked for. Warm rather than the sky blue this used
 * to be, and lighter than the rose, so the two coloured pages a stranger meets
 * still do not look like the same page.
 *
 * The nav bar carries its own dark ground and sits on top of this, so what the
 * colour actually covers is everything below the banner: filters, listings and
 * footer alike.
 */
.calendar-page {
    background: #f4ead6;
}

/*
 * The rules through the listing were drawn as pale grey for a near-white page; on
 * a coloured one that grey reads as a scratch rather than a line. Deepened by
 * about as much as the ground was, and tinted the same way so they belong to it.
 */
.calendar-page .calendar-table th {
    border-bottom-color: #ddd0b4;
}

.calendar-page .gig-month {
    border-bottom-color: #ccbb96;
}

.calendar-page .site-footer {
    border-top-color: #ddd0b4;
}

/* The second Close, after the last date. Room above it so it is not read as part
   of the final month. */
.calendar-close-foot {
    margin-top: 28px;
}

/* The Calendar carries two more columns than a member's own gig list — who is
   playing, and how far away they are — so it takes the full width it is given. */
.calendar-section {
    max-width: none;
}

.calendar-filters {
    margin-bottom: 4px;
}

/* The site-wide zip box is 68px, which is 42px inside its padding: five digits at
   this size need more than that and the last one was being clipped. Only widened
   here, where the row has the room to spare. */
.calendar-filters .radius-range input {
    width: 92px;
}

/* Show stays beside the zip box. The shared .gig-form-actions pushes itself to the
   right with margin-left:auto, which is right on the Gigs page and wrong here: the
   count takes that job instead. */
.calendar-actions {
    margin-left: 0;
}

/* The answer, at the far end of the line the question was asked on. */
.calendar-count {
    margin: 0 0 0 auto;
    font-size: 0.95rem;
    text-align: right;
}

/* Phones: the row has already stacked, so pushing the count right would leave it
   alone on a line under a left-aligned button. */
@media (max-width: 760px) {
    .calendar-count {
        margin-left: 0;
        text-align: left;
    }
}

/* The date boxes size themselves to their own text, which browsers do generously —
   left alone the two of them plus the distance group came to 1475 in a 1460 row and
   pushed the buttons onto a line of their own. Stated, they fit with room to spare. */
.calendar-filters #from,
.calendar-filters #to {
    width: 168px;
}

/* Sorted by a column other than the date there are no month headings, so the first
   table needs the space one would have given it. */
.calendar-table:first-of-type {
    margin-top: 20px;
}

/* Column headings: quiet, because they repeat under every month heading and are
   there to be glanced at once rather than read each time. */
.calendar-table th {
    padding-top: 4px;
    padding-bottom: 4px;
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #777;
    border-bottom: 1px solid #e5e5e5;
}

/* The date column carries a year once the month headings are gone, so it needs
   room it does not need when it is only "Sat 5 Sep". nowrap already; this is the
   width that stops it wrapping at the wider text. */
.calendar-table td.gig-date-cell,
.calendar-table th.gig-date-cell {
    width: 132px;
}

/* Who is playing is the cell being browsed for, so it reads as the strong one and
   the venue steps back to being where. */
.calendar-table td.calendar-who-cell {
    width: 22%;
    font-weight: 600;
}

/* The kind sits after the name at ordinary weight: it tells a Band from a Musician
   at a glance without competing with the name it qualifies. */
.calendar-kind {
    font-weight: 400;
    font-size: 0.85rem;
    margin-left: 6px;
}

/* Distance last and narrow — it is what you scan down, not what you read across.
   The heading is included so it sits over the right-hand edge of the figures it
   heads; left-aligned it floated away from its own column. */
.calendar-table th.calendar-where-cell,
.calendar-table td.calendar-where-cell {
    width: 1%;
    white-space: nowrap;
    text-align: right;
}

.calendar-note {
    margin: 0 0 6px;
    font-size: 0.88rem;
}

/* Phones: the two least load-bearing columns go, rather than every column being
   squeezed to the point where none of them is readable. */
@media (max-width: 760px) {
    /* Heading as well as cells: hiding the column but leaving its header behind
       left an empty word over nothing. */
    .calendar-table th.calendar-where-cell,
    .calendar-table td.calendar-where-cell {
        display: none;
    }

    .calendar-table td.calendar-who-cell {
        width: auto;
    }
}

/* The month is the heading the list is organised by, so it gets to look like one:
   set apart from the table above it, tight to the table below it. */
.gig-month {
    margin-top: 26px;
    margin-bottom: 0;
    padding-bottom: 4px;
    border-bottom: 2px solid #e0e0e8;
}

.gig-table {
    margin-top: 0;
}

/* The date is the column the eye runs down, so it is fixed-width and does not move
   when a long venue name turns up two rows later. Details takes the slack. */
.gig-table td.gig-date-cell {
    width: 108px;
    white-space: nowrap;
    font-weight: 600;
}

.gig-table td.gig-venue-cell {
    width: 34%;
}

/* Change and Delete open the row, together and on one line — the delete button is a
   form, which is block-level by default and would otherwise drop below Change. The
   1% width is the usual trick for "as narrow as the contents allow": the browser
   gives the leftover to the columns that asked for a share. */
.gig-table td.gig-actions-cell {
    width: 1%;
    white-space: nowrap;
    text-align: left;
    padding-left: 0;
}

.gig-table td.gig-actions-cell .btn + .inline-form {
    margin-left: 6px;
}

/* A gig that has been and gone is still worth keeping — it is a record of having
   played — but it is not what you came to check, so it steps back rather than
   disappearing. */
.gig-table tr.gig-past td {
    opacity: 0.55;
}

.gig-back {
    margin-top: 28px;
}

/* Phones. The form row stacks, and the labels stop reserving a column of their
   own — at 390px a 185px label leaves 150px of input. The list keeps its columns:
   four short cells still fit, and stacking them would lose the date-down-the-left
   reading the whole page is arranged for. */
@media (max-width: 760px) {
    .gig-form-row {
        gap: 10px 16px;
    }

    .labels-left .gig-form-row .field {
        display: block;
    }

    .labels-left .gig-form-row .field > label {
        display: block;
        text-align: left;
        margin-bottom: 3px;
    }

    .labels-left .gig-form-row .field > input {
        width: 100%;
        max-width: none;
    }

    .gig-table td.gig-details-cell {
        display: none;
    }

    .gig-table td.gig-actions-cell {
        white-space: normal;
    }

    /* Stacked, the button has no row left to sit at the right of. */
    .gig-form-actions {
        margin-left: 0;
    }
}

/* Two buttons now share this block on a viewed profile — Gigs, and Contact when
   there is an address to contact. They sit on one line, right-aligned with the
   block itself, rather than stacking the way a lone button never had to. */
.profile-contact {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 8px 10px;
}

/* ===================================================================== */
/* A page read in a window rather than a tab.                            */
/*                                                                       */
/* Set by $compactPage before the header include; today that means        */
/* somebody else's gig list, which opens from Find Bands or a profile in  */
/* a window about 700px tall. Nothing is hidden and nothing moves — the   */
/* spacing is simply sized for the container it is actually in, because a */
/* month heading that breathes on a monitor pushes half the dates below   */
/* the fold of a window.                                                  */
/* ===================================================================== */

/* The bar is the tallest block on the page and the least of its information. The
   two things setting that height are the logo and the mark at the left, both drawn
   for a full page. Scaled rather than dropped: a window with no identity on it is
   just a box of text. */
.compact-page .site-nav .nav-brand img,
.compact-page .site-nav .nav-avatar img {
    max-height: 44px;
    width: auto;
}

/* The bar's height is a min-height on its container, set so the wallpaper strip
   behind it has room to read as a picture. In a window it is 124px of scenery
   above a list of dates, so it comes down to what the contents need. */
.compact-page .site-nav .container {
    min-height: 0;
}

.compact-page .form-section {
    padding-top: 10px;
    padding-bottom: 14px;
}

.compact-page h1 {
    font-size: 1.6rem;
}

/* The biggest saving on a list that spans five months: 26px above every heading
   is right on a page you scroll and wrong in a window you glance at. */
.compact-page .gig-month {
    margin-top: 10px;
    padding-bottom: 0;
}

.compact-page .gig-table {
    margin-bottom: 4px;
}

.compact-page .gig-table td {
    padding-top: 5px;
    padding-bottom: 5px;
    /* One gig, one line. A venue label carries its town — "The Field Irish Pub &
       Eatery, Dania Beach" — so the column wraps at any width a window can be, and
       a wrapped row reads as two gigs at a glance, which is the one thing a page of
       dates must not do. If a row ever does outrun the window the window scrolls
       sideways, which is honest; wrapping is not. */
    white-space: nowrap;
}

.compact-page .gig-back {
    margin-top: 14px;
}

/* The footer on a windowed page is the disclaimer nobody opened the window to
   read. It stays — it carries the Terms link — but quietly. */
.compact-page .site-footer {
    padding-top: 10px;
    padding-bottom: 10px;
    font-size: 0.8rem;
}

.compact-page .site-footer p,
.compact-page .footer-links {
    margin: 4px 0;
}

/* The short-notice filter changes whose radius applies, which is the one rule on
   Find Bands a venue cannot infer from what it sees. Said on the heading line while
   it is on, and again at length when it empties the list. */
.last-minute-scope {
    font-size: 0.8rem;
    font-weight: 400;
    color: #7a5c00;
    white-space: nowrap;
}

.last-minute-note {
    color: #7a5c00;
    max-width: 62ch;
}

/* A page whose standfirst sits on the heading line rather than under it. The same
   shape as .gigs-heading-row, named for the job instead of for one page, because
   this is the second page to want it. */
.page-heading-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 4px 32px;
}

.page-heading-row h1,
.page-heading-row .subheading {
    margin-bottom: 0;
}

/*
 * The results line sits closer to the buttons than a section heading normally would.
 *
 * .subheading carries 32px above it, which is right when it opens a new part of a
 * page. This one does not — it is the answer to the button immediately above it, and
 * a wide gap read as the filters and the results being unrelated. The margin is on
 * the heading rather than the row because the heading is a flex child here, so its
 * own margin is what was making the space.
 */
.results-heading-row {
    margin-bottom: 10px;
}

.results-heading-row .subheading {
    margin-top: 10px;
}

/* Capped rather than left to run: against a short heading the line would stretch
   most of the way across the page and stop reading as a note. Two lines of about
   sixty characters sits under the heading's own height, so the row costs nothing. */
/* One line, hard right, ending where the filters end. Not wrapped to a block: two
   or three short ragged lines beside a heading read as a column of something, and
   this is a sentence. It gives up a little size to buy the width. */
.page-heading-note {
    margin: 0;
    flex: 0 1 auto;
    text-align: right;
    white-space: nowrap;
    font-size: 0.82rem;
}

/*
 * The row stops where the filters stop.
 *
 * The heading row is free to run the width of the section; the filter block under
 * it is capped. Left alone the note's right edge hung 160px past the Words box, and
 * a right-justified line that lines up with nothing is just a line that has drifted.
 */
/* Matches the form it heads. */
.band-search-filters-heading {
    max-width: 100%;
    /*
     * Note then count, side by side at the left.
     *
     * space-between is inherited from .page-heading-row and would throw the two to
     * opposite ends of the row. They are one line of related text — what the badge
     * means, then how many rows carry it — so they sit together, and the row's own
     * 32px column gap is the space between them.
     */
    justify-content: flex-start;
}

/* The note no longer hangs off the right edge, so it reads from the left like the
   sentence it is. */
.band-search-filters-heading .page-heading-note {
    text-align: left;
}

/* Below this there is not the width for one line beside a heading, so it stops
   pretending: back under the heading, wrapping, reading left. */
@media (max-width: 1180px) {
    .page-heading-note {
        flex-basis: 100%;
        text-align: left;
        white-space: normal;
    }
}


/* ===================================================================== */
/* media_view.php — one gallery item, alone, in a window.                 */
/* ===================================================================== */

/*
 * The player takes the height the window has left after the title and the button,
 * rather than a fixed number: the window is 780px on a desktop and whatever a small
 * laptop allowed on a small laptop, and a clip letterboxed into a box shorter than
 * the space available is the whole complaint this page exists to answer.
 *
 * 100vh less the chrome above and below it. The figure is generous rather than
 * measured — a little short is a little empty space, a little tall is a scrollbar in
 * a window that should never have one.
 */
.media-view-section {
    max-width: none;
    padding-top: 12px;
    padding-bottom: 12px;
}

/* The name at the left, the way out hard against the right edge, on one line. */
/*
 * The public performer window, opened from the Calendar.
 *
 * Name on the left, Close on the right, on the one line — the shape the media
 * window uses, and for the same reason: in a window the way out is the only
 * control there is, so it belongs where the eye already is rather than under
 * everything else.
 */
.performer-view-titlerow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
}

.performer-view-titlerow h1 {
    margin: 0;
    /* A band can be called anything. One line, cut, so the Close button keeps its
       place instead of being pushed off the end of a long name. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * Instruments on the left, About on the right.
 *
 * "auto" then "1fr": the table is sized by its own two columns and the prose takes
 * whatever is left, rather than the two splitting the line evenly and leaving the
 * table with room it does not want. align-items: start so a short table does not
 * stretch itself down the side of a long paragraph.
 *
 * Only ever applied when both halves exist — see performer_view.php — because a
 * paragraph on its own in a content-sized first track would come out one word wide.
 */
.performer-cols-split {
    align-items: start;
    display: grid;
    gap: 0 44px;
    grid-template-columns: auto minmax(0, 1fr);
}

/* The two headings start on the same line: .subheading carries 32px of top margin
   for the stacked layout it was written for, which here would push one column's
   heading below the other's. Put back on the column instead, once. */
.performer-cols {
    margin-top: 32px;
}

.performer-col > .subheading:first-child {
    margin-top: 0;
}

/* Side by side needs a width to be worth having. Below this they stack, and the
   About heading gets its own air back above it. */
@media (max-width: 700px) {
    .performer-cols-split {
        grid-template-columns: minmax(0, 1fr);
    }

    .performer-cols-split .performer-col + .performer-col > .subheading:first-child {
        margin-top: 24px;
    }
}

/*
 * The instrument list in the public window: as wide as its two columns, not as wide
 * as the window.
 *
 * .data-table is width:100% because it was written for the results tables, where
 * filling the page is the point. Here it put the level a screen away from the
 * instrument it belonged to. auto instead, so the two columns sit together and the
 * table ends where its content does.
 *
 * The gap between them is the padding on the cells plus this, rather than whatever
 * the page had spare — a stated distance holds however long the longest instrument
 * name happens to be.
 */
.performer-instruments {
    width: auto;
}

.performer-instruments th,
.performer-instruments td {
    padding-right: 40px;
}

.performer-instruments th:last-child,
.performer-instruments td:last-child {
    padding-right: 12px;
}

/* Quieter than the row it heads: it names the columns once and then gets out of
   the way of the answers, which are what is being read. */
.performer-instruments th {
    color: #5A646E;
    font-size: 0.8rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* Picture beside the facts, the same as a profile. */
.performer-view-head {
    display: flex;
    gap: 18px;
    align-items: flex-start;
    margin-top: 14px;
}

/* On a phone the window is the whole screen and 110px of photograph beside a
   column 180px wide leaves neither room to say anything. Stacked instead. */
@media (max-width: 560px) {
    .performer-view-head {
        flex-direction: column;
    }
}

.media-view-titlerow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
}

.media-view-title {
    font-size: 1.15rem;
    margin: 0;
    /* A filename can be long and the window is not wide. One line, cut — which is
       also what keeps this row a fixed height, and the stage below it sized off a
       constant that assumes exactly that. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

/* Never squeezed by a long filename beside it. */
.media-view-back {
    flex: none;
}

.media-view-owner {
    margin: 2px 0 10px;
}

/*
 * A definite height rather than one the contents decide.
 *
 * Letting the video size the stage means the stage is 150px tall until the video's
 * metadata arrives — a <video> with no metadata yet has no aspect ratio to work
 * from, so height:auto has nothing to compute. The window opens showing a letterbox
 * slot and then jumps. Fixing the stage and fitting the video inside it means the
 * layout is right on the first frame and never moves.
 *
 * The figure is what is above and below it, measured rather than guessed: the
 * compact nav bar, the title row, the owner line, the footer's copyright line and
 * the section's own padding. The title is clipped to one line above, so none of
 * those can grow and push the page into a scrollbar it should never have.
 *
 * It came down when Back moved up onto the title line: the row under the player went
 * with it, and the height it was taking went into the picture.
 */
.media-view-stage {
    height: calc(100vh - 230px);
    background: #101014;
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

/* Audio has no picture, so it takes the height of its player and no more. */
.media-view-stage-audio {
    height: auto;
}

/*
 * Fills the stage, fitted rather than cropped — a phone video shot upright is shown
 * upright rather than cut to a shape it was never in, with the stage's dark ground
 * filling the sides.
 */
.media-view-stage video,
.media-view-stage img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

/* Audio has no picture, so the stage is a strip rather than a screen. */
.media-view-stage audio {
    width: 100%;
    max-width: 620px;
    margin: 28px 16px;
    display: block;
}

/* The Contact Us honeypot: off the page rather than display:none, because some
   bots skip anything a browser would not paint. Nobody sees it either way. */
.contact-hp {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/*
 * ---------------------------------------------------------------------------
 * Phone: the last word.
 *
 * Everything below overrides a rule defined LATE in this file — .search-actions
 * at ~3490, .venue-pick at ~4180, .radius-range at ~2195. A media query does not
 * outrank anything on its own; at equal specificity the later rule still wins, so
 * a phone block sitting further up the file loses to them silently. Rules that
 * have to beat those live here, at the end, where nothing comes after them.
 *
 * Every one of these is the same underlying fault: a row of controls whose
 * min-content width is wider than a phone, sitting in a "1fr" column. "1fr" is
 * minmax(auto, 1fr), and that auto floor is the widest thing inside — so the
 * column quietly grows past the screen instead of the contents wrapping. The
 * cure each time is minmax(0, 1fr) on the column and permission to wrap inside.
 * ---------------------------------------------------------------------------
 */
@media (max-width: 760px) {
    /* Find Musicians: the buttons and the checkboxes under the filters. */
    .search-actions {
        grid-template-columns: minmax(0, 1fr);
    }

    .search-actions-buttons,
    .search-actions-checks {
        flex-wrap: wrap;
        justify-content: center;
        min-width: 0;
        max-width: 100%;
    }

    /* Calendar and the gear filters: "Within [n] miles" may break its line here
       rather than hold the page open. */
    .radius-range {
        flex-wrap: wrap;
        white-space: normal;
        min-width: 0;
        max-width: 100%;
    }

    /* Gigs: the venue list is sized to the longest venue name on file, which is
       380px — wider than the screen it is being read on. */
    .venue-pick {
        flex-wrap: wrap;
        max-width: 100%;
    }

    .venue-pick #venue_id {
        max-width: 100%;
        width: 100%;
    }

    .venue-radius {
        max-width: 100%;
    }

    /* The date and details boxes carry stated widths for the same reason the venue
       list does — to stop controls resizing as their contents change. On a phone
       there is no room for that courtesy. */
    .labels-left .gig-form-row #gig_date {
        width: 100%;
    }

    /* Gear: the Active/Inactive filter is held 2in clear of the Add Gear button so
       the two do not read as one control. 2in is 192px — half a phone — so on the
       small screen the wrap does that job instead. */
    .gear-filter {
        margin-left: 0;
    }

    /* Add Gear: the file picker sits beside its note. A file input's intrinsic
       width is the browser's own "Choose Files / no file chosen" button plus its
       text, which no CSS width shrinks — so the row is allowed to wrap and the
       input is allowed to be narrower than the browser would like. */
    .file-with-note {
        flex-wrap: wrap;
        min-width: 0;
        max-width: 100%;
    }

    .labels-left .field input[type="file"] {
        min-width: 0;
        max-width: 100%;
    }

    /*
     * The four-across filter rows — Gear buying, and the wide search rows that
     * borrow the same classes.
     *
     * Four columns in 335px is 58px each, and .labels-left gives the label a stated
     * 80px of that, so the control column resolved to LITERALLY 0px: a row of
     * labels with invisible boxes under them. Same one stacked column as every
     * other field on a phone, and the same label-above treatment, so the buying
     * search reads like the rest of the site rather than like a broken table.
     */
    .field-row-4,
    .gear-field-row-4,
    .gear-field-row-2,
    .field-row-even,
    .gear-buy-filters .field-row-4 {
        display: grid;
        grid-template-columns: minmax(0, 1fr);
        gap: 0;
    }

    .labels-left .field-row-4 .field,
    .labels-left .gear-field-row-4 .field,
    .labels-left .gear-field-row-2 .field,
    .labels-left .field-row-even .field,
    .labels-left.gear-form .field {
        grid-template-columns: minmax(0, 1fr);
        align-items: start;
    }

    /* Spans mean nothing once there is one column to span — and neither does the
       padding that held the middle two columns apart, which stacked turns into two
       fields indented 12px from the four above and below them. */
    .field-row-4 > .field-span-2,
    .field-row-4 > .field-span-full,
    .field-row-4 .field:nth-child(2),
    .field-row-4 .field:nth-child(3),
    .field-row-pair .field:nth-child(2) {
        grid-column: 1;
        padding-left: 0;
        padding-right: 0;
    }

    /* Every control in those rows takes the row it now has to itself. */
    .labels-left .field-row-4 .field > input,
    .labels-left .field-row-4 .field > select,
    .labels-left .gear-field-row-4 .field > input,
    .labels-left .gear-field-row-4 .field > select,
    .labels-left.gear-form .gear-field-row-2 .field > input,
    .labels-left .field-row-4 .radius-range select,
    .labels-left .field-row-4 .radius-range input {
        width: 100%;
        max-width: 100%;
        min-width: 0;
        justify-self: stretch;
    }
}

/* ---------------------------------------------------------------------------
 * The "add this to your home screen" bar
 *
 * Built and inserted by js/install-app.js, never by PHP - there is no markup for
 * it in any page, because whether it should exist at all is a question only the
 * browser can answer (Chrome offers to install, Safari cannot, and a phone
 * already running the installed app must be offered nothing).
 *
 * Fixed to the bottom rather than the top: the top of every page is the nav bar
 * and the thing the reader came for, and a strip over that is the pattern
 * everybody has learnt to close without reading.
 * --------------------------------------------------------------------------- */

.install-banner {
    position: fixed;
    left: 12px;
    right: 12px;
    /* Clear of the iPhone home indicator, which sits over the bottom 34px of the
       screen and would take the button with it. Zero everywhere else. */
    bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    z-index: 1000;

    display: flex;
    align-items: center;
    gap: 14px;

    max-width: 560px;
    margin: 0 auto;
    padding: 12px 14px;

    background: #fff;
    border: 1px solid #e5e5e5;
    border-radius: 10px;
    /* Lifted off the page it is covering, so it reads as something on top of the
       site rather than part of it. */
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
}

.install-banner-icon {
    width: 44px;
    height: 44px;
    border-radius: 10px;
    flex-shrink: 0;
}

.install-banner-text {
    flex: 1;
    margin: 0;
    font-size: 0.95rem;
    line-height: 1.35;
}

/* Narrower than a .btn elsewhere: this one shares a line with a sentence and an
   icon on a phone screen, and the 120px minimum every other button carries would
   squeeze the words to nothing. */
.install-banner-accept {
    min-width: 0;
    padding: 9px 18px;
    flex-shrink: 0;
}

.install-banner-close {
    flex-shrink: 0;
    width: 30px;
    height: 30px;
    padding: 0;
    /* Tinted rather than white or transparent, like every other button here. */
    background: #f0f0f5;
    color: #555;
    border: 1px solid #e5e5e5;
    border-radius: 50%;
    font-family: inherit;
    font-size: 1.2rem;
    line-height: 1;
    cursor: pointer;
}

.install-banner-close:hover {
    background: #e5e5e5;
}

@media (max-width: 760px) {
    /* On a phone the sentence, the button and the icon do not fit across, so the
       bar becomes two rows: icon and words on the first, the button under them.
       The close button stays pinned to the top right corner of the bar rather
       than joining a row, where it would either stretch or push the words. */
    .install-banner {
        flex-wrap: wrap;
        padding-right: 44px;
        position: fixed;
    }

    .install-banner-text {
        /* Enough to force the button onto its own line, whatever the wording. */
        flex: 1 1 60%;
    }

    .install-banner-accept {
        flex: 1 1 100%;
        /* Full width once it has a row to itself, the same way every other
           control on the site stacks below 760px. */
        width: 100%;
    }

    .install-banner-close {
        position: absolute;
        top: 8px;
        right: 8px;
    }
}

/* ---------------------------------------------------------------------------
 * Site statistics (admin_stats.php)
 * --------------------------------------------------------------------------- */

/* Wider than an ordinary .form-section, which is sized for a column of fields.
   This one carries a six-column table. */
.stats-section {
    max-width: 1100px;
}

.stats-filters .form-actions {
    margin-top: 4px;
}

.stats-period {
    margin-bottom: 18px;
}

/*
 * The headline figures. A row that wraps rather than a fixed grid: there are five
 * tiles, and five does not divide neatly into any column count a phone can offer,
 * so they are left to fall where they fit.
 */
.stat-tiles {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 24px;
}

.stat-tile {
    flex: 1 1 150px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 14px 16px;
    background: #fff;
    border: 1px solid #e5e5e5;
    border-radius: 8px;
}

.stat-figure {
    font-size: 1.7rem;
    font-weight: 700;
    line-height: 1.1;
    color: #0B7C76;
}

.stat-label {
    font-size: 0.82rem;
    color: #555;
}

/* Figures line up on their last digit, which is the only way a column of numbers
   can be compared at a glance. */
.data-table th.stats-num,
.data-table td.stats-num {
    text-align: right;
    white-space: nowrap;
}

.stats-table th,
.stats-table td {
    vertical-align: middle;
}

/*
 * The day-by-day bar. Takes whatever width is left after the three columns of
 * figures, which on a wide screen is most of the table and on a phone is nothing
 * much - and nothing much is the right answer there, since the numbers beside it
 * say the same thing exactly.
 */
.stats-bar-head,
.stats-bar-cell {
    width: 45%;
}

.stats-bar {
    display: block;
    height: 12px;
    min-width: 2px;
    border-radius: 3px;
    background: #0B9891;
}

/* A member who has since been deleted. Their views still count; there is just
   nobody left to name. */
.stats-gone {
    color: #777;
    font-style: italic;
}

@media (max-width: 760px) {
    /* Two across rather than one: these are short figures with short labels, and
       one per row would be five screens of scrolling before the table starts. */
    .stat-tile {
        flex: 1 1 calc(50% - 6px);
    }

    .stat-figure {
        font-size: 1.4rem;
    }

    /* The bar is decoration, and at phone width it is decoration competing with
       the figures it illustrates for a strip about an inch wide. */
    .stats-bar-head,
    .stats-bar-cell {
        display: none;
    }
}
