﻿/* ============================================================================
   sv-ui.css - stitts-vegas.com UI layer
   Styles the EXISTING class contract emitted by Framework.Web server controls
   (#headernav, table.quicktable, .dvcontainer, ul.dv, .buttons, .contentblock)
   so that no shared framework code had to change. New sv-* classes are additive.
   Requires sv-theme.css to be loaded first.
   ========================================================================= */

/* ---------------------------------------------------------------- reset --- */

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

html, body, h1, h2, h3, h4, h5, h6, p, ol, ul, li, pre, code, address, form,
fieldset, blockquote, figure, table, th, td {
    margin: 0;
    padding: 0;
}

html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;

    /* Hold the scrollbar's width whether or not there is one to show.
       #container is margin:0 auto inside the viewport, so a page long enough to scroll is laid
       out in 15px less width than a page that is not, and the whole shell lands 7 or 8px to the
       left of where the last page put it. Home, News and Stats scroll; Events and Members do not,
       and every step between the two sets nudged the site sideways. Same on the admin dashboard,
       where the menu and three of the five listings fit and the other two do not. */
    scrollbar-gutter: stable;
}

/* Browsers without scrollbar-gutter get the older answer: always show the track, so its width is
   always spoken for. It costs an inert scrollbar on short pages, which is the lesser of the two. */
@supports not (scrollbar-gutter: stable) {
    html { overflow-y: scroll; }
}

/* The ground is whatever the theme says it is: Felt and Parchment supply a
   hairline diagonal weave, Blue Baize supplies the tiling card-suit motif. Both
   the layers and their sizes are tokens, because the two are not interchangeable
   - a gradient wants "auto", a tile wants its own repeat. */
body {
    background-color: var(--sv-bg);
    background-image: var(--sv-bg-image);
    background-size: var(--sv-bg-size);
    background-attachment: fixed;
    color: var(--sv-text);
    font-family: var(--sv-font);
    font-size: var(--sv-fs-base);
    line-height: 1.55;
    min-height: 100vh;
    margin: 0;
}

img { border: 0; max-width: 100%; height: auto; }
svg { display: block; max-width: 100%; }

h1, h2, h3, h4, h5, h6 { color: var(--sv-heading); font-weight: 650; line-height: 1.25; }
h1 { font-size: var(--sv-fs-2xl); }
h2 { font-size: var(--sv-fs-xl); }
h3 { font-size: var(--sv-fs-lg); }

p { margin-bottom: var(--sv-space-3); }
p:last-child { margin-bottom: 0; }

ol { list-style: decimal; margin-left: 1.4em; }
ul { list-style: square; margin-left: 1.4em; }

a { color: var(--sv-link); text-decoration: none; transition: color var(--sv-transition); }
a:hover { color: var(--sv-link-hover); text-decoration: underline; }
a:visited { color: var(--sv-link-visited); }

hr { border: 0; border-top: 1px solid var(--sv-border); margin: var(--sv-space-5) 0; }

strong, b { font-weight: 650; }

/* visible, consistent keyboard focus everywhere */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid var(--sv-accent);
    outline-offset: 2px;
    border-radius: var(--sv-radius-sm);
}

::selection { background: var(--sv-accent-soft); }

/* --------------------------------------------------------------- layout --- */

#top { position: absolute; }

#container {
    width: 100%;
    max-width: var(--sv-shell-max);
    margin: 0 auto;
    padding: var(--sv-space-4);
    display: grid;
    grid-template-columns: var(--sv-sidebar-w) minmax(0, 1fr);
    grid-template-areas:
        "header  header"
        "sidebar content";
    gap: var(--sv-space-4);
    align-items: start;
}

/* DetailView / Mobile masters have no sidebar - collapse to one column.
   Driven by an explicit class on #container (set in the master page) rather
   than :has(), so the layout is correct on older browsers too. */
#container.sv-nosidebar {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
        "header"
        "content";
}

.header {
    grid-area: header;
    display: flex;
    align-items: center;
    gap: var(--sv-space-4);
    flex-wrap: wrap;
    min-height: 0;
    position: relative;
    width: auto;
    padding: var(--sv-space-2) 0 var(--sv-space-4) 0;
    border-bottom: 1px solid var(--sv-border);
}

.header img {
    float: none;
    margin: 0;
    max-height: 54px;
    width: auto;
    border-radius: var(--sv-radius-sm);
}

/* ----------------------------------------------------------------- logo --- */

.sv-logo-link { display: block; flex: 0 0 auto; line-height: 0; text-decoration: none; }
.sv-logo-link:hover { text-decoration: none; }
.sv-logo-link:hover .sv-logo-notch { fill: var(--sv-accent-hover); }

.sv-logo { width: 268px; height: auto; max-width: 100%; overflow: visible; }

/* CSS variables are ignored in SVG presentation attributes, so the logo's
   colours all come from these classes - see UserControls/SvLogo.ascx */
.sv-logo-chip   { fill: var(--sv-surface-2); stroke: var(--sv-accent); stroke-width: 2; }
.sv-logo-inner  { fill: none; stroke: var(--sv-accent); stroke-width: 1; opacity: 0.45; }
.sv-logo-notch  { fill: var(--sv-accent); transition: fill var(--sv-transition); }

/* red suits stay red in both themes; black suits follow the text colour, so
   they are dark on parchment and light on felt */
.sv-logo-red    { fill: var(--sv-danger); }
.sv-logo-dark   { fill: var(--sv-text); }

.sv-logo-kicker {
    fill: var(--sv-text-muted);
    font-family: var(--sv-font);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 2.6px;
}
.sv-logo-word {
    fill: var(--sv-heading);
    font-family: var(--sv-font-display);
    font-size: 25px;
    font-weight: 700;
    letter-spacing: 0.6px;
}

/* ------------------------------------------- PokerCadabra brand mark ----- */

.sv-brand-link {
    display: block;
    flex: 0 0 auto;
    line-height: 0;
    text-decoration: none;
    /* divider between the tour logo and the brand mark */
    margin-left: var(--sv-space-4);
    padding-left: var(--sv-space-4);
    border-left: 1px solid var(--sv-border);
}
.sv-brand-link:hover { text-decoration: none; }
.sv-brand-link:hover .sv-brand-arc { opacity: 1; }

/* Two PNG variants for three themes (see SvBrandMark.ascx). The light-lettering
   mark is the base, so it serves both dark grounds - Blue Baize and Felt - and
   Parchment is the only one that has to override. Keeping amber as the shared
   accent is what makes two artworks enough. */
.sv-art { display: block; }
.sv-art-light { display: none; }
:root[data-theme="light"] .sv-art-dark  { display: none; }
:root[data-theme="light"] .sv-art-light { display: block; }

/* The two variants differ slightly in aspect (3.31 vs 3.46). object-fit
   letterboxes both into one fixed box, so toggling the theme does not nudge
   the header layout, and neither variant is distorted. */
.sv-brand-link .sv-art {
    height: 42px;
    width: 146px;
    object-fit: contain;
    object-position: left center;
    transition: opacity var(--sv-transition);
}
.sv-brand-link:hover .sv-art { opacity: 0.9; }

/* tiny mark beside the footer copyright - reuses the same 2x asset */
.sv-footer-brand {
    display: inline-block;
    vertical-align: middle;
    margin: 0 var(--sv-space-1);
    line-height: 0;
    text-decoration: none;
}
.sv-footer-brand:hover { text-decoration: none; }
.sv-footer-brand .sv-art {
    height: 18px;
    width: 63px;
    object-fit: contain;
    object-position: center;
    opacity: 0.85;
    transition: opacity var(--sv-transition);
}
.sv-footer-brand:hover .sv-art { opacity: 1; }

/* ------------------------------------------- the unusual hand prompt --- */

/* A disclosure, not a field: the question is optional and almost always skipped, so it stays
   one quiet line until somebody wants it. */
.sv-hand-toggle {
    display: flex;
    align-items: center;
    gap: var(--sv-space-2);
    width: 100%;
    margin-top: var(--sv-space-3);
    padding: var(--sv-space-2) 0;
    border: 0;
    background: none;
    color: var(--sv-link);
    font-family: inherit;
    font-size: var(--sv-fs-sm);
    text-align: left;
    cursor: pointer;
}
.sv-hand-toggle:hover { color: var(--sv-link-hover); }
.sv-hand-caret { font-size: 9px; opacity: 0.8; }

.sv-hand-fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: var(--sv-space-3);
    padding: var(--sv-space-3);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    background: var(--sv-surface-2);
}
.sv-hand-field { display: flex; flex-direction: column; gap: var(--sv-space-1); min-width: 0; }
.sv-hand-field > span {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 600;
}
.sv-hand-field select { width: 100%; }
.sv-hand-fields .sv-hint { grid-column: 1 / -1; margin: 0; }

/* The hand on a play-by-play line, quieter than the bust it explains. */
.sv-ko-hand {
    color: var(--sv-text-faint);
    font-size: var(--sv-fs-xs);
}
.sv-badbeat {
    margin-left: var(--sv-space-2);
    padding: 1px 7px;
    border: 1px solid var(--sv-danger);
    border-radius: var(--sv-radius-pill);
    color: var(--sv-danger);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    white-space: nowrap;
}

/* --------------------------------------------- home page game cards --- */

/* Three stacked cards on the signed-in home page: being played now, coming up, recently
   played. One row shape for all three, so the eye learns it once. */
.sv-games-card + .sv-games-card { margin-top: var(--sv-space-4); }

.sv-game-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.sv-game-row {
    display: flex;
    align-items: center;
    gap: var(--sv-space-4);
    padding: var(--sv-space-3) 0;
    list-style: none;
}
.sv-game-row + .sv-game-row { border-top: 1px solid var(--sv-border); }

/* The date block. Fixed width so every title starts at the same place down the card, and wide
   enough for the longest label it can hold - "TOMORROW" overflowed into the title at 62px. */
.sv-game-when {
    display: flex;
    flex-direction: column;
    flex: 0 0 auto;
    width: 80px;
    text-align: center;
    text-decoration: none;
}
.sv-game-when:hover { text-decoration: none; }
.sv-game-day {
    color: var(--sv-accent);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    white-space: nowrap;
}
.sv-game-date {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
}

.sv-game-main {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1 1 auto;
}
.sv-game-title {
    color: var(--sv-text);
    font-size: var(--sv-fs-md);
    font-weight: 600;
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sv-game-title:hover { color: var(--sv-link-hover); text-decoration: underline; }
.sv-game-sub {
    color: var(--sv-text-faint);
    font-size: var(--sv-fs-sm);
}

.sv-game-pill {
    flex: 0 0 auto;
    padding: 3px 10px;
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius-pill);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 600;
    white-space: nowrap;
}
.sv-game-pill-yes   { border-color: var(--sv-success, var(--sv-accent)); color: var(--sv-success, var(--sv-accent)); }
.sv-game-pill-maybe { border-color: var(--sv-accent); color: var(--sv-accent); }
.sv-game-pill-no    { border-color: var(--sv-border); color: var(--sv-text-faint); }
.sv-game-pill-ask   { border-style: dashed; }
.sv-game-pill-live  { border-color: var(--sv-danger); color: var(--sv-danger); }

/* The quiet "live" marker beside the heading of a game under way. */
.sv-live-dot::before {
    content: "";
    display: inline-block;
    width: 7px;
    height: 7px;
    margin-right: var(--sv-space-2);
    border-radius: 50%;
    background: var(--sv-danger);
    vertical-align: middle;
}

/* A phone drops the pill under the title rather than squeezing three columns. */
@media (max-width: 520px) {
    .sv-game-row { flex-wrap: wrap; gap: var(--sv-space-2) var(--sv-space-3); }
    .sv-game-main { flex: 1 1 60%; }
    .sv-game-pill { margin-left: calc(80px + var(--sv-space-3)); }
}

/* ------------------------------------------------------- account pill --- */

/* The right-hand cluster of the header: theme toggle, then the account pill. margin-left:auto
   takes all the slack the flex row has, so it sits hard against the right edge however wide
   the nav is, and wraps to its own line with the nav on a phone. */
.sv-header-right {
    display: flex;
    align-items: center;
    gap: var(--sv-space-2);
    margin-left: auto;
}

/* <details> is the whole mechanism: open state, keyboard and screen-reader semantics all
   come free. position:relative anchors the menu to it. */
.sv-account {
    position: relative;
}

.sv-account-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-space-2);
    max-width: 220px;
    padding: 3px 10px 3px 3px;
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-pill);
    background: var(--sv-surface-2);
    color: var(--sv-text);
    font-size: var(--sv-fs-sm);
    line-height: 1;
    cursor: pointer;
    user-select: none;
    transition: border-color var(--sv-transition), background var(--sv-transition);
}
.sv-account-btn:hover { border-color: var(--sv-border-strong); }

/* The default disclosure triangle, removed in both engines. */
.sv-account-btn::-webkit-details-marker { display: none; }
.sv-account-btn { list-style: none; }
.sv-account-btn::marker { content: ""; }

/* Keyboard focus has to stay visible: this is a real control, and it is the only way out
   of the site for somebody not using a mouse. */
.sv-account-btn:focus-visible {
    outline: 2px solid var(--sv-accent);
    outline-offset: 2px;
}

/* A poker chip: amber edge spots around a dark middle holding the member's initials, or
   their picture once they have one. One definition for every size it appears at - the
   account pill, that pill's menu, My Account - because a second one briefly appeared
   further down this file and silently won, which resized the pill and hid its initials.

   The middle is dark and the spots are amber ON that dark ground. An earlier version had
   it the other way round - amber middle, dark notches cut into it - and at the pill's
   28px that stops reading as a chip's edge and starts reading as a sunburst.

   Drawn entirely in backgrounds, deliberately: the initials arrive as a bare Literal - a
   text node, not an element - so anything painted in a ::before covers them, and no
   z-index lifts a text node above it. Backgrounds always sit underneath.

   The spots need repeating-conic-gradient. Where that is missing the background-color
   shows through on its own and the chip degrades to a plain dark disc with initials. */
.sv-avatar {
    --sv-avatar-size: 28px;
    --sv-chip-rim: 3px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: var(--sv-avatar-size);
    height: var(--sv-avatar-size);
    padding: var(--sv-chip-rim);
    box-sizing: border-box;
    border-radius: 50%;
    overflow: hidden;
    background-color: var(--sv-surface-2);
    background-image:
        /* closest-side, and 100% rather than 50%. A circle gradient's colour-stop
           percentages are relative to its RAY LENGTH, and the default sizing runs that ray
           to the farthest CORNER - 1.41x the radius - so "50%" landed well inside the disc
           and the spots came out as sun rays reaching into the middle. closest-side makes
           the ray exactly the radius, and 100% minus the rim is then the rim. */
        radial-gradient(circle closest-side at 50% 50%,
            var(--sv-surface-2) calc(100% - var(--sv-chip-rim)),
            rgba(0, 0, 0, 0) calc(100% - var(--sv-chip-rim))),
        repeating-conic-gradient(var(--sv-accent) 0deg 13deg, rgba(0, 0, 0, 0) 13deg 45deg);
    background-origin: border-box;
    background-clip: border-box;
    /* The chip's own edge, in the colour the cards use. A box-shadow rather than a border:
       it follows the border-radius, and it sits outside the box so adding it does not
       resize the chip or push the rim spots inward. */
    box-shadow: 0 0 0 1px var(--sv-border);
    color: var(--sv-text);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.02em;
}
/* The picture fills the middle and leaves the rim showing. */
.sv-avatar img {
    width: 100%;
    height: 100%;
    display: block;
    border-radius: 50%;
    object-fit: cover;
}

.sv-avatar-sm { --sv-avatar-size: 22px; --sv-chip-rim: 2px; font-size: 9px; }
.sv-avatar-lg { --sv-avatar-size: 38px; --sv-chip-rim: 4px; font-size: 14px; }
.sv-avatar-xl { --sv-avatar-size: 104px; --sv-chip-rim: 7px; font-size: var(--sv-fs-xl); }

/* One face size for the three places where the person IS the row: the top of a feed card, a name
   in the roster, and whoever won a night. At 38 all three read as decoration beside the words.
   Written as a rule rather than another modifier class because it belongs to those three places
   rather than to whoever happens to pass it, and because each of them already outranks the
   sv-avatar-lg two of them ask for. */
.sv-post-head > .sv-avatar,
.sv-roster-cell > .sv-avatar,
.sv-game-winners .sv-avatar {
    --sv-avatar-size: 48px;
    --sv-chip-rim: 5px;
    font-size: 17px;
}

/* Who won a finished night, at the right-hand end of the row - the slot a pill would use, and
   a recent game never has one. The faces overlap a little when there are several, which is
   what makes a four way tie read as one group rather than as a row of unrelated people. */
.sv-game-winners {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    flex: 0 0 auto;
    gap: 3px;
    margin-left: auto;
    max-width: 52%;
    text-align: right;
}
.sv-game-chips { display: inline-flex; flex: 0 0 auto; }
/* Overlapped, and later chips sit on top so the stack reads left to right. Roughly a quarter of
   the face, so a tie still reads as a group now the chips are 48 rather than 38. */
.sv-game-chips .sv-avatar + .sv-avatar { margin-left: -12px; }
.sv-game-won {
    max-width: 100%;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Signed out there are no initials to show, so the circle is quiet rather than accent - the
   pill should not look like an account somebody already has. */
.sv-avatar-anon {
    padding: 0;
    background-color: var(--sv-surface-3);
    background-image: none;
    color: var(--sv-text-muted);
    border: 1px solid var(--sv-border);
}

/* ------------------------------------------------ the member hover card --- */

/* Built once by js\sv-hovercard.js and appended to <body>; shown at the pointer when it rests
   on any chip carrying data-sv-user. Fixed, because it is placed from pointer coordinates,
   which are viewport coordinates. Above the account menu (60) so a card for a face in the
   header's menu is not cut off by the menu; below a modal (100), which owns the screen.

   The face inside is a plain .sv-avatar-xl - the same 104px chip My Account draws - so the
   two can never disagree about what "large" means. The width is fixed rather than fitted:
   the picture arrives a moment after the words, and a card that grew when it landed would
   twitch under the pointer. */
.sv-hovercard {
    position: fixed;
    z-index: 90;
    display: flex;
    align-items: center;
    gap: var(--sv-space-4);
    width: 330px;
    max-width: calc(100vw - 16px);
    padding: var(--sv-space-4);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius-lg);
    background-color: var(--sv-surface);
    background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.03), transparent 80px);
    box-shadow: var(--sv-shadow-2);
    color: var(--sv-text);
    font-size: var(--sv-fs-sm);
    line-height: 1.4;
    text-align: left;
    opacity: 0;
    transform: translateY(4px);
    transition: opacity var(--sv-transition), transform var(--sv-transition);
}
.sv-hovercard.is-open { opacity: 1; transform: none; }

/* REQUIRED, for the same reason as .sv-overlay[hidden]: display:flex above is an author rule
   and would beat the UA stylesheet's [hidden]{display:none}. */
.sv-hovercard[hidden] { display: none !important; }

.sv-hovercard-body {
    display: flex;
    flex-direction: column;
    gap: var(--sv-space-1);
    min-width: 0;
}

.sv-hovercard-name {
    color: var(--sv-heading);
    font-size: var(--sv-fs-lg);
    font-weight: 600;
    line-height: 1.25;
    margin-bottom: var(--sv-space-1);
    overflow-wrap: anywhere;
}

/* The words quiet, the fact in them - a date, a count - in the text colour, so the eye lands
   on "November 11, 2010" and "142" rather than on "Member since". */
.sv-hovercard-line { color: var(--sv-text-muted); }
.sv-hovercard-value { color: var(--sv-text); font-weight: 600; }

.sv-account-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 550;
}

.sv-account-caret {
    flex: 0 0 auto;
    color: var(--sv-text-faint);
    transition: transform var(--sv-transition);
}
.sv-account[open] .sv-account-caret { transform: rotate(180deg); }

/* ------------------------------------------------------ the open menu --- */

.sv-account-menu {
    position: absolute;
    z-index: 60;
    top: calc(100% + 6px);
    right: 0;
    min-width: 220px;
    padding: var(--sv-space-2);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius);
    background: var(--sv-surface);
    box-shadow: var(--sv-shadow-2);
    font-size: var(--sv-fs-sm);
    text-align: left;
}

.sv-account-head {
    display: flex;
    align-items: center;
    gap: var(--sv-space-3);
    padding: var(--sv-space-2);
    margin-bottom: var(--sv-space-2);
    border-bottom: 1px solid var(--sv-border);
}
.sv-account-head-text { display: flex; flex-direction: column; min-width: 0; }
.sv-account-head-text strong {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sv-account-sub {
    color: var(--sv-text-faint);
    font-size: var(--sv-fs-xs);
}

/* Every row in the menu is a link or a LinkButton; both render as <a>. */
.sv-account-menu a.sv-account-item,
.sv-account-item {
    display: block;
    padding: var(--sv-space-2);
    border-radius: var(--sv-radius-sm);
    color: var(--sv-text);
    text-decoration: none;
}
.sv-account-menu a.sv-account-item:hover,
.sv-account-item:hover {
    background: var(--sv-surface-2);
    color: var(--sv-text);
    text-decoration: none;
}
.sv-account-menu a.sv-account-signout { color: var(--sv-danger); }

.sv-account-sep {
    height: 1px;
    margin: var(--sv-space-2) 0;
    background: var(--sv-border);
}

/* ------------------------------------ the sign-in form, in the popover --- */

.sv-account-form { min-width: 260px; }
.sv-account-form .sv-field { margin-bottom: var(--sv-space-3); }
.sv-account-form .sv-field label {
    display: block;
    margin-bottom: var(--sv-space-1);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
}
.sv-account-form .sv-field input[type="text"],
.sv-account-form .sv-field input[type="email"],
.sv-account-form .sv-field input[type="password"] { width: 100%; }
.sv-account-form .sv-field-check label {
    display: inline;
    margin: 0 0 0 var(--sv-space-2);
    color: var(--sv-text);
    font-size: var(--sv-fs-sm);
}
.sv-account-submit { width: 100%; }
.sv-account-signup {
    display: block;
    margin-top: var(--sv-space-2);
    text-align: center;
    font-size: var(--sv-fs-xs);
}

/* A phone has no room for a 220px panel hanging off the right edge, so it becomes a sheet
   pinned to both sides under the header instead. */
@media (max-width: 600px) {
    .sv-account-name { display: none; }
    .sv-account-btn { padding: 3px; }
    .sv-account-menu {
        position: fixed;
        top: auto;
        left: var(--sv-space-3);
        right: var(--sv-space-3);
        min-width: 0;
    }
}

.sidebar {
    grid-area: sidebar;
    display: flex;
    flex-direction: column;
    gap: var(--sv-space-4);
    float: none;
    width: auto;
    min-width: 0;
    max-width: none;
    margin: 0;
    overflow: visible;
    color: var(--sv-text);
    font-size: var(--sv-fs-base);
}

.content {
    grid-area: content;
    float: none;
    display: block;
    width: auto;
    min-width: 0;
    min-height: 0;
    margin: 0;
    overflow: visible;
    background: none;
    border: 0;
    font-size: var(--sv-fs-base);
}

.content span.h {
    display: block;
    color: var(--sv-heading);
    font-size: var(--sv-fs-lg);
    font-variant: normal;
    font-weight: 650;
    letter-spacing: -0.01em;
    margin-bottom: var(--sv-space-2);
}

#footer-spacer { display: none; }

#footer {
    width: 100%;
    max-width: var(--sv-shell-max);
    height: auto;
    margin: var(--sv-space-5) auto 0 auto;
    padding: var(--sv-space-4);
    border-top: 1px solid var(--sv-border);
    color: var(--sv-text-faint);
    font-size: var(--sv-fs-xs);
    text-align: center;
}
#footer a { color: var(--sv-text-muted); }

/* The footer reads as three short facts - the caller's address, the copyright, who built the
   site - and a hairline after each of the first two keeps them apart without adding words. */
.sv-footer-seg {
    display: inline-block;
    margin-right: var(--sv-space-2);
    padding-right: var(--sv-space-2);
    border-right: 1px solid var(--sv-border);
}

/* The address is diagnostic rather than decorative, so it is quieter than the segments beside
   it, and is never allowed to wrap through the middle of an address. */
.sv-footer-ip {
    color: var(--sv-text-faint);
    font-family: var(--sv-font-mono, ui-monospace, "Cascadia Mono", "Consolas", monospace);
    font-size: 0.9em;
    letter-spacing: 0.01em;
    white-space: nowrap;
    opacity: 0.75;
}

/* Too narrow for one line. The rules come off - a divider left dangling at the end of a
   wrapped line reads as a mistake - and the address takes a line of its own. */
@media (max-width: 600px) {
    .sv-footer-seg {
        padding-right: 0;
        border-right: 0;
    }
    .sv-footer-ip {
        display: block;
        margin: 0 0 var(--sv-space-1) 0;
    }
}

/* ------------------------------------------------------------------ nav --- */

/* HeaderLinkBar renders: <span><ul id="headernav"><li><span><a>..</a></span></li></ul></span> */
#headernav {
    position: static;
    right: auto;
    top: auto;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-space-1);
    list-style: none;
    margin: 0;
    padding: 0;
}

#headernav li {
    display: block;
    float: none;
    margin: 0;
    padding: 0;
    background: none;
}

#headernav span { display: block; float: none; margin: 0; padding: 0; background: none; }

#headernav a {
    display: block;
    float: none;
    height: auto;
    line-height: 1.2;
    margin: 0;
    padding: var(--sv-space-2) var(--sv-space-3);
    border: 1px solid transparent;
    border-radius: var(--sv-radius-pill);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-base);
    font-weight: 550;
    text-transform: none;
    text-decoration: none;
    white-space: nowrap;
    background: none;
    transition: background-color var(--sv-transition), color var(--sv-transition), border-color var(--sv-transition);
}

/* Clear the old sprite backgrounds from the <li>/<span> wrappers only. Do NOT
   include "span a" here: that selector is more specific than the .current rule
   below, and it silently cancelled the amber fill - leaving near-black label
   text on dark felt, i.e. an invisible "home" link. */
#headernav li:hover,
#headernav li:hover span,
#headernav li.current,
#headernav li.current span { background: none; }

#headernav li span a:hover {
    background-color: var(--sv-surface-2);
    border-color: var(--sv-border);
    color: var(--sv-text);
    text-decoration: none;
}

#headernav li.current span a {
    background-color: var(--sv-accent);
    border-color: var(--sv-accent);
    color: var(--sv-accent-ink);
    font-weight: 650;
}
#headernav li.current span a:hover {
    background-color: var(--sv-accent-hover);
    border-color: var(--sv-accent-hover);
    color: var(--sv-accent-ink);
}

/* HeaderLinkBar's own wrapper span (CssClass="sv-nav") - push nav right */
.header .sv-nav { display: block; margin-left: auto; }

/* ------------------------------------------------------- theme toggle --- */

.sv-theme-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sv-space-2);
    width: 38px;
    height: 38px;
    flex: 0 0 auto;
    padding: 0;
    background-color: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-pill);
    box-shadow: var(--sv-shadow-1);
    color: var(--sv-text-muted);
    cursor: pointer;
    font-family: inherit;
    font-size: var(--sv-fs-md);
    line-height: 1;
    transition: background-color var(--sv-transition), color var(--sv-transition),
                border-color var(--sv-transition), transform var(--sv-transition);
}
.sv-theme-toggle:hover {
    background-color: var(--sv-surface-2);
    border-color: var(--sv-border-strong);
    color: var(--sv-accent);
    transform: translateY(-1px);
}
.sv-theme-toggle:active { transform: translateY(0); }
/* The toggle cycles Blue Baize -> Felt -> Parchment -> Blue Baize, and its icon
   names WHERE THE NEXT TAP GOES, not where you are. Baize (the default, on bare
   :root) offers the moon for Felt; Felt offers the sun for Parchment; Parchment
   offers the spade to come back to Baize. sv-theme.js keeps the label in step. */
.sv-theme-toggle .sv-icon-moon { display: block; }
.sv-theme-toggle .sv-icon-sun,
.sv-theme-toggle .sv-icon-suit { display: none; }

:root[data-theme="dark"] .sv-theme-toggle .sv-icon-moon { display: none; }
:root[data-theme="dark"] .sv-theme-toggle .sv-icon-sun  { display: block; }

:root[data-theme="light"] .sv-theme-toggle .sv-icon-moon { display: none; }
:root[data-theme="light"] .sv-theme-toggle .sv-icon-suit { display: block; }

/* ---------------------------------------------------------------- cards --- */

/* .contentblock is the workhorse content card across every page */
.contentblock,
.sv-card {
    background-color: var(--sv-surface);
    background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.02), transparent 120px);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-lg);
    box-shadow: var(--sv-shadow-1);
    color: var(--sv-text);
    margin: 0 0 var(--sv-space-4) 0;
    padding: var(--sv-space-5);
    overflow: hidden;
}
.contentblock:last-child, .sv-card:last-child { margin-bottom: 0; }
.contentblock p { padding-bottom: 0; margin-bottom: var(--sv-space-3); }
.contentblock a, .contentblock a:active { color: var(--sv-link); }
.contentblock a:visited { color: var(--sv-link-visited); }

/* felt surface - used for the calendar and chart cards */
.sv-felt {
    background-color: var(--sv-felt);
    background-image:
        radial-gradient(ellipse at 50% 0%, rgba(255, 255, 255, 0.04) 0%, transparent 70%),
        repeating-linear-gradient(45deg, var(--sv-bg-pattern) 0 3px, transparent 3px 8px);
    border: 1px solid var(--sv-border);
    box-shadow: var(--sv-shadow-2), inset 0 0 0 1px var(--sv-felt-edge);
}

.sv-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sv-space-3);
    flex-wrap: wrap;
    margin: 0 0 var(--sv-space-4) 0;
    padding-bottom: var(--sv-space-3);
    border-bottom: 1px solid var(--sv-border);
}
.sv-card-head h2, .sv-card-head h3 { margin: 0; }
.sv-card-sub { color: var(--sv-text-muted); font-size: var(--sv-fs-sm); }

.sidebarcontent {
    display: block;
    width: auto;
    margin: 0;
    padding: var(--sv-space-4);
    background-color: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    box-shadow: var(--sv-shadow-1);
    font-size: var(--sv-fs-base);
}
.sidebarcontent a {
    color: var(--sv-link);
    font-variant: normal;
    font-weight: 550;
    text-decoration: none;
}
.sidebarcontent a:hover { color: var(--sv-link-hover); text-decoration: underline; }
/* the empty 323px spacer block in Site.Master no longer needs to prop the layout */
.sidebarcontent:empty { display: none; }

.dvcontainer {
    grid-area: content;
    display: block;
    float: none;
    width: 100%;
    min-height: 0;
    margin: 0;
    padding: 0;
    background-color: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    filter: none;
    font-size: var(--sv-fs-base);
}
.dvcontainer span.valerr { color: var(--sv-danger); font-weight: 600; }
.dvcontainer table { margin: 0 auto var(--sv-space-4) auto; }

.dvoptions {
    display: flex;
    align-items: center;
    gap: var(--sv-space-3);
    flex-wrap: wrap;
    min-height: 0;
    margin: 0 0 var(--sv-space-4) 0;
    padding: var(--sv-space-3) var(--sv-space-4);
    background-color: var(--sv-surface-2);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    overflow: visible;
}
.dvoptions .title {
    float: none;
    order: -1;
    margin-right: auto;
    padding-top: 0;
    color: var(--sv-heading);
    font-size: var(--sv-fs-lg);
    font-weight: 650;
}

/* -------------------------------------------------------------- buttons --- */

.buttons { display: flex; align-items: center; gap: var(--sv-space-2); flex-wrap: wrap; }

.buttons a,
.buttons button,
.sv-btn,
input[type="submit"],
input[type="button"],
input[type="reset"] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sv-space-2);
    width: auto;
    margin: 0;
    padding: var(--sv-space-2) var(--sv-space-4);
    background-color: var(--sv-surface-2);
    background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.06), transparent);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius);
    box-shadow: var(--sv-shadow-1), var(--sv-inset);
    color: var(--sv-text);
    cursor: pointer;
    font-family: var(--sv-font);
    font-size: var(--sv-fs-base);
    font-weight: 600;
    line-height: 1.4;
    overflow: visible;
    text-align: center;
    text-decoration: none;
    white-space: nowrap;
    transition: background-color var(--sv-transition), border-color var(--sv-transition),
                color var(--sv-transition), transform var(--sv-transition),
                box-shadow var(--sv-transition);
}

.buttons a:hover,
.buttons button:hover,
.sv-btn:hover,
input[type="submit"]:hover,
input[type="button"]:hover,
input[type="reset"]:hover {
    background-color: var(--sv-surface-3);
    border-color: var(--sv-accent);
    color: var(--sv-text);
    text-decoration: none;
    transform: translateY(-1px);
    box-shadow: var(--sv-shadow-2), var(--sv-inset);
}

.buttons a:active,
.buttons button:active,
.sv-btn:active,
input[type="submit"]:active,
input[type="button"]:active {
    background-color: var(--sv-surface-3);
    border-color: var(--sv-accent);
    transform: translateY(0);
    box-shadow: var(--sv-shadow-1);
}

.buttons a[disabled], .buttons button:disabled,
input[type="submit"]:disabled, input[type="button"]:disabled, .sv-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
}

.buttons button img, .buttons a img, .sv-btn img {
    border: 0;
    width: 16px;
    height: 16px;
    margin: 0 !important;
    padding: 0;
    flex: 0 0 auto;
}

/* Framework.Web's ButtonControl renders <button><span><img/>Text</span></button>,
   so the icon and label sit inline inside that span - baseline-aligned and with
   no gap. Making the span a flex row centres the icon against the text properly. */
.buttons button > span,
.sv-btn > span {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-space-2);
    min-width: 0;
}

/* primary - amber fill, the main call to action.
   NOTE: the input[...] variants are required, not redundant. The base rule
   above matches input[type="submit"] at specificity (0,1,1), which outranks a
   bare .sv-btn-primary at (0,1,0) - so an asp:Button would silently render as
   a secondary button without these. */
.buttons a.mainbtn, .buttons button.mainbtn,
button.mainbtn, .sv-btn-primary,
input[type="submit"].mainbtn,
input[type="submit"].sv-btn-primary,
input[type="button"].sv-btn-primary {
    background-color: var(--sv-accent);
    background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.14), transparent);
    border-color: var(--sv-accent);
    color: var(--sv-accent-ink);
}
.buttons a.mainbtn:hover, .buttons button.mainbtn:hover,
button.mainbtn:hover, .sv-btn-primary:hover,
input[type="submit"].mainbtn:hover,
input[type="submit"].sv-btn-primary:hover,
input[type="button"].sv-btn-primary:hover {
    background-color: var(--sv-accent-hover);
    border-color: var(--sv-accent-hover);
    color: var(--sv-accent-ink);
}

/* destructive */
.buttons a.negative, .buttons button.negative,
button.negative, .sv-btn-danger {
    background-color: var(--sv-danger-soft);
    border-color: var(--sv-danger);
    color: var(--sv-danger);
}
.buttons a.negative:hover, .buttons button.negative:hover,
button.negative:hover, .sv-btn-danger:hover {
    background-color: var(--sv-danger);
    border-color: var(--sv-danger);
    color: var(--sv-accent-ink);
}

/* A .sv-btn is a button that happens to be an <a>, and inside a content block it was losing
   its colour to the prose link rule: ".contentblock a" scores (0,2,1) against ".sv-btn-primary"
   at (0,1,0), so the main call to action rendered in sky blue lettering on the amber fill and
   the plain ones came out link-coloured too. The link rule is right for prose, so rather than
   weakening it, the four button colours are restated here at a specificity that wins. Anything
   that adds a button variant above has to add it here as well. */
.contentblock a.sv-btn,
.contentblock a.sv-btn:active {
    color: var(--sv-text);
}
.contentblock a.sv-btn-primary,
.contentblock a.sv-btn-primary:hover,
.contentblock a.sv-btn-primary:active {
    color: var(--sv-accent-ink);
}
.contentblock a.sv-btn-danger,
.contentblock a.sv-btn-danger:active {
    color: var(--sv-danger);
}
.contentblock a.sv-btn-danger:hover {
    color: var(--sv-accent-ink);
}
.contentblock a.sv-btn-quiet,
.contentblock a.sv-btn-quiet:active {
    color: var(--sv-link);
}

/* quiet / secondary */
.buttons a.regular, .buttons button.regular, button.regular, .sv-btn-quiet {
    background: none;
    border-color: transparent;
    box-shadow: none;
    color: var(--sv-link);
}
.buttons a.regular:hover, .buttons button.regular:hover,
button.regular:hover, .sv-btn-quiet:hover {
    background-color: var(--sv-surface-2);
    border-color: var(--sv-border);
    box-shadow: none;
}

/* wide attendance buttons on Event.aspx - let them breathe instead of 325px */
.buttons button.eventattendance {
    width: 100%;
    max-width: 340px;
    justify-content: flex-start;
    padding: var(--sv-space-3) var(--sv-space-4);

    /* These carry a whole sentence, not a word. The base button rule says white-space:nowrap,
       which is right for "Save" and wrong for 'Change to: "I am willing to host, but only if
       absolutely necessary."' - and with overflow:visible alongside it, the sentence ran straight
       out through the right-hand border instead of wrapping or being clipped. */
    white-space: normal;
}

/* The icon keeps its size while the sentence beside it wraps - without this the flex row shrinks
   the image first, because it is the item with something to give. */
.buttons button.eventattendance img { flex: 0 0 auto; }

.javaimagelink {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-space-2);
    margin-top: var(--sv-space-2);
    padding: var(--sv-space-2) var(--sv-space-3);
    background-color: var(--sv-surface-2);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius);
    color: var(--sv-text);
    cursor: pointer;
    font-weight: 600;
    text-decoration: none;
    transition: background-color var(--sv-transition), border-color var(--sv-transition);
}
.javaimagelink:hover { background-color: var(--sv-surface-3); border-color: var(--sv-accent); text-decoration: none; }

/* ---------------------------------------------------------------- chips --- */

.sv-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-space-1);
    padding: 2px var(--sv-space-2);
    border: 1px solid transparent;
    border-radius: var(--sv-radius-pill);
    font-size: var(--sv-fs-xs);
    font-weight: 650;
    line-height: 1.5;
    white-space: nowrap;
}
.sv-chip-yes       { background: var(--sv-success-soft); border-color: var(--sv-success); color: var(--sv-success); }
.sv-chip-maybe     { background: var(--sv-warn-soft);    border-color: var(--sv-warn);    color: var(--sv-warn); }
.sv-chip-no        { background: var(--sv-danger-soft);  border-color: var(--sv-danger);  color: var(--sv-danger); }
.sv-chip-undecided { background: var(--sv-surface-3);    border-color: var(--sv-border-strong); color: var(--sv-text-muted); }
.sv-chip-attended  { background: var(--sv-info-soft);    border-color: var(--sv-info);    color: var(--sv-info); }
.sv-chip-full      { background: var(--sv-accent-soft);  border-color: var(--sv-accent);  color: var(--sv-accent); }

/* --------------------------------------------------------------- tables --- */

table.quicktable {
    width: 100%;
    border: 1px solid var(--sv-border);
    border-collapse: separate;
    border-spacing: 0;
    border-radius: var(--sv-radius);
    background-color: var(--sv-surface);
    box-shadow: var(--sv-shadow-1);
    font-size: var(--sv-fs-sm);
    margin-bottom: var(--sv-space-4);
    overflow: hidden;
}

table.quicktable th {
    position: sticky;
    top: 0;
    z-index: 1;
    padding: var(--sv-space-3);
    background-color: var(--sv-surface-3);
    border: 0;
    border-bottom: 1px solid var(--sv-border-strong);
    color: var(--sv-heading);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-align: left;
    text-transform: uppercase;
    white-space: nowrap;
}

table.quicktable td.even,
table.quicktable td.odd,
table.quicktable td {
    padding: var(--sv-space-2) var(--sv-space-3);
    border: 0;
    border-bottom: 1px solid var(--sv-border);
    background-color: transparent;
    vertical-align: middle;
}
table.quicktable td.even { background-color: var(--sv-surface-2); }
table.quicktable tr:last-child td { border-bottom: 0; }
table.quicktable tbody tr:hover td,
table.quicktable tr:hover td.even,
table.quicktable tr:hover td.odd { background-color: var(--sv-accent-soft); }

table.quicktable a { color: var(--sv-link); text-decoration: none; }
table.quicktable a:hover { text-decoration: underline; }

table.quicktable td.title {
    padding: var(--sv-space-3) var(--sv-space-4);
    background-color: var(--sv-surface-3);
    border-bottom: 1px solid var(--sv-border-strong);
}
table.quicktable td.title li {
    display: inline;
    list-style: none;
    color: var(--sv-heading);
    font-size: var(--sv-fs-md);
    font-variant: normal;
    font-weight: 650;
}
table.quicktable td.title li a { float: right; font-variant: normal; font-weight: 600; }

table.quicktable td.nodata {
    padding: var(--sv-space-4);
    background-color: var(--sv-surface-2);
    border-top: 1px solid var(--sv-border);
    color: var(--sv-text-muted);
    font-style: italic;
    text-align: center;
}

/* Numeric columns read better right-aligned and tabular. Applied by wrapper
   class rather than a per-cell class, because QuickTable only stripes cells
   whose CssClass is empty - see Stats.aspx.cs. First column is the name. */
.sv-stats-table table.quicktable td:not(:first-child),
.sv-stats-table table.quicktable th:not(:first-child) {
    text-align: right;
    font-variant-numeric: tabular-nums;
}
table.quicktable td.sv-num, table.quicktable th.sv-num { text-align: right; font-variant-numeric: tabular-nums; }
.sv-money-pos { color: var(--sv-success); font-weight: 650; font-variant-numeric: tabular-nums; }
.sv-money-neg { color: var(--sv-danger);  font-weight: 650; font-variant-numeric: tabular-nums; }

/* Completed-event results table, sharing a card with the winnings chart.
   Headers are allowed to wrap here (the global th rule is nowrap), which with
   the explicit column widths keeps the table inside its card instead of
   overflowing into a horizontal scrollbar. */
/* chart beside the table; stacks on narrower viewports */
.sv-results-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--sv-space-5);
    align-items: start;
}
.sv-results-split > .sv-results-table { margin-top: 0; }

@media (max-width: 1000px) {
    .sv-results-split { grid-template-columns: minmax(0, 1fr); }
}

.sv-results-table { margin-top: var(--sv-space-4); }
.sv-results-table table.quicktable { margin-bottom: 0; }
.sv-results-table table.quicktable th { white-space: normal; }
.sv-results-table table.quicktable td:not(:first-child),
.sv-results-table table.quicktable th:not(:first-child) {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* Event.aspx completed-event result rows. Data attribute rather than a class
   so QuickTable's even/odd striping still applies underneath (it only stripes
   cells whose CssClass is empty). */
table.quicktable td[data-sv-result="win"]  { background-color: var(--sv-success-soft); color: var(--sv-text); }
table.quicktable td[data-sv-result="loss"] { background-color: var(--sv-danger-soft);  color: var(--sv-text); }
table.quicktable td[data-sv-result="even"] { background-color: var(--sv-warn-soft);    color: var(--sv-text); }
table.quicktable tr:hover td[data-sv-result] { filter: brightness(1.12); }
/* the Net Winnings column is the last cell - make the number carry the weight */
table.quicktable td[data-sv-result]:last-child { font-weight: 650; font-variant-numeric: tabular-nums; text-align: right; }

/* any wide table can scroll inside its card rather than blowing out the page */
.sv-table-scroll { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* --------------------------------------------------- live scoring grid --- */

/* Counting buy-ins and game powers during a game, then settling up. Rows rather
   than a table, matching the app's stats screen - which is a list of rows with a
   -/+ group per counter, not a data grid.

   Rendered by EventScorer.aspx's Vue template. Also the read-only stats table's
   companion, so keep the class names stable.

   How many counter columns there are depends on which game powers the event
   switched on, so the count goes on .sv-score as a modifier and the template
   it sets is inherited by the header and every row. Keeping one template for
   both is what stops the counters drifting out from under their headings - the
   same constraint the app's layouts call out. */
/* Counter tracks are minmax(0, N) rather than a flat N so they COMPRESS when the row runs
   out of room instead of pushing the grid wider than the card. A fixed track overflows, and
   with three game powers switched on that meant a horizontal scrollbar on a phone. */
.sv-score   { --sv-score-grid: minmax(0, 1fr) minmax(0, 108px); }
.sv-score-1 { --sv-score-grid: minmax(0, 1fr) minmax(0, 108px); }
.sv-score-2 { --sv-score-grid: minmax(0, 1fr) repeat(2, minmax(0, 108px)); }
.sv-score-3 { --sv-score-grid: minmax(0, 1fr) repeat(3, minmax(0, 108px)); }

.sv-score-head,
.sv-score-row {
    display: grid;
    grid-template-columns: var(--sv-score-grid);
    align-items: center;
    gap: var(--sv-space-2);
}

.sv-score-head {
    padding: var(--sv-space-2) var(--sv-space-1) var(--sv-space-3);
    border-bottom: 1px solid var(--sv-border-strong);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.sv-score-row {
    position: relative;
    padding: var(--sv-space-2) var(--sv-space-1);
    border-bottom: 1px solid var(--sv-border);
}
.sv-score-row:last-child { border-bottom: 0; }

/* Each row carries a kebab menu at its right edge. It lives in extra right padding rather
   than a grid track, so the counter columns and their headings are untouched on every
   layout - the header gets the same padding so the columns still line up. */
.sv-score-head,
.sv-score-row { padding-right: 40px; }

/* Centred with flex, not translateY: a transform would start a stacking context and trap
   anything positioned inside it beneath the rows that follow. */
.sv-row-menu {
    position: absolute;
    top: 0;
    right: 2px;
    bottom: 0;
    display: flex;
    align-items: center;
}
.sv-kebab {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    color: var(--sv-text-muted);
    background: transparent;
    border: 1px solid transparent;
    border-radius: 50%;
    font-size: var(--sv-fs-lg);
    line-height: 1;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.sv-kebab:hover,
.sv-kebab[aria-expanded="true"] {
    color: var(--sv-text);
    background: var(--sv-surface-2);
    border-color: var(--sv-border-strong);
}
/* Drawn once at the app root, position:fixed at the button's viewport rectangle (set from
   script), so neither the grid's scroll container nor a following row can clip or cover it. */
.sv-menu {
    position: fixed;
    z-index: 45;
    min-width: 160px;
    padding: var(--sv-space-1);
    background: var(--sv-surface);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius-sm);
    box-shadow: var(--sv-shadow-1);
}
.sv-menu a {
    display: block;
    padding: var(--sv-space-2) var(--sv-space-3);
    border-radius: var(--sv-radius-sm);
    color: var(--sv-text);
    font-size: var(--sv-fs-sm);
    white-space: nowrap;
}
.sv-menu a:hover {
    background: var(--sv-surface-2);
    color: var(--sv-text);
    text-decoration: none;
}
/* invisible; exists so a click anywhere else closes the open menu */
.sv-menu-backdrop {
    position: fixed;
    inset: 0;
    z-index: 40;
}

/* min-width:0 on both cells is what lets the tracks above actually shrink - a grid item
   defaults to min-width:auto, which refuses to go below its content and reintroduces the
   overflow the minmax() was added to prevent. */
.sv-score-name {
    min-width: 0;
    overflow: hidden;
    color: var(--sv-text);
    font-size: var(--sv-fs-md);
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sv-score-head .sv-score-col { min-width: 0; text-align: center; }

/* the "Position / Player" heading is a heading like the counter ones, not a name */
.sv-score-head .sv-score-name { color: inherit; font-size: inherit; }

/* the seat the game was dealt in: a small pill in the stepper's colours, so the number reads
   as a label on the row rather than part of the name */
.sv-score-seat {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    margin-right: var(--sv-space-2);
    padding: 0 6px;
    background: var(--sv-surface-2);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius-pill);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 650;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    vertical-align: middle;
}

/* -------- stepper -------- */

.sv-stepper {
    display: flex;
    min-width: 0;
    align-items: center;
    justify-content: space-between;
    padding: 2px;
    background: var(--sv-surface-2);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius-pill);
}

.sv-stepper .sv-step {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    color: var(--sv-text);
    background: var(--sv-surface);
    border: 1px solid var(--sv-border-strong);
    border-radius: 50%;
    font-size: var(--sv-fs-lg);
    font-weight: 700;
    line-height: 1;
    text-align: center;
    text-decoration: none;
    transition: var(--sv-transition);
    -webkit-tap-highlight-color: transparent;
}
.sv-stepper .sv-step:hover {
    color: var(--sv-accent-ink);
    background: var(--sv-accent);
    border-color: var(--sv-accent);
    text-decoration: none;
}
.sv-stepper .sv-step:active { transform: scale(0.92); }

/* At a limit the arrow greys out rather than disappearing, so the row doesn't
   reflow under someone's thumb mid-tap. It still posts back to nothing, so the
   cap is enforced on the server either way - this is only the tell. */
.sv-stepper .sv-step.is-off {
    color: var(--sv-text-faint);
    background: var(--sv-surface-2);
    border-color: var(--sv-border);
    cursor: default;
    opacity: 0.45;
}
.sv-stepper .sv-step.is-off:hover {
    color: var(--sv-text-faint);
    background: var(--sv-surface-2);
    border-color: var(--sv-border);
}

.sv-stepper-value {
    flex: 1 1 auto;
    color: var(--sv-text);
    font-size: var(--sv-fs-md);
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    text-align: center;
}

/* Read-only rows - watching from the event page, or scoring lost to a fault - show the number
   alone, at the height the arrows would have given the pill. */
.sv-stepper.is-readonly { justify-content: center; min-height: 34px; }

/* -------- cashing out -------- */

/* Name and running net on top, the amount handed back underneath - two lines,
   as the app's cash-out row is, because a money field wide enough to type into
   doesn't fit beside a name on a phone. */
.sv-cash-row {
    padding: var(--sv-space-3) var(--sv-space-1);
    border-bottom: 1px solid var(--sv-border);
}
.sv-cash-row:last-child { border-bottom: 0; }

.sv-cash-top {
    display: flex;
    align-items: baseline;
    gap: var(--sv-space-2);
}
.sv-cash-top .sv-score-name { flex: 1 1 auto; }

.sv-cash-net {
    flex: 0 0 auto;
    font-size: var(--sv-fs-md);
    font-variant-numeric: tabular-nums;
    font-weight: 650;
}
.sv-cash-net-even { color: var(--sv-text-muted); }

.sv-cash-entry {
    display: flex;
    align-items: center;
    gap: var(--sv-space-2);
    margin-top: var(--sv-space-2);
}
.sv-cash-entry input[type="number"],
.sv-cash-entry input[type="text"] {
    width: auto;
    flex: 1 1 auto;
    max-width: 180px;
}

.sv-cash-clear {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    padding: 0;
    color: var(--sv-text-muted);
    background: var(--sv-surface-2);
    border: 1px solid var(--sv-border-strong);
    border-radius: 50%;
    font-size: var(--sv-fs-md);
    font-weight: 700;
    line-height: 1;
    text-align: center;
    text-decoration: none;
}
.sv-cash-clear:hover {
    color: var(--sv-danger);
    border-color: var(--sv-danger);
    text-decoration: none;
}

/* -------- picking players to seat -------- */

.sv-pick-head {
    display: flex;
    align-items: center;
    gap: var(--sv-space-3);
    flex-wrap: wrap;
    margin-top: var(--sv-space-3);
}

/* Every member and guest, which is forty-odd names - so it wraps into as many
   columns as fit and scrolls inside its own box, the page's answer to the app's
   scrollable picker dialog. Grid rather than CSS columns: with a capped height,
   multi-column layout overflows sideways instead of scrolling. */
ul.sv-pick-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: var(--sv-space-1) var(--sv-space-4);
    max-height: 300px;
    overflow-y: auto;
    padding: var(--sv-space-3);
    margin: var(--sv-space-2) 0 0;
    list-style: none;
    background: var(--sv-surface-2);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
}
ul.sv-pick-list li {
    display: flex;
    align-items: center;
    gap: var(--sv-space-2);
    min-width: 0;
    padding: 2px 0;
}
ul.sv-pick-list input[type="checkbox"] {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    margin: 0;
}
ul.sv-pick-list label {
    overflow: hidden;
    color: var(--sv-text);
    cursor: pointer;
    font-size: var(--sv-fs-sm);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* -------- running totals -------- */

.sv-score-totals {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-space-6);
    padding-top: var(--sv-space-4);
    margin-top: var(--sv-space-3);
    border-top: 1px solid var(--sv-border-strong);
}

.sv-total-label {
    display: block;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.sv-total-value {
    display: block;
    margin-top: 2px;
    color: var(--sv-accent);
    font-family: var(--sv-font-display);
    font-size: var(--sv-fs-xl);
    font-variant-numeric: tabular-nums;
    font-weight: 700;
}
.sv-total-note {
    margin-top: 2px;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
}
.sv-total-settled  { color: var(--sv-success); }
.sv-total-outstanding { color: var(--sv-warn); }

/* -------- knockouts -------- */

/* In the name cell, after the name: a KO count for anyone who has taken someone out
   tonight, and an Out chip once they are gone for the night. Both are state, never
   actions - the actions live in the sheet. Inline so the name cell's ellipsis still
   governs the row. */
.sv-ko-badge,
.sv-out-badge {
    display: inline-block;
    margin-left: var(--sv-space-2);
    padding: 0 7px;
    border-radius: var(--sv-radius-pill);
    font-size: var(--sv-fs-xs);
    font-weight: 650;
    line-height: 1.5;
    /* baseline, not middle: the pill's text sits on the name's baseline, so the pill's
       bottom lines up with the bottom of the name instead of hanging below it */
    vertical-align: baseline;
}
.sv-ko-badge {
    color: var(--sv-accent);
    background: var(--sv-accent-soft);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
/* the glove (drawn in sv-scorer.js) takes the badge's text colour, so it follows the theme */
.sv-ko-glove {
    display: inline-block;   /* the global reset makes svg block-level, which put it on its own line */
    width: 14px;
    height: 14px;
    margin-right: 4px;
    vertical-align: -2px;
    fill: currentColor;
    transform: rotate(45deg);   /* punching up and to the right, cuff trailing bottom-left */
    transform-origin: 50% 50%;
}
.sv-out-badge {
    color: var(--sv-text-muted);
    background: var(--sv-surface-2);
    border: 1px solid var(--sv-border);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
/* The "who took X out?" sheet: bottom-anchored on a phone, centred where there is room.
   The names are the buttons; one tap records a re-buy. Leaving is not asked here: that is
   a $0 cash-out, see .sv-cash-bust. */
.sv-sheet {
    position: fixed;
    inset: 0;
    z-index: 50;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    padding: var(--sv-space-4);
    background: rgba(0, 0, 0, 0.45);
}
.sv-sheet-panel {
    width: 100%;
    max-width: 520px;
    padding: var(--sv-space-4);
    background: var(--sv-surface);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius-sm);
    box-shadow: var(--sv-shadow-1);
}
.sv-sheet-title { margin-bottom: var(--sv-space-2); }
.sv-sheet-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: var(--sv-space-2);
}
.sv-sheet-choice { justify-content: center; text-align: center; }
@media (min-width: 621px) {
    .sv-sheet { align-items: center; }
}

/* Cash-out at $0 for somebody with buy-ins is their bust; the row grows a "knocked out by"
   picker under the amount. Label and select on one line, the select sized to its content
   rather than the global full-width form rule. */
.sv-cash-bust {
    display: flex;
    align-items: center;
    gap: var(--sv-space-2);
    margin-top: var(--sv-space-2);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
}
.sv-cash-bust label { flex: 0 0 auto; }
.sv-cash-bust select {
    width: auto;
    flex: 1 1 auto;
    max-width: 260px;
    padding: var(--sv-space-1) var(--sv-space-2);
    font-size: var(--sv-fs-sm);
}

/* Tonight's knockouts under the totals, oldest first. Quiet text, tabular times. */
.sv-ko-strip {
    margin-top: var(--sv-space-4);
    padding-top: var(--sv-space-3);
    border-top: 1px solid var(--sv-border);
}

/* On a finished game's recap the strip is the only thing in the card - there are no counters
   or totals above it - so the rule that separates it from them has nothing to separate and
   reads as a stray line under the heading. */
.sv-recap .sv-ko-strip {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}
.sv-ko-item {
    display: flex;
    align-items: baseline;
    gap: var(--sv-space-3);
    padding: var(--sv-space-1) 0;
    font-size: var(--sv-fs-sm);
}
.sv-ko-time {
    flex: 0 0 auto;
    min-width: 4.5em;
    color: var(--sv-text-faint);
    font-size: var(--sv-fs-xs);
    font-variant-numeric: tabular-nums;
}
.sv-ko-text { flex: 1 1 auto; min-width: 0; }

/* A line that carries photographs. The strip goes UNDER the sentence rather than beside it: a
   thumbnail in the row would take a third of a phone's width and leave "Paul out by Baja" wrapping
   one word per line, which is the same mistake the app's play-by-play made with a long trailer. */
.sv-ko-has-photos { flex-wrap: wrap; }

.sv-ko-photos {
    flex: 0 0 100%;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-space-2);

    /* Indented past the clock, so the pictures read as belonging to the line above them and not
       to the column of times. 4.5em is .sv-ko-time's own min-width. */
    margin: var(--sv-space-2) 0 var(--sv-space-1) calc(4.5em + var(--sv-space-3));
}

.sv-ko-photo {
    display: block;
    line-height: 0;
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    overflow: hidden;
}

.sv-ko-photo:hover { border-color: var(--sv-accent-ring); }

/* Square and cropped, so a row of them is even however they were shot - portrait off a phone
   beside landscape off a camera. The full picture is one click away. */
.sv-ko-photo img {
    display: block;
    width: 108px;
    height: 108px;
    object-fit: cover;
}

@media (max-width: 560px) {
    /* The clock indent is worth more than the alignment once the screen is this narrow. */
    .sv-ko-photos { margin-left: 0; }
}
.sv-ko-kind {
    margin-left: var(--sv-space-2);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
}
.sv-ko-actions {
    display: flex;
    flex: 0 0 auto;
    gap: var(--sv-space-3);
    font-size: var(--sv-fs-xs);
    font-weight: 650;
}

/* Narrow phones: the counters give first, and the more of them there are the narrower each
   one goes. The name keeps a floor - shrinking it to fit is worse than either alternative,
   because a column of "1..." "2..." tells the scorekeeper nothing. */
@media (max-width: 560px) {
    .sv-score-1 { --sv-score-grid: minmax(96px, 1fr) minmax(0, 100px); }
    .sv-score-2 { --sv-score-grid: minmax(96px, 1fr) repeat(2, minmax(0, 88px)); }
    .sv-score-3 { --sv-score-grid: minmax(96px, 1fr) repeat(3, minmax(0, 84px)); }
    .sv-score-head, .sv-score-row { gap: var(--sv-space-1); }
    .sv-stepper { padding: 1px; }
    .sv-stepper .sv-step { width: 26px; height: 26px; }
}

/* Below here a readable name and three tappable counters cannot share a line, so the row
   stacks instead: name on its own line, counters underneath. That is the app's shape on a
   phone, and it beats both of the alternatives - a horizontal scrollbar, or a name column
   squeezed down to an ellipsis.

   Works on the existing markup, which is why both pages get it for free: the name is pushed
   to a full-width row and the steppers flow into the next one. The header drops "Player",
   which a full-width name line does not need, and keeps the counter labels above their
   columns. */
@media (max-width: 470px) {
    .sv-score-3 .sv-score-head,
    .sv-score-3 .sv-score-row { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .sv-score-3 .sv-score-row .sv-score-name { grid-column: 1 / -1; white-space: normal; }
    .sv-score-3 .sv-score-head .sv-score-name { display: none; }
    .sv-score-3 .sv-stepper { max-width: 132px; }
}

@media (max-width: 400px) {
    .sv-score-2 .sv-score-head,
    .sv-score-2 .sv-score-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .sv-score-2 .sv-score-row .sv-score-name { grid-column: 1 / -1; white-space: normal; }
    .sv-score-2 .sv-score-head .sv-score-name { display: none; }
    .sv-score-2 .sv-stepper { max-width: 132px; }
    .sv-stepper .sv-step { width: 24px; height: 24px; font-size: var(--sv-fs-md); }
    .sv-stepper-value { font-size: var(--sv-fs-sm); }
}

/* ---------------------------------------------------------------- forms --- */

input[type="text"], input[type="password"], input[type="email"], input[type="number"],
input[type="search"], input[type="tel"], input[type="url"], input[type="date"],
select, textarea {
    width: 100%;
    max-width: 100%;
    padding: var(--sv-space-2) var(--sv-space-3);
    background-color: var(--sv-surface);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius-sm);
    color: var(--sv-text);
    font-family: var(--sv-font);
    font-size: var(--sv-fs-base);
    line-height: 1.4;
    transition: border-color var(--sv-transition), box-shadow var(--sv-transition);
}
input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus,
input[type="number"]:focus, select:focus, textarea:focus {
    border-color: var(--sv-accent);
    box-shadow: 0 0 0 3px var(--sv-accent-ring);
    outline: none;
}
input::placeholder, textarea::placeholder { color: var(--sv-text-faint); }
textarea { min-height: 120px; resize: vertical; }
input[type="checkbox"], input[type="radio"] { accent-color: var(--sv-accent); width: auto; }
label { color: var(--sv-text-muted); font-weight: 550; }

/* ul.dv is the Framework detail-view form grid */
ul.dv {
    list-style: none;
    width: 100%;
    max-width: 640px;
    margin: 0 auto;
    padding: 0;
}
ul.dv li {
    display: grid;
    grid-template-columns: 160px minmax(0, 1fr);
    align-items: center;
    gap: var(--sv-space-3);
    width: auto;
    height: auto;
    min-height: 40px;
    list-style: none;
    padding: var(--sv-space-1) 0;
}
ul.dv li span {
    float: none;
    width: auto;
    padding-top: 0;
    color: var(--sv-text-muted);
    font-weight: 550;
    list-style: none;
}
ul.dv li input, ul.dv li select { float: none; width: 100%; font-family: var(--sv-font); font-size: var(--sv-fs-base); }
ul.dv li input[type="checkbox"] {
    width: 16px; height: 16px; margin: 0; padding: 0;
    position: static; top: auto; vertical-align: middle; justify-self: start;
}
ul.dv li img { margin-top: 0; }

/* A note under a field. ul.dv li is a two column grid, so the note takes a row of
   its own across both rather than squeezing in beside the input. Both of these beat
   the "ul.dv li span" label rule above on specificity, which is why neither needs
   an !important to stop reading as a label. */
ul.dv li .sv-fieldnote {
    grid-column: 1 / -1;
    color: var(--sv-text-faint);
    font-size: var(--sv-fs-xs);
    font-weight: 400;
}
/* Plain text, or a link, sitting where a field would be. It has to be one element: every
   child of ul.dv li is a grid item, so loose text around a link lands on rows of its own.
   Two classes beat the label rule above, which is why it does not read as a label. */
ul.dv li .sv-dv-value {
    color: var(--sv-text);
    font-weight: 400;
}
ul.dv li .sv-logo-empty {
    color: var(--sv-text-faint);
    font-size: var(--sv-fs-xs);
    font-weight: 400;
}

/* The logo slot on the club editor. Each variant previews on the kind of background
   it is for, because the whole reason a club has two is that one of them is
   unreadable on the other, and that has to be visible before it ships to a header. */
.sv-logo-slot {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-space-3);
    min-height: 52px;
    margin: var(--sv-space-2) 0;
}
.sv-logo-preview {
    max-height: 48px;
    max-width: 260px;
    padding: var(--sv-space-2);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
}
.sv-logo-preview-light { background: #F4F1E8; }
.sv-logo-preview-dark  { background: #12202C; }

.sv-editclub-message,
.sv-flag-message {
    max-width: 640px;
    margin: var(--sv-space-3) auto;
    padding: var(--sv-space-3);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius);
    background: var(--sv-surface-2);
    color: var(--sv-text);
    font-size: var(--sv-fs-sm);
}

/* The admin editors that carry a standing explanation rather than a one-off message:
   what a system flag is for, and what setting it wrongly costs. Quieter than a message
   because it is always there - it is the page telling you where you are, not the page
   answering something you just did. */
.sv-flag-about {
    max-width: 640px;
    margin: var(--sv-space-3) auto;
    padding: var(--sv-space-3);
    border-left: 3px solid var(--sv-border-strong);
    background: var(--sv-surface-2);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    line-height: 1.5;
}
.sv-flag-about b { color: var(--sv-text); font-weight: 650; }
.sv-flag-about code,
ul.dv li .sv-fieldnote code {
    padding: 0 3px;
    border-radius: 3px;
    background: var(--sv-surface-3, var(--sv-surface-2));
    font-family: var(--sv-font-mono, ui-monospace, Consolas, monospace);
    font-size: 0.95em;
}

/* The tick that has to be given before a change that can shut everybody out. Sits below
   the fields, not among them, because it is not one of the flag's values. */
.sv-flag-confirm {
    max-width: 640px;
    margin: var(--sv-space-3) auto;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
}
.sv-flag-confirm input[type="checkbox"] { width: 16px; height: 16px; margin-right: var(--sv-space-2); vertical-align: middle; }

/* The primary key, shown and never edited. */
ul.dv li .sv-flag-code {
    color: var(--sv-text);
    font-family: var(--sv-font-mono, ui-monospace, Consolas, monospace);
    font-weight: 400;
}

/* ---------------------------------------------- profile pictures (the chip) --- */

/* The stored image is a plain square. The chip is drawn around it here, so the theme can
   change without re-cropping anybody, and a browser that does not do conic gradients gets
   a plain amber ring instead of nothing. */
/* The chip itself is defined once, up with the account pill that shares it. Only the
   surrounding layout for My Account lives here. */

.sv-avatar-row {
    display: flex;
    align-items: flex-start;
    gap: var(--sv-space-5);
    max-width: 640px;
    margin: var(--sv-space-4) auto var(--sv-space-5);
}
.sv-avatar-actions { flex: 1 1 auto; min-width: 0; }
.sv-avatar-hint { margin: 0 0 var(--sv-space-3); color: var(--sv-text-muted); font-size: var(--sv-fs-sm); }
/* Off screen, not gone: the label beside it is what people see and click, and display:none
   would take the input out of the tab order and stop the label opening it. The cropper finds it
   by client id, so how it looks is nobody else's business. */
.sv-avatar-file {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

.sv-avatar-actions .sv-photo-pick { margin-bottom: var(--sv-space-3); }

/* The input's own focus ring is off screen with it. */
.sv-avatar-actions:focus-within .sv-photo-pick {
    border-color: var(--sv-accent-ring);
    box-shadow: 0 0 0 3px var(--sv-accent-soft);
}
.sv-avatar-buttons { display: flex; flex-wrap: wrap; gap: var(--sv-space-3); }

/* ------------------------------------------------------- choosing the square --- */

.sv-crop {
    max-width: 640px;
    margin: 0 auto var(--sv-space-5);
    padding: var(--sv-space-4);
    background: var(--sv-surface-2);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
}

/* Square, and the photo is positioned inside it in script. overflow hidden is what makes
   the stage the crop: whatever is outside it is what gets cut. */
.sv-crop-stage {
    position: relative;
    width: 260px;
    height: 260px;
    max-width: 100%;
    margin: 0 auto;
    overflow: hidden;
    background: var(--sv-surface-3);
    border-radius: var(--sv-radius-sm);
    cursor: grab;
    touch-action: none;
    user-select: none;
}
.sv-crop-stage:active { cursor: grabbing; }
.sv-crop-img { position: absolute; max-width: none; user-select: none; -webkit-user-drag: none; }

/* Shows the member the chip they are aiming at: everything outside the circle is dimmed,
   and the rim is drawn in the same amber as the finished picture. Never catches the
   pointer - the drag belongs to the stage underneath. */
.sv-crop-mask {
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: var(--sv-radius-sm);
    box-shadow: inset 0 0 0 999px rgba(6, 12, 20, 0.55);
    -webkit-mask: radial-gradient(circle at 50% 50%, transparent 0 49%, #000 49%);
            mask: radial-gradient(circle at 50% 50%, transparent 0 49%, #000 49%);
}
.sv-crop-mask::after {
    content: "";
    position: absolute;
    inset: 1%;
    border: 2px dashed var(--sv-accent);
    border-radius: 50%;
    opacity: 0.85;
}

.sv-crop-zoom-row {
    display: flex;
    align-items: center;
    gap: var(--sv-space-3);
    max-width: 260px;
    margin: var(--sv-space-4) auto 0;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
}
.sv-crop-zoom { flex: 1 1 auto; width: auto; }
.sv-crop-hint { margin: var(--sv-space-2) 0 0; color: var(--sv-text-faint); font-size: var(--sv-fs-xs); text-align: center; }

/* A member's standing, said under their name rather than in columns of its own. Two
   columns holding "Yes" twice and blanks everywhere else cost a quarter of the table's
   width to say nothing about almost everybody; a pill appears only when there is
   something to say, and disappears the rest of the time. */
/* Chip, then name with its pills beneath. The chip does not shrink; the name column does,
   which is what keeps a long address from squeezing a face into an oval. */
.sv-roster-cell { display: flex; align-items: center; gap: var(--sv-space-3); }
.sv-roster-who { min-width: 0; }

/* The whole group - face and name - is the link into the editor when there is one, so the
   anchor takes the flex layout and the text colour, and only the name is dressed as a link.
   Painting the pills and the chip in link blue made the row read as three links. The chip
   answers the hover on its own, with the ring the buttons use. */
a.sv-roster-cell,
a.sv-roster-cell:visited { color: inherit; text-decoration: none; }
a.sv-roster-cell .sv-roster-name { color: var(--sv-link); }
a.sv-roster-cell:hover { text-decoration: none; }
a.sv-roster-cell:hover .sv-roster-name { color: var(--sv-link-hover); text-decoration: underline; }
a.sv-roster-cell:hover > .sv-avatar,
a.sv-roster-cell:focus-visible > .sv-avatar { box-shadow: 0 0 0 3px var(--sv-accent-soft); }
.sv-roster-name { display: block; }
.sv-roster-tags { display: block; margin-top: 3px; line-height: 1; }
.sv-roster-pill {
    display: inline-block;
    margin-right: var(--sv-space-2);
    padding: 2px 8px;
    border: 1px solid var(--sv-accent);
    border-radius: var(--sv-radius-pill);
    color: var(--sv-accent);
    font-size: var(--sv-fs-xs);
    font-weight: 600;
    white-space: nowrap;
}
/* Locked out is not a distinction, it is a problem, so it reads as one. */
.sv-roster-pill-locked { border-color: var(--sv-danger); color: var(--sv-danger); }

/* These sit on a span inside the cell, never on the cell. QuickTable stripes a table by
   writing "odd" or "even" into each cell's CssClass and only where that class is empty,
   so a cell classed from the code-behind drops out of the striping - which showed up as
   a members list banded across the first three columns and flat across the rest. */
.sv-num-plain { display: block; text-align: right; font-variant-numeric: tabular-nums; }

/* A date in a listing, with "how long ago" underneath it. The date answers when; the phrase
   answers how long, which is the question actually being asked of a members list and is
   arithmetic the reader should not have to do from 2024-11-03. */
.sv-when { display: block; white-space: nowrap; line-height: 1.25; }
.sv-when-date { display: block; }
.sv-when-ago  { display: block; color: var(--sv-text-faint); font-size: var(--sv-fs-xs); }
.sv-when-never { color: var(--sv-text-faint); }

/* Fields the editor reports rather than offers: set elsewhere, shown here so the answer
   to "why can't they sign in?" is on the same page as the things that can change it. */
ul.dv.sv-readonly { color: var(--sv-text-muted); font-size: var(--sv-fs-sm); }
ul.dv.sv-readonly li { min-height: 28px; }
ul.dv + ul.dv { margin-top: var(--sv-space-4); padding-top: var(--sv-space-3); border-top: 1px solid var(--sv-border); }

ul.dv li.wide, ul.dv li.twoline, ul.dv li.htmleditor {
    display: block;
    height: auto;
    margin-top: var(--sv-space-3);
}
ul.dv li.wide > span, ul.dv li.twoline > span {
    display: block;
    float: none;
    width: 100%;
    padding-top: 0;
    margin-bottom: var(--sv-space-1);
}
ul.dv li.wide textarea, ul.dv li.twoline textarea {
    display: block;
    float: none;
    width: 100%;
    height: auto;
    min-height: 200px;
    padding: var(--sv-space-3);
    font-family: var(--sv-font);
    font-size: var(--sv-fs-base);
}
ul.dv li.wide input.stylebutton, ul.dv li.twoline input.stylebutton {
    width: auto;
    background-color: var(--sv-accent-soft);
    border: 1px solid var(--sv-accent);
    border-radius: var(--sv-radius-sm);
    color: var(--sv-accent);
    font: 650 var(--sv-fs-sm) var(--sv-font);
}
ul.dv li table.cblist { display: block; float: none; width: 100%; font-size: var(--sv-fs-sm); }
ul.dv li table.cblist td { padding: var(--sv-space-1) var(--sv-space-2); }

/* ------------------------------------------------------ notices / login --- */

.errorbox, .loginbox {
    display: block;
    width: 100%;
    max-width: 480px;
    margin: var(--sv-space-7) auto;
    padding: var(--sv-space-5);
    background-color: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-lg);
    box-shadow: var(--sv-shadow-2);
}
.errorbox {
    max-width: 640px;
    border-left: 4px solid var(--sv-danger);
}
.errorbox img, .loginbox img { margin-right: var(--sv-space-3); vertical-align: middle; }
.loginbox table { margin: 0 auto; }
.loginbox ul { float: none; list-style: none; margin: var(--sv-space-3) 0 0 0; }
.loginbox span.err { color: var(--sv-danger); font-weight: 650; }

.errsummary {
    display: block;
    margin: 0 0 var(--sv-space-4) 0;
    padding: var(--sv-space-3) var(--sv-space-4);
    background-color: var(--sv-danger-soft);
    border: 1px solid var(--sv-danger);
    border-left-width: 4px;
    border-radius: var(--sv-radius);
    color: var(--sv-danger);
    font-weight: 600;
}
.errsummary li { list-style: none; color: var(--sv-danger); font-weight: 600; }

#login, #logout {
    height: auto;
    max-height: none;
    min-height: 0;
    max-width: none;
    list-style: none;
    margin: 0;
    padding: 0;
    overflow: visible;
    display: flex;
    flex-direction: column;
    gap: var(--sv-space-2);
}
#login li, #logout li { list-style: none; }
/* With allowPublicSignup="False" the Sign Up link renders nothing, leaving an
   empty <li class="links"> that would still draw its divider rule. */
#login li:empty, #logout li:empty { display: none; }
#login li span { text-align: left; }
#login li label { color: var(--sv-text-muted); font-weight: 550; }
#login li input.text { width: 100%; font-size: var(--sv-fs-base); }
/* asp:CheckBox with Text renders <span class="check"><input><label for></span> */
#login li span.check {
    display: flex;
    align-items: center;
    gap: var(--sv-space-2);
    position: static;
    top: auto;
    margin: 0;
}
#login li span.check input { width: 16px; height: 16px; margin: 0; flex: 0 0 auto; }
#login li span.check label { color: var(--sv-text-muted); font-size: var(--sv-fs-sm); cursor: pointer; }
#login li input.submit { float: none; margin: var(--sv-space-1) 0 0 0; width: 100%; }
#login li.links {
    display: flex;
    justify-content: center;
    gap: var(--sv-space-3);
    flex-wrap: wrap;
    float: none;
    width: auto;
    margin: var(--sv-space-2) 0 0 0;
    padding-top: var(--sv-space-2);
    border-top: 1px solid var(--sv-border);
    font-size: var(--sv-fs-xs);
    text-align: center;
}
#logout { line-height: 1.7; }
#logout ul { width: 100%; list-style: none; margin: 0; }

ul.dashboardimg {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-space-4);
    list-style: none;
    margin: 0;
    padding: 0;
}
ul.dashboardimg img { border: 0; padding: 0; }
ul.dashboardimg a {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sv-space-2);
    float: none;
    padding: var(--sv-space-4);
    background-color: var(--sv-surface-2);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    color: var(--sv-text);
    font-variant: normal;
    font-weight: 600;
    text-align: center;
    text-decoration: none;
    transition: background-color var(--sv-transition), border-color var(--sv-transition), transform var(--sv-transition);
}
ul.dashboardimg a:hover {
    background-color: var(--sv-surface-3);
    border-color: var(--sv-accent);
    font-weight: 600;
    transform: translateY(-2px);
    text-decoration: none;
}

/* ------------------------------------------------------------- calendar --- */

.sv-cal-wrap { margin: 0; }

/* Last month | the month and its two pickers | next month. A grid rather than a wrapping flex
   row: the middle grew when the pickers arrived, and wrapping sent "next month" onto a line of
   its own, left-aligned under "last month". Three columns hold the title in the middle whatever
   width the arrows are, and the narrow rule below lifts the title onto its own line rather than
   leaving one arrow stranded. */
.sv-cal-nav {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--sv-space-3);
    margin-bottom: var(--sv-space-4);
}
.sv-cal-title {
    color: var(--sv-heading);
    font-size: var(--sv-fs-xl);
    font-weight: 650;
    text-align: center;
    letter-spacing: -0.01em;
}

table.calendar, table.sv-cal {
    width: 100%;
    border: 1px solid var(--sv-border);
    border-collapse: separate;
    border-spacing: 0;
    border-radius: var(--sv-radius);
    font-size: var(--sv-fs-sm);
    overflow: hidden;
    table-layout: fixed;
}

table.calendar th, table.sv-cal th {
    height: auto;
    padding: var(--sv-space-2) var(--sv-space-2);
    background-color: var(--sv-surface-3);
    border: 0;
    border-bottom: 1px solid var(--sv-border-strong);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-align: center;
    text-transform: uppercase;
}

table.calendar td, table.sv-cal td {
    height: 104px;
    padding: var(--sv-space-1);
    background-color: var(--sv-surface);
    border: 0;
    border-right: 1px solid var(--sv-border);
    border-bottom: 1px solid var(--sv-border);
    vertical-align: top;
}
table.calendar tr td:last-child, table.sv-cal tr td:last-child { border-right: 0; }
table.calendar tr:last-child td, table.sv-cal tr:last-child td { border-bottom: 0; }

table.calendar td.othermonth, table.sv-cal td.othermonth {
    height: 104px;
    padding: var(--sv-space-1);
    background-color: var(--sv-surface-2);
    color: var(--sv-text-faint);
    opacity: 0.72;
}
table.calendar td.day, table.sv-cal td.day { height: auto; }
table.calendar td.today, table.sv-cal td.today {
    background-color: var(--sv-accent-soft);
    border: 0;
    border-right: 1px solid var(--sv-border);
    border-bottom: 1px solid var(--sv-border);
    box-shadow: inset 0 0 0 2px var(--sv-accent);
}

.sv-day-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    margin-bottom: var(--sv-space-1);
    border-radius: var(--sv-radius-pill);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
td.today .sv-day-num { background-color: var(--sv-accent); color: var(--sv-accent-ink); }
td.othermonth .sv-day-num { color: var(--sv-text-faint); }

/* event chip inside a calendar day */
.sv-evt {
    display: block;
    margin-bottom: 3px;
    padding: 3px var(--sv-space-2);
    background-color: var(--sv-surface-3);
    border: 1px solid var(--sv-border-strong);
    border-left-width: 3px;
    border-radius: var(--sv-radius-sm);
    color: var(--sv-text);
    font-size: var(--sv-fs-xs);
    font-weight: 600;
    line-height: 1.35;
    overflow: hidden;
    text-decoration: none;
    text-overflow: ellipsis;
    transition: background-color var(--sv-transition), transform var(--sv-transition);
}
.sv-evt:hover { background-color: var(--sv-accent-soft); color: var(--sv-text); text-decoration: none; transform: translateX(1px); }
.sv-evt:visited { color: var(--sv-text); }
.sv-evt-time { display: block; color: var(--sv-text-muted); font-size: 10px; font-weight: 700; letter-spacing: 0.03em; }
.sv-evt-meta { display: block; color: var(--sv-text-muted); font-size: 10px; font-weight: 600; }

.sv-evt.is-open     { border-left-color: var(--sv-success); }
.sv-evt.is-full     { border-left-color: var(--sv-accent); }
.sv-evt.is-canceled { border-left-color: var(--sv-danger); opacity: 0.72; }
.sv-evt.is-canceled .sv-evt-title { text-decoration: line-through; }
.sv-evt.is-past     { border-left-color: var(--sv-info); }

.sv-cal-legend {
    display: flex;
    gap: var(--sv-space-4);
    flex-wrap: wrap;
    margin-top: var(--sv-space-4);
    padding-top: var(--sv-space-3);
    border-top: 1px solid var(--sv-border);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
}
.sv-cal-legend span { display: inline-flex; align-items: center; gap: var(--sv-space-2); }
.sv-cal-legend i { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }

.sv-cal-actions {
    display: flex;
    gap: var(--sv-space-2);
    flex-wrap: wrap;
    margin-top: var(--sv-space-4);
}

/* ---------------------------------------------------------- modal dialog --- */

/* Built by js/sv-modal.js. Replaces the old absolutely-positioned #framediv,
   which was centred by a jQuery .center() plugin the page never loaded. */
.sv-overlay {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sv-space-4);
    background-color: rgba(6, 18, 14, 0.62);
    -webkit-backdrop-filter: blur(7px) saturate(115%);
    backdrop-filter: blur(7px) saturate(115%);
    opacity: 0;
    transition: opacity 160ms cubic-bezier(0.4, 0, 0.2, 1);
}
:root[data-theme="light"] .sv-overlay { background-color: rgba(40, 34, 20, 0.42); }
.sv-overlay.is-open { opacity: 1; }

/* REQUIRED. The `hidden` attribute only hides an element because the UA
   stylesheet says [hidden]{display:none}, and the `display:flex` above is an
   author rule, so it wins. Without this the closed overlay stays in the layout
   at opacity 0 - invisible but still covering the viewport and eating every
   click on the page. */
.sv-overlay[hidden] { display: none !important; }

.sv-dialog {
    --sv-dialog-w: 760px;
    --sv-dialog-h: 660px;
    display: flex;
    flex-direction: column;
    width: var(--sv-dialog-w);
    height: var(--sv-dialog-h);
    max-width: 100%;
    max-height: 100%;
    background-color: var(--sv-surface);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius-lg);
    box-shadow: var(--sv-shadow-3);
    overflow: hidden;
    transform: translateY(10px) scale(0.985);
    transition: transform 180ms cubic-bezier(0.4, 0, 0.2, 1);
}
.sv-dialog:focus { outline: none; }
.sv-overlay.is-open .sv-dialog { transform: none; }

.sv-dialog-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sv-space-3);
    flex: 0 0 auto;
    padding: var(--sv-space-3) var(--sv-space-4);
    background-color: var(--sv-surface-2);
    border-bottom: 1px solid var(--sv-border);
}
.sv-dialog-title {
    margin: 0;
    color: var(--sv-heading);
    font-size: var(--sv-fs-lg);
    font-weight: 650;
}
.sv-dialog-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    flex: 0 0 auto;
    padding: 0;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--sv-radius-pill);
    color: var(--sv-text-muted);
    cursor: pointer;
    font-family: var(--sv-font);
    font-size: 22px;
    line-height: 1;
    transition: background-color var(--sv-transition), color var(--sv-transition), border-color var(--sv-transition);
}
.sv-dialog-close:hover {
    background-color: var(--sv-danger-soft);
    border-color: var(--sv-danger);
    color: var(--sv-danger);
}

.sv-dialog-body { flex: 1 1 auto; min-height: 0; }
.sv-dialog-frame {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
    background: transparent;
}

/* ------------------------------------------------------------ form grid --- */

/* used by the Create Event dialog; general enough for other forms */
.sv-form {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sv-space-5);
    align-items: stretch;
}
/* a column of stacked fields, so one side can hold several fields while the
   other holds a single field that grows to match (e.g. the notes box) */
.sv-form-col {
    display: flex;
    flex-direction: column;
    gap: var(--sv-space-4);
    min-width: 0;
}
.sv-field { display: flex; flex-direction: column; gap: var(--sv-space-1); min-width: 0; }
.sv-field-full { grid-column: 1 / -1; }

/* fills the remaining column height - kills the dead space under the form */
.sv-field-grow { flex: 1 1 auto; min-height: 0; }
.sv-field-grow textarea {
    flex: 1 1 auto;
    height: 100%;
    min-height: 150px;
    resize: vertical;
}

/* small numeric / short inputs should not stretch across the column */
.sv-field-narrow input,
.sv-field-narrow select { max-width: 140px; }

/* A full-width row whose control holds a sentence, not a number: it needs the row to itself but
   must not stretch to the width of the dialog. The cap is a backstop - on the create-event form
   a script measures the date row above and matches this control to it exactly, because that row
   is four content-sized selects and CSS has no way to name its width. */
.sv-field-sentence input,
.sv-field-sentence select { max-width: 420px; }
.sv-field > label,
.sv-field-label {
    color: var(--sv-heading);
    font-size: var(--sv-fs-sm);
    font-weight: 650;
}
.sv-optional {
    margin-left: var(--sv-space-1);
    color: var(--sv-text-faint);
    font-size: var(--sv-fs-xs);
    font-weight: 500;
}
.sv-hint { margin: 0; color: var(--sv-text-muted); font-size: var(--sv-fs-xs); }
.sv-hint strong { color: var(--sv-accent); font-weight: 650; }

/* the year / month / day trio. width:auto lets each select size to its own
   widest option, so nothing stretches and the month name is never clipped
   regardless of how the month labels are worded. */
.sv-date-row { display: flex; gap: var(--sv-space-2); flex-wrap: wrap; align-items: center; }
.sv-date-row select { flex: 0 0 auto; width: auto; min-width: 0; }

/* -------------------------------------------------------- busy overlay --- */

.sv-busy {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(6, 18, 14, 0.55);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
    cursor: progress;
}
:root[data-theme="light"] .sv-busy { background-color: rgba(40, 34, 20, 0.38); }

/* Same trap as .sv-overlay: the `hidden` attribute loses to the display:flex
   above, so without this the overlay never goes away and silently eats clicks. */
.sv-busy[hidden] { display: none !important; }

.sv-busy-inner {
    display: flex;
    align-items: center;
    gap: var(--sv-space-4);
    padding: var(--sv-space-4) var(--sv-space-5);
    background-color: var(--sv-surface);
    background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.04), transparent);
    border: 1px solid var(--sv-border-strong);
    border-left: 3px solid var(--sv-accent);
    border-radius: var(--sv-radius);
    box-shadow: var(--sv-shadow-3);
    color: var(--sv-text);
    font-size: var(--sv-fs-base);
    font-weight: 600;
}

/* ---------------------------------------------------------- spinner ------ */

/* Replaces i/spinner.gif, which was a fixed grey/white bitmap that looked
   pasted-on in both themes. Built from borders (not conic-gradient/mask) so it
   renders identically on older browsers, and coloured from the theme tokens. */
.sv-spinner {
    display: inline-block;
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    box-sizing: border-box;
    border: 3px solid var(--sv-surface-3);
    border-top-color: var(--sv-accent);
    border-right-color: var(--sv-accent);
    border-radius: 50%;
    animation: sv-spin 650ms linear infinite;
}

.sv-spinner-lg { width: 30px; height: 30px; border-width: 4px; }

@keyframes sv-spin {
    to { transform: rotate(360deg); }
}

/* A frozen arc reads as a broken image, so drop it entirely when the visitor
   has asked for no motion - the accompanying text still says what is happening.
   (The global prefers-reduced-motion rule below neutralises the animation with
   !important, which is why hiding is the right fallback rather than restyling.) */
@media (prefers-reduced-motion: reduce) {
    .sv-spinner { display: none; }
}

/* toolbar above a grid (e.g. the Send Invites "Invite all" toggle) */
.sv-grid-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sv-space-3);
    flex-wrap: wrap;
    margin-bottom: var(--sv-space-2);
}
.sv-check-inline {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-space-2);
    color: var(--sv-heading);
    cursor: pointer;
    font-size: var(--sv-fs-sm);
    font-weight: 650;
}
.sv-check-inline input[type="checkbox"] { width: 16px; height: 16px; margin: 0; }

.sv-form-actions {
    display: flex;
    align-items: center;
    gap: var(--sv-space-3);
    flex-wrap: wrap;
    margin-top: var(--sv-space-5);
    padding-top: var(--sv-space-4);
    border-top: 1px solid var(--sv-border);
}
.sv-form-actions .sv-spacer { margin-left: auto; }
.sv-form-status { color: var(--sv-danger); font-size: var(--sv-fs-sm); font-weight: 600; }

@media (max-width: 620px) {
    .sv-form { grid-template-columns: minmax(0, 1fr); }
}

/* ---------------------------------------------------------- event page --- */

/* details beside attendance; stacks on narrow screens. Replaces the old
   <table width="100%"> two-column layout. */
.sv-event-grid {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: var(--sv-space-5);
    align-items: start;

    /* The last card in each column is a :last-child, so .contentblock's own bottom margin is
       taken off it - which left the results and the live view butted straight against the
       bottom of whichever column was taller, reading as one card crowding another. The gap
       belongs to the grid rather than to the cards inside it, and it is the wider step because
       what follows is a new section of the page rather than the next card in a stack. */
    margin-bottom: var(--sv-space-5);
}

/* the details list should sit flush left in its card, not centred */
.sv-details-card ul.dv { max-width: none; margin: 0; }

/* The 40px row height on ul.dv is for forms with inputs; these rows are label and text only,
   so they sit tighter - the list reads as one block of facts rather than a spaced-out form. */
.sv-details-card ul.dv li { min-height: 28px; }
.sv-details-card .sv-card-head,
.sv-attend-card .sv-card-head { margin-bottom: var(--sv-space-3); }

/* "Take over scoring" at the right-hand end of the Live card's heading, and the refresh the
   watch view draws in its own head. Both sit in a .sv-card-head, which is space-between - the
   subtitle beside the heading takes the slack so the button lands at the far end. */
.sv-card-head > .sv-card-sub + .sv-takeover,
.sv-card-head > .sv-card-sub + .sv-refresh { margin-left: auto; }

.sv-refresh { display: inline-flex; align-items: center; gap: var(--sv-space-2); }

/* A read in flight: the glyph turns, and the button stops taking presses. */
.sv-refresh[disabled] { opacity: 0.7; cursor: default; }
.sv-refresh[disabled] .sv-refresh-glyph { animation: sv-spin 0.9s linear infinite; }

@keyframes sv-spin { to { transform: rotate(360deg); } }

/* Whose night it is and when, at the top of the left-hand column, above the details. */
.sv-host-card {
    display: flex;
    align-items: center;
    gap: var(--sv-space-4);
}

/* Both cards in the left column take the surface the attendance panel opposite them uses. They
   wore .sv-card's own darker one, which made the grid read as a dark side and a light side
   rather than as one row of panels. */
.sv-host-card,
.sv-details-card { background-color: var(--sv-surface-2); }

.sv-host-who { display: flex; flex-direction: column; min-width: 0; }

/* The word first and small, the name second and large: the eye lands on the face, then on who
   it is. "Host" above the name reads as a caption on the picture rather than as a field. */
.sv-host-label {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.sv-host-name {
    color: var(--sv-heading);
    font-size: var(--sv-fs-lg);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* When the night is, in words. Under the name rather than beside it: the line is long, and a
   name and a date competing for one row is what the old "Date:" list row did badly. */
.sv-host-when {
    margin-top: var(--sv-space-2);
    color: var(--sv-text);
    font-size: var(--sv-fs-sm);
}

/* the framed attendance panel */
.sv-attend-card {
    padding: var(--sv-space-4);
    background-color: var(--sv-surface-2);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    box-shadow: var(--sv-shadow-2);
}

.sv-attend {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: var(--sv-space-3);
}

.sv-attend-group {
    min-width: 0;
    padding: var(--sv-space-3);
    background-color: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-top: 3px solid var(--sv-border-strong);
    border-radius: var(--sv-radius-sm);
    box-shadow: var(--sv-shadow-1);
}
.sv-attend-group.is-yes       { border-top-color: var(--sv-success); }
.sv-attend-group.is-waiting   { border-top-color: var(--sv-info); }
.sv-attend-group.is-maybe     { border-top-color: var(--sv-warn); }
.sv-attend-group.is-no        { border-top-color: var(--sv-danger); }
.sv-attend-group.is-invited   { border-top-color: var(--sv-border-strong); }
.sv-attend-group.is-attended  { border-top-color: var(--sv-info); }

.sv-attend-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sv-space-2);
    margin-bottom: var(--sv-space-2);
    padding-bottom: var(--sv-space-2);
    border-bottom: 1px solid var(--sv-border);
}
.sv-attend-label {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}
.sv-attend-count {
    color: var(--sv-heading);
    font-size: var(--sv-fs-lg);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.sv-attend-list { list-style: none; margin: 0; padding: 0; }

/* flex row so a pill stays on the same line as the name instead of wrapping onto its own line
   in these narrow columns - and wrapping when there is genuinely no room, which there is not
   once a name carries both HOST and SCORER in a 140px grid column. */
.sv-attend-list li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-space-1);
    padding: 2px 0;
    color: var(--sv-text);
    font-size: var(--sv-fs-sm);
    line-height: 1.45;
}

/* No min-width:0 here, deliberately. A flex item with it will shrink below its own content, so
   the pills kept their width and the name was crushed to nothing - "Chad Hosick" came out as
   four lines reading Cha / d / Hosi / ck. Left at the default the name will not go narrower than
   its longest word, and the pills wrap under it instead, which is the thing that should give.
   break-word stays for the one genuinely enormous name that is longer than the column. */
.sv-attend-name { overflow-wrap: break-word; }

/* compact solid pill: amber fill, text knocked out in the page ground colour */
.sv-chip-host,
.sv-chip-scorer {
    flex: 0 0 auto;
    padding: 0 5px;
    background-color: var(--sv-host-chip-bg);
    border: 0;
    border-radius: var(--sv-radius-pill);
    color: var(--sv-bg);
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.04em;
    line-height: 15px;
    text-transform: uppercase;
}

/* Who kept the score, which is a different job from hosting and often a different person. Shares
   everything but the fill, so the two sit side by side on one name without arguing. */
.sv-chip-scorer { background-color: var(--sv-scorer-chip-bg); }
.sv-attend-empty { margin: 0; color: var(--sv-text-faint); font-size: var(--sv-fs-sm); }

.sv-attend-actions {
    display: flex;
    gap: var(--sv-space-2);
    flex-wrap: wrap;
    margin-top: var(--sv-space-4);
    padding-top: var(--sv-space-3);
    border-top: 1px solid var(--sv-border);
}

/* "Your current response is ..." plus the change-response buttons */
.sv-response {
    margin-top: var(--sv-space-5);

    /* And below it. It had a top margin and no bottom one, so whatever came next - the
       conversation about the night, these days - was pressed straight against it. */
    margin-bottom: var(--sv-space-5);

    padding: var(--sv-space-5);
    background-color: var(--sv-surface-2);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    box-shadow: var(--sv-shadow-1);
    text-align: center;
}
.sv-response-label { margin: 0; color: var(--sv-text-muted); font-size: var(--sv-fs-sm); }
.sv-response-value {
    margin: var(--sv-space-1) 0 var(--sv-space-4) 0;
    color: var(--sv-heading);
    font-size: var(--sv-fs-lg);
    font-weight: 700;
}
.sv-response-value.is-yes   { color: var(--sv-success); }
.sv-response-value.is-maybe { color: var(--sv-warn); }
.sv-response-value.is-no    { color: var(--sv-danger); }

/* centre the response / host buttons - they used to be left-aligned and
   separated by <br/> tags */
.sv-response .buttons,
.sv-response-actions {
    justify-content: center;

    /* stretch, not the centre the base .buttons rule asks for: these carry sentences of different
       lengths, so one wraps to two lines and its neighbour does not, and a short button floating
       at half the height of the one beside it reads as a mistake. Stretch makes every button on a
       line as tall as the tallest, and each one centres its own icon and text inside that. */
    align-items: stretch;
}
.sv-response hr { margin: var(--sv-space-5) 0 var(--sv-space-4) 0; }

/* the wide attendance buttons should not be full-bleed when centred */
.sv-response .buttons button.eventattendance {
    width: auto;
    min-width: 260px;
    justify-content: center;
}

.sv-note-block { margin-top: var(--sv-space-4); }
.sv-note-label {
    display: block;
    margin-bottom: var(--sv-space-1);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}
.sv-note-block ul { margin: var(--sv-space-1) 0 0 1.2em; }

@media (max-width: 860px) {
    .sv-event-grid { grid-template-columns: minmax(0, 1fr); }
}

/* --------------------------------------------------------------- charts --- */

.sv-chart-card { margin-bottom: var(--sv-space-4); padding: var(--sv-space-5); border-radius: var(--sv-radius-lg); }

.sv-chart-tabs {
    display: flex;
    gap: var(--sv-space-1);
    flex-wrap: wrap;
    margin: 0;
    padding: 0;
    list-style: none;
}
.sv-chart-tab {
    padding: var(--sv-space-1) var(--sv-space-3);
    background: none;
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-pill);
    color: var(--sv-text-muted);
    cursor: pointer;
    font-family: var(--sv-font);
    font-size: var(--sv-fs-xs);
    font-weight: 650;
    font-variant-numeric: tabular-nums;
    transition: background-color var(--sv-transition), color var(--sv-transition), border-color var(--sv-transition);
}
.sv-chart-tab:hover { background-color: var(--sv-surface-2); color: var(--sv-text); }
.sv-chart-tab[aria-selected="true"] {
    background-color: var(--sv-accent);
    border-color: var(--sv-accent);
    color: var(--sv-accent-ink);
}

.sv-chart { width: 100%; position: relative; }

/* --------------------------------------- the dead-end pages: 404 and 500 --- */
/* One look, two pages. NotFound.aspx and Error.aspx are the only places a visitor
   lands when there is nothing more the site can do for them, and they should read
   as the same kind of moment rather than as two unrelated apologies. */

.sv-errpage {
    max-width: 640px;
    margin: var(--sv-space-7) auto;
    padding: var(--sv-space-7) var(--sv-space-6);
    text-align: center;
}

/* Two cards face down: there is nothing to see, which is the whole message. */
.sv-errpage-hand {
    position: relative;
    height: 92px;
    margin-bottom: var(--sv-space-5);
}
.sv-muck-card {
    position: absolute;
    top: 0;
    left: 50%;
    width: 60px;
    height: 86px;
    background-color: var(--sv-surface-3);
    background-image: repeating-linear-gradient(45deg, var(--sv-bg-pattern) 0 3px, transparent 3px 7px);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius-sm);
    box-shadow: var(--sv-shadow-2);
}
.sv-muck-card-1 { transform: translateX(-58px) rotate(-13deg); }
.sv-muck-card-2 { transform: translateX(-2px) rotate(9deg); }

.sv-errpage-code {
    margin: 0;
    color: var(--sv-accent);
    font-size: 3.5rem;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    letter-spacing: 0.06em;
    line-height: 1;
}

.sv-errpage-title {
    margin: var(--sv-space-3) 0 0;
    color: var(--sv-heading);
    font-size: var(--sv-fs-xl);
}

.sv-errpage-body {
    max-width: 46ch;
    margin: var(--sv-space-3) auto 0;
    color: var(--sv-text-muted);
}

/* The address that missed, so a broken link can be reported without guesswork. */
.sv-errpage-url {
    margin: var(--sv-space-4) 0 0;
    padding: var(--sv-space-2) var(--sv-space-3);
    background: var(--sv-surface-2);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-sm);
    color: var(--sv-text-faint);
    font-family: var(--sv-font-mono, monospace);
    font-size: var(--sv-fs-sm);
    overflow-wrap: anywhere;
}

.sv-errpage-actions {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: var(--sv-space-3);
    margin-top: var(--sv-space-6);
}

/* ------------------------------------------------ knockouts (Stats.aspx) --- */

/* The nemesis card: two short ranked lists side by side, stacking on a phone. */
.sv-nemesis {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--sv-space-5);
}
.sv-nemesis h4 {
    margin: 0 0 var(--sv-space-2);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.sv-nemesis ol { margin: 0; padding-left: 1.5em; }
.sv-nemesis li { padding: var(--sv-space-1) 0; }
.sv-nemesis-count { margin-left: var(--sv-space-2); color: var(--sv-text-muted); font-size: var(--sv-fs-sm); }
.sv-nemesis-empty { margin: 0; color: var(--sv-text-muted); font-size: var(--sv-fs-sm); }
.sv-chart svg { width: 100%; height: auto; overflow: visible; }
.sv-chart-empty { padding: var(--sv-space-6); color: var(--sv-text-muted); font-style: italic; text-align: center; }

.sv-chart-legend {
    display: flex;
    gap: var(--sv-space-4);
    flex-wrap: wrap;
    margin-top: var(--sv-space-3);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
}
.sv-chart-legend span { display: inline-flex; align-items: center; gap: var(--sv-space-2); }
.sv-chart-legend i { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }

.sv-bar { transition: opacity var(--sv-transition); }
.sv-chart:hover .sv-bar:not(:hover) { opacity: 0.65; }

.sv-tooltip {
    position: absolute;
    z-index: 20;
    max-width: 240px;
    padding: var(--sv-space-2) var(--sv-space-3);
    background-color: var(--sv-surface);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius-sm);
    box-shadow: var(--sv-shadow-3);
    color: var(--sv-text);
    font-size: var(--sv-fs-xs);
    line-height: 1.5;
    pointer-events: none;
    opacity: 0;
    transform: translate(-50%, -100%);
    transition: opacity var(--sv-transition);
    white-space: nowrap;
}
.sv-tooltip.is-visible { opacity: 1; }
.sv-tooltip b { color: var(--sv-heading); }

/* ------------------------------------------- standalone / embedded pages --- */

/* Login.aspx and Error.aspx have no master - give the toggle somewhere to sit */
.sv-standalone-bar {
    display: flex;
    justify-content: flex-end;
    width: 100%;
    max-width: var(--sv-shell-max);
    margin: 0 auto;
    padding: var(--sv-space-4) var(--sv-space-4) 0 var(--sv-space-4);
}

/* CreateEvent.aspx / SendInvites.aspx render inside a modal iframe - they
   previously used style="background: url()" to defeat the body image */
body.sv-embedded {
    background: transparent;
    padding: var(--sv-space-4);
}
body.sv-embedded > form > div { max-width: 100%; }

/* ------------------------------------------------------------- a11y ----- */

.sv-visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* ---------------------------------------------------- the feed (home.aspx) --- */

/* The button that applies a reaction, a pin or a removal. Off screen rather than display:none
   or hidden, because a hidden submit button is not clickable from script in every browser and
   the whole mechanism is a scripted click on it. */
.sv-offscreen {
    position: absolute;
    left: -9999px;
    width: 1px; height: 1px;
    overflow: hidden;
}

.sv-compose .sv-compose-head {
    display: flex;
    align-items: center;
    gap: var(--sv-space-3);
    margin-bottom: var(--sv-space-3);
}

.sv-compose-title { flex: 1 1 auto; min-width: 0; }

/* --- the composer at rest ------------------------------------------------

   Collapsed until somebody means to write something. The feed is a thing people come to read;
   a full-height box, a photo picker and a Post button sitting above the first post charge every
   reader the same vertical space for a thing most of them are not here to do.

   min-height overrides the global textarea rule, which sets 120px on every textarea on the
   site - without this the box cannot go small however short the height is.

   It opens on :focus-within, and stays open on the two conditions that mean work is in
   progress: something typed, or photographs chosen. That second part is not decoration. On
   focus alone the Post button would disappear the instant the textarea lost focus - which is
   exactly what pressing the mouse on Post does - so the click would land on nothing and the
   post would never be made.

   Those two live in their own rules rather than in one selector list with :focus-within. An
   unsupported :has() invalidates the whole rule it sits in, and taking the focus behaviour down
   with it would leave the composer permanently shut. Apart on purpose. */
.sv-compose-body {
    width: 100%;
    resize: vertical;
    min-height: 0;
    height: 42px;
    transition: height var(--sv-transition);
}

.sv-compose .sv-compose-extras,
.sv-compose .sv-compose-actions { display: none; }

/* opened, by intent */
.sv-compose:focus-within .sv-compose-body { height: 96px; }
.sv-compose:focus-within .sv-compose-extras,
.sv-compose:focus-within .sv-compose-actions { display: flex; }

/* opened, because there is something to lose */
.sv-compose:has(.sv-compose-title:not(:placeholder-shown)) .sv-compose-body,
.sv-compose:has(.sv-compose-body:not(:placeholder-shown)) .sv-compose-body { height: 96px; }

.sv-compose:has(.sv-compose-title:not(:placeholder-shown)) .sv-compose-extras,
.sv-compose:has(.sv-compose-title:not(:placeholder-shown)) .sv-compose-actions,
.sv-compose:has(.sv-compose-body:not(:placeholder-shown)) .sv-compose-extras,
.sv-compose:has(.sv-compose-body:not(:placeholder-shown)) .sv-compose-actions { display: flex; }

/* opened, because photographs are attached and would otherwise be hidden and still posted */
.sv-compose:has(.sv-photo-chosen:not(:empty)) .sv-compose-body { height: 96px; }
.sv-compose:has(.sv-photo-chosen:not(:empty)) .sv-compose-extras,
.sv-compose:has(.sv-photo-chosen:not(:empty)) .sv-compose-actions { display: flex; }

/* The file input and the backdate box, under the body and above the buttons. */
.sv-compose-extras {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-space-3);
    margin-top: var(--sv-space-3);
    font-size: var(--sv-fs-sm);
    color: var(--sv-text-muted);
}

.sv-compose-date { display: inline-flex; align-items: center; gap: var(--sv-space-2); }

/* --- picking photographs ------------------------------------------------- */

/* The native file input says "Choose Files / No file chosen" in the browser's own grey, cannot
   be styled, and looks like nothing else on the page. It is off screen instead - not display
   none, which would take it out of the tab order and stop the label working - and this label is
   what people see and click. No script: a label pointed at an input opens its picker. */
.sv-photo-pick {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-space-2);
    padding: 5px 12px;
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-pill);
    background: var(--sv-surface-2);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    font-weight: 600;
    cursor: pointer;
    user-select: none;
}

.sv-photo-pick:hover {
    border-color: var(--sv-accent-ring);
    background: var(--sv-accent-soft);
    color: var(--sv-accent);
}

.sv-photo-pick svg { display: block; flex: 0 0 auto; }

/* The input is off screen, so its own focus ring is too. This puts one where the eye is. */
.sv-compose-extras:focus-within .sv-photo-pick {
    border-color: var(--sv-accent-ring);
    box-shadow: 0 0 0 3px var(--sv-accent-soft);
}

/* What was picked, drawn by js\sv-feed.js from the files themselves - nothing is uploaded until
   Post, so these come out of the browser rather than off the server. */
.sv-photo-chosen {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-space-2);
    flex-wrap: wrap;
}

.sv-photo-chosen img {
    width: 34px;
    height: 34px;
    object-fit: cover;
    border-radius: var(--sv-radius-sm);
    border: 1px solid var(--sv-border);
    display: block;
}

.sv-photo-chosen-count { color: var(--sv-text-faint); font-size: var(--sv-fs-xs); }

.sv-compose-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--sv-space-3);
    margin-top: var(--sv-space-3);
}

.sv-compose-hint { margin-right: auto; color: var(--sv-text-faint); font-size: var(--sv-fs-xs); }
.sv-reply-to     { margin: 0 0 var(--sv-space-2); color: var(--sv-text-muted); font-size: var(--sv-fs-sm); }
.sv-thread-back  { margin: 0 0 var(--sv-space-3); font-size: var(--sv-fs-sm); }

/* --- one card ----------------------------------------------------------- */

.sv-post-head {
    display: flex;
    align-items: center;
    gap: var(--sv-space-3);
    margin-bottom: var(--sv-space-3);
}

/* The face at the top of a card is sized with the roster's and the winners', up beside the
   avatar rules - the three of them are the same decision. */

.sv-post-by     { display: flex; flex-direction: column; min-width: 0; margin-right: auto; }
.sv-post-author { font-weight: 600; color: var(--sv-heading); }
.sv-post-when   { color: var(--sv-text-faint); font-size: var(--sv-fs-xs); }

.sv-post-title { margin: 0 0 var(--sv-space-2); font-size: var(--sv-fs-lg); }

.sv-post-body {
    /* Posts are written in a textarea, so the line breaks somebody typed are the ones they
       meant; long words - a pasted URL - wrap rather than widen the card. */
    overflow-wrap: anywhere;
    color: var(--sv-text);
}

.sv-post-body img { max-width: 100%; height: auto; }

/* --- photographs --------------------------------------------------------- */

/* Prints laid on the table rather than a grid of tiles: wrapping flex, so a row of them stays
   loose and the last one does not stretch to fill a column it does not want. */
.sv-photos {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-space-4);
    margin-top: var(--sv-space-4);
    padding-bottom: var(--sv-space-2);
}

.sv-polaroid {
    display: block;
    width: 168px;
    padding: 9px 9px 0;
    background: #f7f5ef;
    border-radius: 2px;
    box-shadow: var(--sv-shadow-2);
    text-decoration: none;
    transition: transform 0.14s ease, box-shadow 0.14s ease;
}

.sv-polaroid:hover,
.sv-polaroid:focus-visible {
    /* Straightens and lifts, which is what picking one up looks like. */
    transform: rotate(0deg) translateY(-4px) scale(1.03);
    box-shadow: var(--sv-shadow-3);
    z-index: 2;
    position: relative;
}

.sv-polaroid-shot {
    display: block;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    background: #1a1a1a;
}

/* Square, because a print is: a row of portraits and landscapes has to line up. The whole
   photograph is in the viewer, so nothing is lost by cropping the thumbnail. */
.sv-polaroid-shot img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* The deep strip along the bottom, which is the thing that makes it read as a print. It keeps
   its height with no caption, or the shape is just a photograph with a white border. */
.sv-polaroid-strip {
    display: block;
    min-height: 34px;
    padding: 9px 2px;
    color: #4a4a46;
    font-size: var(--sv-fs-xs);
    line-height: 1.25;
    text-align: center;
    overflow: hidden;
}

/* Tilted off their position rather than at random, so the pile does not reshuffle itself every
   time the page is drawn. */
/* Smaller inside a reply, which is already an indented block within a card. */
.sv-reply .sv-photos { gap: var(--sv-space-3); margin-top: var(--sv-space-3); }
.sv-reply .sv-polaroid { width: 118px; }

.sv-tilt-0 { transform: rotate(-1.6deg); }
.sv-tilt-1 { transform: rotate(1.2deg); }
.sv-tilt-2 { transform: rotate(-0.6deg); }
.sv-tilt-3 { transform: rotate(2deg); }

/* Smaller in the editor, and not tilted: this is a list of what is on the post, not the post. */
.sv-photos-editing { gap: var(--sv-space-3); margin-top: var(--sv-space-3); }

.sv-polaroid-editing {
    width: 116px;
    transform: none;
    cursor: default;
}

.sv-polaroid-editing:hover { transform: none; box-shadow: var(--sv-shadow-2); }
.sv-polaroid-editing .sv-polaroid-strip { min-height: 0; padding: 7px 2px; }

/* --- the viewer ---------------------------------------------------------- */

/* Built by js\sv-feed.js when a print is clicked. Over the page rather than in a new tab: a feed
   is something you scroll, and losing your place to look at one photograph is a poor trade. */
.sv-viewer {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sv-space-2);
    padding: var(--sv-space-5);
    background: rgba(4, 9, 14, 0.92);
}

/* The rule above would otherwise keep it on screen for ever. The hidden attribute hides an
   element through the browser's own [hidden] { display: none }, and any display an author writes
   beats the browser's stylesheet however specific it is - so closing the viewer set the attribute
   and changed nothing anybody could see. This is what makes hidden mean hidden. */
.sv-viewer[hidden] { display: none; }

body.sv-viewer-open { overflow: hidden; }

.sv-viewer-figure {
    margin: 0;
    max-width: 100%;
    max-height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sv-space-3);
}

.sv-viewer-figure img {
    max-width: 100%;

    /* Room for the caption under it, so a tall photograph does not push it off the screen. */
    max-height: calc(100vh - 140px);
    object-fit: contain;
    border-radius: var(--sv-radius);
    box-shadow: var(--sv-shadow-3);
}

.sv-viewer-figure figcaption {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    text-align: center;
}

.sv-viewer-close,
.sv-viewer-step {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    border: 1px solid var(--sv-border);
    border-radius: 50%;
    background: var(--sv-surface-2);
    color: var(--sv-text);
    font-size: var(--sv-fs-xl);
    line-height: 1;
    cursor: pointer;
}

.sv-viewer-close:hover,
.sv-viewer-step:hover { border-color: var(--sv-border-strong); background: var(--sv-surface-3); }

.sv-viewer-close {
    position: absolute;
    top: var(--sv-space-4);
    right: var(--sv-space-4);
}

@media (max-width: 560px) {

    .sv-polaroid { width: 132px; }

    /* The arrows would take half the width on a phone; a swipe is not built, so the photograph
       gets the room and the close button stays. */
    .sv-viewer-step { display: none; }
    .sv-viewer { padding: var(--sv-space-3); }
}

/* --- the post that is a night ------------------------------------------- */

.sv-post-night-card { border-left: 3px solid var(--sv-accent); }

.sv-post-night {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-space-2);
    margin-bottom: var(--sv-space-2);
}

.sv-post-night-when { color: var(--sv-text-muted); font-size: var(--sv-fs-sm); }

/* The status pill IS the way through to the night: one control rather than a word at one end of
   the line and an icon at the other doing one job between them. Inherits everything from
   .sv-game-pill, including the colour the status gives it, which the icon then picks up through
   currentColor. */
.sv-night-pill {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    text-decoration: none;
}

/* The paperclip, beside the status pill and shaped like one. A button, not a link: it opens a
   file picker and then posts, which is not something to be followed by a crawler. */
.sv-attach-pill {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--sv-surface-2);
    font: inherit;
    font-size: var(--sv-fs-xs);
    font-weight: 600;
    cursor: pointer;
}

.sv-attach-pill:hover,
.sv-attach-pill:focus-visible {
    border-color: var(--sv-accent-ring);
    background: var(--sv-accent-soft);
    color: var(--sv-accent);
}

.sv-attach-pill svg { display: block; flex: 0 0 auto; }

/* Only the first of the pair pushes itself right; the rest sit beside it. */
.sv-attach-pill ~ .sv-night-pill { margin-left: var(--sv-space-2); }

/* The pill keeps its status colour now that it is a link.

   .contentblock a sets every link in a card to the link colour, and at one class plus one type it
   outranks .sv-game-pill-live at one class - so the moment the pill became an anchor, "being
   played now" went from red to blue and said nothing. Naming the anchor alongside the modifier
   puts the status back in charge. The values are the same variables the modifiers use a few
   hundred lines up; only the selectors are repeated. */
a.sv-night-pill                       { color: var(--sv-text-muted); }
a.sv-night-pill.sv-game-pill-yes      { color: var(--sv-success, var(--sv-accent)); }
a.sv-night-pill.sv-game-pill-maybe    { color: var(--sv-accent); }
a.sv-night-pill.sv-game-pill-no       { color: var(--sv-text-faint); }
a.sv-night-pill.sv-game-pill-live     { color: var(--sv-danger); }

.sv-night-pill:hover,
.sv-night-pill:focus-visible {
    /* Brightened rather than recoloured: the colour is saying what the night is, and a hover is
       not the moment to change that. */
    background: var(--sv-surface-3);
    border-color: currentColor;
    text-decoration: none;
}

/* The chart has no descenders and the text does, so they do not sit on the same line by default. */
.sv-night-pill svg { display: block; flex: 0 0 auto; }

/* --- replies ------------------------------------------------------------ */

/* Indented by depth up to three, and no further: a conversation nested deeper than that is
   still readable flat, and a fourth step would leave the words in a column an inch wide. */
.sv-post-reply    { border-left: 2px solid var(--sv-border); }
.sv-post-depth-1  { margin-left: var(--sv-space-4); }
.sv-post-depth-2  { margin-left: calc(var(--sv-space-4) * 2); }
.sv-post-depth-3  { margin-left: calc(var(--sv-space-4) * 3); }

/* --- the conversation on a night's own page ----------------------------- */

/* UserControls\PostThread.ascx: the same cards the feed draws, inside one card of their own on
   event.aspx. The cards are left alone - a post has to look like a post wherever it is read -
   and what is adjusted is only what being inside another card changes. */

/* The heading, the count and the night's own glyphs share one flex row, and space-between would
   otherwise strand the count in the middle of it. The count belongs beside the heading; only the
   glyphs belong at the far end. */
.sv-night-talk > .sv-card-head > .sv-card-sub { margin-right: auto; }

/* Everything in here is a reply to the night, so the first step of indent says nothing that the
   card around them does not already say - it only narrows them. Deeper replies keep their step,
   because THAT one still means something. */
.sv-night-talk .sv-post-depth-1 { margin-left: 0; }

/* The box to write in is the last thing in the card rather than a card in its own right, so it
   loses the gap that would otherwise sit under it. */
.sv-night-talk .sv-reply-box { margin-bottom: 0; }

/* --- the replies shown inside a feed card ------------------------------- */

/* Inside the post's own card, not cards of their own, so a short conversation reads as one
   block rather than as four things that happen to be next to each other. */
.sv-replies {
    margin-top: var(--sv-space-3);
    padding-top: var(--sv-space-3);
    border-top: 1px solid var(--sv-border);
    display: flex;
    flex-direction: column;
    gap: var(--sv-space-3);
}

/* Above the replies, because what is shown is the END of the conversation. */
.sv-replies-more { font-size: var(--sv-fs-sm); }

.sv-reply { display: flex; gap: var(--sv-space-2); align-items: flex-start; }

.sv-reply-body { min-width: 0; font-size: var(--sv-fs-sm); }
.sv-reply-who  { font-weight: 600; color: var(--sv-heading); }
.sv-reply .sv-post-body { margin-top: 2px; }
.sv-reply .sv-post-when { display: inline; }

/* A reply gets the same control a post does, at the same size. It was smaller here at first, on
   the theory that it should sit quietly beside a line of text - but a thumb shrunk to fs-xs in a
   pill with no vertical padding read as cramped rather than quiet, and it is the same tap target
   either way. All that is left of the difference is the space above it. */
.sv-react-small { margin-top: var(--sv-space-2); }

/* --- reactions and actions ---------------------------------------------- */

.sv-post-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-space-3);
    margin-top: var(--sv-space-4);
    padding-top: var(--sv-space-3);
    border-top: 1px solid var(--sv-border);
}

/* Collapsed to one control: a glyph and how many people have reacted. Six faces under every post
   and every reply shouted louder than anything anybody had written. The picker is one hover,
   focus or tap away and sits over the card rather than pushing it about. */
.sv-react { position: relative; display: inline-flex; }

.sv-react-trigger,
.sv-react-btn {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-pill);
    background: var(--sv-surface-2);
    color: var(--sv-text-muted);
    font: inherit;
    font-size: var(--sv-fs-sm);
    line-height: 1.6;
    cursor: pointer;
}

.sv-react-trigger:hover,
.sv-react-btn:hover { border-color: var(--sv-border-strong); background: var(--sv-surface-3); }

/* The viewer's own, so a glance says what they have already said - on the trigger too, which is
   why the collapsed control shows their glyph rather than always the thumb. */
.sv-react-on {
    border-color: var(--sv-accent-ring);
    background: var(--sv-accent-soft);
    color: var(--sv-accent);
}

.sv-react-glyph { font-size: var(--sv-fs-md); line-height: 1; }
.sv-react-count { font-variant-numeric: tabular-nums; }

/* --- the picker ---------------------------------------------------------- */

.sv-react-pill {
    position: absolute;
    bottom: 100%;
    left: 0;
    z-index: 5;

    /* A column now: the glyphs to pick from, and under them the names of whoever already has.
       The row of glyphs keeps the nowrap that used to be on the whole thing - the names have to
       be free to wrap or a popular post produces a popover wider than the screen. */
    display: flex;
    flex-direction: column;
    gap: var(--sv-space-1);
    max-width: min( 22rem, calc( 100vw - 2 * var(--sv-space-4) ) );
    margin-bottom: 8px;
    padding: 6px 10px;
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius-pill);
    background: var(--sv-surface-3);
    box-shadow: var(--sv-shadow-2);

    /* Hidden by visibility rather than display, so the fade has something to animate and the
       buttons stay out of the tab order while it is closed. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(4px);
    transition: opacity 0.12s ease, transform 0.12s ease, visibility 0.12s;
}

/* The gap between trigger and pill would close it as the pointer crossed. This bridges it. */
.sv-react-pill::after {
    content: "";
    position: absolute;
    left: 0; right: 0; top: 100%;
    height: 10px;
}

/* In a card's HEADING the pill has nowhere to go upwards. The night's own reactions ride in the
   heading on event.aspx - the post they belong to is the night, and the night is the page - so
   the trigger sits a few pixels below the card's top edge, and .sv-card clips what it holds. The
   glyphs came out sliced off along that edge.

   So there it opens downward instead, over the card's own body, which is where the room is. And
   it anchors to the right, because the trigger is at the far end of the heading and a pill hung
   from its left edge runs off the other side of the card into the same clip. */
.sv-card-head .sv-react-pill {
    top: 100%;
    bottom: auto;
    left: auto;
    right: 0;
    margin-top: 8px;
    margin-bottom: 0;

    /* The closed state slides UP into place when the pill is above its trigger; opening the other
       way it should slide down, or it appears to come from the wrong side. */
    transform: translateY(-4px);
}

.sv-card-head .sv-react-pill::after {
    top: auto;
    bottom: 100%;
}

.sv-react:hover .sv-react-pill,
.sv-react:focus-within .sv-react-pill,
.sv-react-open .sv-react-pill {
    opacity: 1;
    visibility: visible;
    transform: none;
}

/* The glyphs to choose from. */
.sv-react-row {
    display: flex;
    gap: var(--sv-space-1);
    white-space: nowrap;
}

/* Who has already reacted, under the pickers. Once there are names the popover is no longer a
   pill shape, so the corner softens to match - behind :has() so a browser without it simply
   keeps the rounder outline rather than losing the whole rule. */
.sv-react-pill:has(.sv-react-who) { border-radius: var(--sv-radius); }

.sv-react-who {
    margin-top: 2px;
    padding-top: var(--sv-space-2);
    border-top: 1px solid var(--sv-border);
    white-space: normal;
    text-align: left;
}

.sv-react-who-line {
    display: flex;
    align-items: baseline;
    gap: var(--sv-space-2);
    padding: 1px 0;
}

.sv-react-who-names {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    line-height: 1.35;
    overflow-wrap: break-word;
    min-width: 0;
}

/* Bigger inside the picker: this is the moment somebody is choosing one. */
.sv-react-pill .sv-react-btn {
    padding: 2px 6px;
    border-color: transparent;
    background: none;
}

/* Eight glyphs are wider than a phone, and the pill hangs off the left of the trigger, so what
   runs off the screen is always its right-hand end. On a narrow screen it becomes two rows and
   loses the pill radius with them; on anything wider it keeps its single row. */
@media (max-width: 560px) {
    .sv-react-pill {
        flex-wrap: wrap;
        justify-content: center;

        /* width as well as max-width, and this is the whole trick: the pill is positioned
           against .sv-react, which is only as wide as the trigger, so an auto width wraps to
           that and comes out one glyph per row. max-content asks for the single row it would
           have had, and max-width is then what breaks it into two. */
        width: max-content;
        max-width: 78vw;
        white-space: normal;
        border-radius: var(--sv-radius);
    }
}

.sv-react-pill .sv-react-glyph { font-size: var(--sv-fs-xl); }
.sv-react-pill .sv-react-btn:hover .sv-react-glyph { transform: scale(1.15); }
.sv-react-pill .sv-react-glyph { transition: transform 0.1s ease; display: inline-block; }

.sv-post-actions {
    display: flex;
    align-items: center;

    /* Tighter than the card's other rows: the brackets already separate them, and a wide gap
       on top of those reads as four things rather than one set. */
    gap: var(--sv-space-2);
    margin-left: auto;
}

/* A button that looks like a link, because pinning and removing change something and a real
   link would be followed by a crawler or a prefetch.

   Small and bracketed. These are the card's controls rather than its content - what somebody
   came to read is the post - so they sit quietly in the corner until wanted. 11px has no token
   of its own: --sv-fs-xs is the smallest and is still 12. */
.sv-post-link {
    padding: 0;
    border: 0;
    background: none;
    color: var(--sv-link);
    font: inherit;
    font-size: 0.6875rem;
    letter-spacing: 0.02em;
    cursor: pointer;
    text-decoration: none;
}

/* Brackets in CSS rather than in the markup, so the button's own text stays the word - which is
   what a screen reader announces and what a test looks for. */
.sv-post-link::before { content: "["; }
.sv-post-link::after  { content: "]"; }

.sv-post-link::before,
.sv-post-link::after { color: var(--sv-text-faint); }

.sv-post-link:hover  { color: var(--sv-link-hover); text-decoration: none; }
.sv-post-link:hover::before,
.sv-post-link:hover::after { color: var(--sv-link-hover); }

.sv-post-link:disabled { opacity: 0.5; cursor: default; }
.sv-post-link-danger { color: var(--sv-danger); }
.sv-post-link-danger:hover { color: var(--sv-danger); }
.sv-post-link-danger:hover::before,
.sv-post-link-danger:hover::after { color: var(--sv-danger); }

.sv-feed-pager {
    display: flex;
    justify-content: space-between;
    gap: var(--sv-space-3);
    margin-top: var(--sv-space-4);
}

.sv-feed-pager .sv-btn:only-child { margin-left: auto; }

@media (max-width: 560px) {
    .sv-post-depth-1, .sv-post-depth-2, .sv-post-depth-3 { margin-left: var(--sv-space-3); }
    .sv-post-actions { margin-left: 0; }
}

/* ---------------------------------------------------------- responsive --- */

@media (max-width: 900px) {
    #container {
        grid-template-columns: minmax(0, 1fr);
        /* Sidebar above content when stacked: it is the small "welcome / logout / my account"
           card, so it costs almost no scroll, and this keeps the visual order the same as the
           source order in Site.Master (sidebar then content) - which is what the tab and
           screen-reader order follows too. */
        grid-template-areas:
            "header"
            "sidebar"
            "content";
        padding: var(--sv-space-3);
        gap: var(--sv-space-3);
    }
    .header { padding-bottom: var(--sv-space-3); }
    .header .sv-nav { margin-left: 0; width: 100%; }
    #headernav { justify-content: flex-start; }
    /* two lockups plus nav is too much for a stacked header */
    .sv-brand-link { display: none; }
    .contentblock, .sv-card, .sv-chart-card { padding: var(--sv-space-4); border-radius: var(--sv-radius); }
    ul.dv li { grid-template-columns: minmax(0, 1fr); gap: var(--sv-space-1); }
    ul.dv li span { margin-bottom: 0; }
    table.calendar td, table.sv-cal td,
    table.calendar td.othermonth, table.sv-cal td.othermonth { height: 84px; }
}

@media (max-width: 640px) {
    :root { --sv-fs-base: 0.9375rem; }
    .header { justify-content: center; text-align: center; }
    .header img { max-height: 44px; }
    .sv-logo { width: 208px; }
    table.quicktable { font-size: var(--sv-fs-xs); }
    table.quicktable th, table.quicktable td { padding: var(--sv-space-2); }
    /* calendar becomes a compact agenda-style grid rather than 7 tiny columns */
    table.calendar td, table.sv-cal td,
    table.calendar td.othermonth, table.sv-cal td.othermonth { height: 64px; padding: 2px; }
    .sv-evt { font-size: 10px; padding: 2px 4px; }
    .sv-evt-time, .sv-evt-meta { display: none; }
    .sv-cal-title { font-size: var(--sv-fs-lg); }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
    .buttons a:hover, .buttons button:hover, .sv-btn:hover,
    ul.dashboardimg a:hover, .sv-theme-toggle:hover { transform: none; }
}

@media print {
    body { background: #fff; color: #000; }
    .sidebar, #headernav, .sv-theme-toggle, .sv-chart-tabs, #footer { display: none !important; }
    #container { display: block; max-width: none; padding: 0; }
    .contentblock, .sv-card, table.quicktable { border-color: #999; box-shadow: none; }
}

/* --- breadcrumbs ---------------------------------------------------------- */

/* Where you are, and the way back up. Every admin listing is the same page under a different
   command, so without this the browser's Back button was the only way out - and Back is not
   navigation, it is undo. */
.sv-crumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sv-space-2);
    margin-bottom: var(--sv-space-4);
    font-size: var(--sv-fs-sm);
}

.sv-crumbs a,
.contentblock .sv-crumbs a {
    color: var(--sv-link);
    text-decoration: none;
}

.sv-crumbs a:hover { text-decoration: underline; }

.sv-crumb-sep { color: var(--sv-text-faint); }

/* The page you are on, which is the heading as well as the last crumb - so it carries the weight
   the old <strong>Dashboard</strong> line used to. */
.sv-crumb-here {
    color: var(--sv-heading);
    font-weight: 650;
}

/* --- the admin dashboard's front door ------------------------------------ */

/* One card per thing an admin can go and do. auto-fill rather than a fixed count, so the row
   holds as many as the card is wide enough for and stacks to one on a phone without a breakpoint
   having to know how many there are. */
.sv-admin-menu {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: var(--sv-space-4);
    margin-top: var(--sv-space-4);
}

/* a.sv-admin-card, not .sv-admin-card: ".contentblock a" sets the link colour and would otherwise
   outrank a single class, painting the whole card link-blue. Same shape as .contentblock a.sv-btn
   above, for the same reason. */
.contentblock a.sv-admin-card,
.contentblock a.sv-admin-card:visited,
.contentblock a.sv-admin-card:active,
.sv-admin-card {
    display: flex;
    align-items: flex-start;
    gap: var(--sv-space-3);
    padding: var(--sv-space-4);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-lg);
    background-color: var(--sv-surface-2);
    color: var(--sv-text);
    text-decoration: none;
    transition: border-color var(--sv-transition), background-color var(--sv-transition),
                transform var(--sv-transition), box-shadow var(--sv-transition);
}

.contentblock a.sv-admin-card:hover,
.sv-admin-card:hover {
    border-color: var(--sv-accent);
    background-color: var(--sv-surface-3);
    color: var(--sv-text);
    text-decoration: none;
    transform: translateY(-2px);
    box-shadow: var(--sv-shadow-2);
}

.sv-admin-card-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 40px;
    height: 40px;
    border-radius: var(--sv-radius);
    background-color: var(--sv-accent-soft);
    color: var(--sv-accent);
}

.sv-admin-card-icon svg { display: block; width: 22px; height: 22px; }

/* min-width:0 so a long word wraps inside the card instead of widening the grid column. */
.sv-admin-card-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.sv-admin-card-title {
    color: var(--sv-heading);
    font-size: var(--sv-fs-md);
    font-weight: 650;
}

.sv-admin-card-blurb {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    line-height: 1.35;
}

/* --- the admin listing's row menu ---------------------------------------- */

/* A <details> rather than a scripted popup: the browser already knows how to open it, close it,
   reach it by keyboard and read it out. js\sv-admin-games.js adds the one thing it will not do
   by itself, which is close when the pointer goes somewhere else. */
.sv-rowmenu {
    position: relative;
    display: inline-block;
}

.sv-rowmenu > summary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: var(--sv-radius);
    color: var(--sv-text-muted);
    cursor: pointer;
    font-size: 18px;
    line-height: 1;
    user-select: none;

    /* The disclosure triangle every browser draws in front of a summary. The kebab IS the
       affordance here, and a triangle beside it says the same thing twice. */
    list-style: none;
}

.sv-rowmenu > summary::-webkit-details-marker { display: none; }
.sv-rowmenu > summary::marker { content: ""; }

.sv-rowmenu > summary:hover,
.sv-rowmenu[open] > summary {
    background: var(--sv-surface-3);
    color: var(--sv-text);
}

/* Both the table and the card it sits in are overflow:hidden - that is what keeps the sticky
   header and the striped cells inside the rounded corners - so a menu on one of the last rows
   would be cut off at the card's edge. js\sv-admin-games.js switches this to position:fixed on
   open and places it against the kebab's own rectangle, flipping above the row when the window
   runs out underneath. Nothing clips a fixed box here: no ancestor carries a transform, a filter
   or contain, which are the only things that would pull it back inside.

   The absolute placement below is the fallback for a page whose script has not run, where being
   clipped is no worse than being absent. */
.sv-rowmenu-items {
    position: absolute;
    top: 100%;
    right: 0;
    z-index: 20;
    min-width: 11rem;
    margin-top: 4px;
    padding: var(--sv-space-1);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius);
    background: var(--sv-surface-2);
    box-shadow: var(--sv-shadow-2);
    text-align: left;
}

/* Right-hand column, so the menu opens inward rather than off the edge of the table. */
.sv-rowmenu-item {
    display: block;
    width: 100%;
    padding: var(--sv-space-2) var(--sv-space-3);
    border: 0;
    border-radius: var(--sv-radius-sm, 4px);
    background: none;
    color: var(--sv-text);
    cursor: pointer;
    font: inherit;
    font-size: var(--sv-fs-sm);
    text-align: left;
    white-space: nowrap;
}

.sv-rowmenu-item:hover { background: var(--sv-surface-3); }

.sv-rowmenu-item.is-danger { color: var(--sv-danger); }

/* Drawn but not offered: an admin who looks for an action and simply cannot find it learns
   nothing, where one who sees it greyed with a reason learns why. */
.sv-rowmenu-item.is-off {
    color: var(--sv-text-faint);
    cursor: not-allowed;
}

.sv-rowmenu-item.is-off:hover { background: none; }

/* The unfinished-games listing. The class is on the wrapper, not the table: QuickTable writes its
   own class onto the table as it renders and loses any set in the page. The night is one fact -
   "Fri, Jun 5, 2026 6:00 PM" - and a column narrow enough to break it into five lines makes a row
   four times taller to no purpose.

   The .title and .nodata cells are excluded from both rules: each is the only cell in its row, so
   it is the first child and the last child at once, and without this the table's own caption came
   out right-aligned and refusing to wrap. */
.sv-stale-games table.quicktable td:first-child:not(.title):not(.nodata) { white-space: nowrap; }

/* The kebab sits at the end of the row and wants no more room than it needs. */
.sv-stale-games table.quicktable td:last-child:not(.title):not(.nodata),
.sv-stale-games table.quicktable th:last-child { text-align: right; white-space: nowrap; }

/* --- calendar: getting to another year ----------------------------------- */

/* Reaching March 2019 used to be 114 taps of the month arrow. A month and a year beside the
   title go straight there. */
.sv-cal-jump {
    display: inline-flex;
    justify-content: center;
    gap: var(--sv-space-2);
    vertical-align: middle;
}

/* width:auto against the global form rule, which puts every select at 100% of whatever holds it -
   here an inline-flex, so the months and the years both came out the same width and "November"
   was cut to "Novemb". Left to itself a select takes the width of its longest option, which is
   what a picker of month names wants. */
/* These two ARE the title now - there is no "October 2026" written beside them - so they take the
   heading's size and colour. Anything quieter and the month stops being the first thing read. */
.sv-cal-jump select {
    width: auto;
    padding: var(--sv-space-1) var(--sv-space-2);
    border: 1px solid var(--sv-border-strong);
    border-radius: var(--sv-radius);
    background-color: var(--sv-surface-2);
    color: var(--sv-heading);
    font-family: inherit;
    font-size: var(--sv-fs-lg);
    font-weight: 650;
    letter-spacing: -0.01em;
    cursor: pointer;
}

.sv-cal-jump select:hover { border-color: var(--sv-accent); }

/* Too narrow for all three across: the month and its two selects take a line of their own above,
   and the two arrows keep the line below, one at each end. */
@media (max-width: 720px) {
    .sv-cal-nav { grid-template-columns: 1fr 1fr; }
    .sv-cal-title { grid-column: 1 / -1; order: -1; }
    .sv-cal-nav > .buttons:last-child { justify-self: end; }
}

/* The title carries the selects now, so it stops being one long unbreakable line on a phone. */
@media (max-width: 560px) {
    .sv-cal-title { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--sv-space-2); }
    .sv-cal-jump { margin-left: 0; }
}
