
body, input,td,th,button, select, textarea {
    font-size:min(5.2vmin - 0.5rem, 1.8rem);
    font-family:sans-serif;
}
body {
    margin:0;
    padding:0;
    /* Normal wrapping only breaks at spaces - one long unbroken word (e.g. in a director-entered
       game/section name) can still force its container, and from there the whole page, wider than
       the viewport with nothing to stop it. That's the actual root cause behind #menuHolder's
       position:fixed;right:... sometimes landing off-screen: when the page overflows the visual
       viewport, some mobile browsers compute fixed positioning against the wider layout viewport
       instead. overflow-wrap (rather than overflow-x:hidden on html) fixes the cause instead of
       hiding the symptom - it only ever breaks a word that has nowhere else to break, so ordinary
       text is untouched and nothing becomes inaccessible. */
    overflow-wrap: anywhere;
}
button {
    padding: min(8px, 1.5vmin);
    background:purple;
    border:0;
    color:white;
    cursor:pointer;
    position:relative;
    border-radius: min(12px, 3vmin);
    white-space:nowrap;
}
dialog, [popover] {
    white-space:nowrap;
    width:fit-content;
    background:white;
    z-index:5;
    border-radius:12px;
    overflow:visible;
}
/* :where(:not([popover])) matters here - without it, a popover that gets reparented into another
   open popover (e.g. #newReservation moving into reservation-list/#editClub - see
   ReservationPopover.editReservation()/addRegular()) would itself match as a plain "div inside a
   popover" and get this padding instead of its own (from :popover-open), since this rule's
   specificity beats that pseudo-class regardless of source order. :where(...) rather than a bare
   :not([popover]) specifically - :not() carries the specificity of its argument (making this rule
   (0,2,1) instead of (0,1,1), enough to start beating other same-specificity rules like
   div.headerDiv's text-align:center that used to win via source order alone), while :where()
   always contributes zero regardless of what's inside it, so this stays at the original (0,1,1). */
dialog div, [popover] div:where(:not([popover])) {
    text-align:left;
    padding:3px;
}
:popover-open {
    min-width:250px;
    background:white;
    border: 2px solid #333;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    padding-left: 1vw;
    padding-right: 1vw;
}


/* general pop-up menu behavor */
/* usage: <menu-frame><select>...</select></menu-frame> */
select {
    appearance: none;
    border:0;
    background-color: white;
    padding-right:40px;
}
select:focus {
    outline:none;
}
option {
    padding:0 10px;
}
menu-frame {
    border: thin gray solid;
    border-radius: 6px;
    padding: 1px 10px;
    position:relative;
}
menu-frame > div.transparent {
    position:absolute;
    bottom:0;
    right:0;
    height:100%;
    pointer-events: none;
    padding:0 !important;
}
menu-frame > div.transparent::before {
    content:' \25BE';
    padding-right:10px;
}



/* Useful classes */
.small {
    font-size:50%;
}
.red-border {
    border-color:red;
}
.red {
    color:#e55;
}
.gray {
    opacity:.5;
}
.hidden {
    display:none !important;
}
@media (max-width:1000px) {
    .nophone {
        display:none;
    }
}
.director .nodirector,
body:not(.director) .directorOnly {
    display:none;
}


/* General input appearance */
input {
    background-color: #fffbff;
    border:thin solid;
    padding-left:5px;
    color:purple;
    font-size:clamp(16px, -.5rem + 5.333vmin, 1.8rem);
}
input:focus {
    outline:none;
    outline-style: double;
    outline-offset: -1px;
    outline-color:#a64da6;
    color:black;
}
input.red-border:focus {
    outline-color: red;
}
input:not(.red-border) {
    border-color:purple;
}
input.hideText {
    display: none;
}
input::placeholder {
    color:lightgray;
}
input:not([type]):disabled {
    opacity:0.5;
    outline-color:gray;
    border-color:gray;
}
input:invalid {
    border-color:red;
    outline-color:red;
}


/* Checkbox scaling */
input[type="checkbox"] {
    --scaling-factor: clamp(
        1,                                            /* minimum (when width < 500px) */
        calc((100vmin - 500px) / (1000px - 500px) + 1),  /* calculated value */
        2                                            /* maximum (when width > 1000px) */
    );
    transform: scale(var(--scaling-factor));
    vertical-align: middle;
}




/* Overall appearance */
#body {
    max-width:1000px;
    margin:auto;
}
/* padding-bottom is set dynamically, per the actual opened list's height, by
   ViewReservations.click() in CustomButton.js - see hideReservations() in pabc.js too */



/* Notify user with a 3-second message */
.notification {
    /* Cancels the popover UA default stylesheet's inset:0/margin:auto (spec'd centering) before
       applying our own top/left below - otherwise the unset right/bottom insets plus margin:auto
       auto-center the toast instead of pinning it near the top. */
    inset:auto;
    margin:0;
    position:fixed;
    top: -100px;
    width:calc(90vw - 50px);
    left:5vw;
    z-index:25;
    padding:25px;
    text-align: center;
    box-shadow: 0 0 5px 2px purple;
    background-color: white;
    color:purple;
    border:solid purple thin;
    border-radius: 12px;
    opacity: 1;
    transition: top 0.3s ease-out, opacity 0.8s ease;
}
/* resting position (top: 20px equivalent) is computed in JS from window.visualViewport - see
   notify() in pabc.js */
.notification-fade-out {
    opacity: 0;
}

/* Points at a specific invalid field, positioned via getBoundingClientRect() in
   CustomPopover.showFieldTooltip() - a lower-level relative of .notification for messages tied
   to one field rather than shown as a general toast. It's a manual popover itself (see
   showFieldTooltip()) so it renders above whatever [popover] the field lives in; position:fixed
   and margin:0 override the [popover] UA stylesheet's default inset:0/margin:auto centering.
   right/bottom:auto neutralize the UA's inset:0, which otherwise stays in effect alongside our
   own top/left and stretches the box's auto-computed width out to the viewport's right edge. */
.field-tooltip {
    position: fixed;
    margin: 0;
    right: auto;
    bottom: auto;
    box-sizing: border-box;
    z-index: 25;
    padding: 6px 10px;
    /* width itself stays fit-content (the [popover] UA default) so short messages don't look
       artificially boxy; max-width is set inline in showFieldTooltip(), computed from the
       actual space available to the right of the field rather than a fixed CSS value */
    /* overrides the broad "dialog, [popover] {white-space:nowrap}" rule above, which otherwise
       applies here too since this is itself a [popover] (see showFieldTooltip()) - that rule
       suppresses wrapping at spaces entirely, so a full sentence just runs off the right edge
       regardless of width/word-wrap */
    white-space: normal;
    word-wrap: break-word;
    box-shadow: 0 1px 4px rgba(0,0,0,0.3);
    background-color: white;
    color: red;
    border: 1px solid black;
    border-radius: 8px;
    font-size: 70%;
    opacity: 1;
    transition: opacity 0.8s ease;
}
.field-tooltip.fade-out {
    opacity: 0;
}

/* Persists until refreshed or dismissed - unlike .notification, doesn't auto-fade */
#versionBanner {
    position: fixed;
    top: 20px;
    width: calc(90vw - 50px);
    left: 5vw;
    z-index: 25;
    padding: 15px 25px;
    text-align: center;
    box-shadow: 0 0 5px 2px purple;
    background-color: white;
    color: purple;
    border: solid purple thin;
    border-radius: 12px;
}
#versionBanner button {
    margin-left: 10px;
}


/* Director list (EditClubPopover, EditGamePopover, EditDirectorPopover in CustomPopover.js) */
.directorList .directorRow {
    padding: 0;
}
/* Shared pencil.png "edit" icon - used here, in #renameClubButton, and in #mergeNames */
.edit {
    cursor: pointer;
    vertical-align: middle;
    width: min(40px, 6vmin);
    margin-left: 4px;
    padding:0;
}
/* plus.png is a near-white outline, invisible on the popover's light background without a
   backdrop - pencil.png is already solid purple, so it needs no such treatment */
.addDirector {
    cursor: pointer;
    vertical-align: middle;
    width: min(32px, 5vmin);
    margin-left: 4px;
    padding: 4px;
    background: purple;
    border-radius: 4px;
    box-sizing: border-box;
}


/* Context menu */
.contextMenu {
    position:absolute;
    left:20px;
    top:75%;
    z-index:10;
    background:white;
    border-width:1px 3px 3px 1px; 
    color:purple;
    font-size:smaller;
    border-style: solid;
    border-color:black;
    padding:0;
    cursor:pointer;
    text-wrap: nowrap;

    & div {
        padding:3px 8px;
        &:hover {
            background-color: #eee;
        }
        @media (max-width:500px) {
            font-size:larger;
        padding: 2px 8px;
        }
    }
    & div.clicked {
        animation: flash-twice-reverse 0.5s;
    }
}



/* Contains the action menu and the help icons */
#menuHolder {
    position:fixed;
    top:3px;
    right: min(25px, 2vw);
    background:white;
    z-index:10;
    display: flex;
    /* #help and #menuItems are DOM children here but position:absolute in their own rules, so
       they're pulled out of flex layout entirely - the only real flex items are #updateWarning,
       #lastUpdated, #helpLink, #menuLink, and none of them actually need to fill the row's full
       height (no vertical-centering trick keyed off it). center rather than the stretch default -
       stretch was quietly forcing #helpLink/#menuLink taller than their own 35x35 icon, which is
       what broke their square shape; see the (now-redundant) align-self:center note below. */
    align-items:center;
    column-gap: 3px;
}

/* Update warning indicator */
#updateWarning {
    padding:1px 1vmin;
    opacity: 0;
    transition: opacity 0.5s ease-in;
    transition-delay: 60s;
    cursor: default;
    pointer-events: none;

    & img {
        width:min(35px,7vmin);
        height:min(35px,7vmin);
        display:block;
    }
    &.active {
        opacity: 1;
    }
    &[title] {
        cursor: help;
        pointer-events: auto;
    }
    &.reset {
        opacity: 0;
        transition: none;
    }
}
#lastUpdated {
    font-size: clamp(10px, 3vmin, 14px);
    color: gray;
    padding: 4px 1vmin;

    @media (max-width: 600px) {
        display: none;
    }
}


/* Action menu and help icons */
#menuLink, #helpLink {
    font-size: clamp(12px, 6vmin, 32px);
    padding:2px;
    border:3px purple solid;
    cursor:pointer;

    &.open {
        background-color: lightgray;
    }

    & img {
        width:min(35px,7vmin);
        height:min(35px,7vmin);
        display:block;
    }
}



/* Menu items */
#menuItems {
    position:absolute;
    /* Same formula/reasoning as #help's top - see its comment. right:0 already lands exactly on
       #menuLink's own right edge for free, since it's the last flex item in #menuHolder and thus
       always flush against the container's own right edge - no formula needed for that side. */
    top: calc(min(35px, 7vmin) + 8px);
    right:0;
    display:none;
    border:thin solid gray;
    z-index:10;

    #menuLink.open ~ & {
        display:block;
    }
    & a {
        text-decoration: none;
        color:white;
    }
    & div {
        background-color: purple;
        color:white;
        display:none;
        border:thin solid gray;
        padding:4px 9px;
        cursor: pointer;
        white-space: nowrap;
        text-align: right;
        &:hover {background-color: plum;}
        &.clicked {animation: flash-twice 0.5s;}
        .director &.directorOnly {display:block;}
        body:not(.director) &.public {display:block;}
    }
}

@keyframes flash-twice {
    0%   {background-color: plum;}
    20%  {background-color: purple;}
    40%  {background-color: plum;}
    60%  {background-color: purple;}
    80%  {background-color: plum;}
    100% {background-color: purple;}
  }
/* Same oscillation as flash-twice, colors swapped - .contextMenu div's resting background is
   white (not plum, like #menuItems div's hover state is), so starting flash-twice's own sequence
   there was an abrupt white->plum snap rather than a continuation of an already-plum surface.
   Also swaps text color at each step - .contextMenu sets color:purple (unlike #menuItems div,
   which uses white text), so purple text would go invisible against the purple background frames
   without this. */
@keyframes flash-twice-reverse {
    0% {
      background-color: purple;
      color: white;
    }
    20% {
      background-color: plum;
      color: white;
    }
    40% {
      background-color: purple;
      color: white;
    }
    60% {
      background-color: plum;
      color: white;
    }
    80% {
      background-color: purple;
      color: white;
    }
    100% {
        background-color: plum;
        color: purple;
    }
  }


/* Help item */
#help {
    position:absolute;
    /* Both formulas mirror #helpLink's own border-box geometry (icon + 2px padding + 2px border,
       each side) rather than a snapshot from getBoundingClientRect() (formerly set in JS by
       toggleHelp(), now just toggles the .open class) - #helpLink/#menuLink are #menuHolder's
       tallest flex items, so with align-items:center they span its full height with zero
       centering offset, and #menuLink is the last one, flush against the container's own right
       edge - so top lands on #helpLink's bottom edge (minus 2px, matching the JS version's own
       empirical tuck under the icon's border), and right accounts for #menuLink's own width (same
       formula) plus the column-gap (3px) between the two icons. */
    right: calc(min(35px, 7vmin) + 13px);
    top: calc(min(35px, 7vmin) + 8px);
    width:70vw;
    background:white;
    z-index: 10;
    font-size:clamp(9px, 3vmin, 24px);
    color: black;
    font-weight:normal;
    border: purple 2px solid;
    padding: 5px;

    display:none;
    #helpLink.open ~ & {display:block;}


    & ul {
        padding-left: min(25px, 5vw);
        margin-bottom:5px;
    
        & li {
            padding-bottom: 6px;
            &.indented {margin-left:4vw;}
        }
    }
    & img {
        height:30px;
        max-height:6vmin;
        transform:translateY(5px);
    }
    #closeHelp {
        float:right;
    }
}

#videoHelp {
    padding:3px;
    max-height:80vh;
    overflow-y: scroll;
}
video {
    max-height: 75vh;
}




/* Top banner */

#logo {
    height: clamp(50px, calc(50px + ((100vh - 700px) / 4)), 100px);
    margin-right:min(25px, 2vmin);
    float:left;
    margin-bottom:10px;
    margin-left:2px;
    animation: temp-glow 0.7s linear;
    animation-delay:3.6s;
}
@keyframes temp-glow {
    0% {
        filter:blur(0);
        opacity:1;
   }
    50% {
        filter:blur(2px);
        opacity:0.5;
    }
    100%    {
        filter:blur(0);
        opacity:1;
    }
}

h2#topHeader {
    margin-top:5px;
    padding-top:5px;
    margin-bottom:0;
}

h2#topHeader {
    color:transparent;
    -webkit-text-fill-color: transparent;
    background: linear-gradient(
        -25deg,
        purple,
        purple,
        #FF0000, /* Red */
        #FFA500, /* Orange */
        #FFFF00, /* Yellow */
        #008000, /* Green */
        #0000FF, /* Blue */
        #4B0082, /* Indigo */
        black,
        black
    );
    background-size: 1000% 1000%;
    -webkit-background-clip: text;
    background-clip: text;
    animation: gradient-shift 3s linear forwards;
    animation-delay:2s;
}

/* this gives a rainbow sweep effect to the text */
@keyframes gradient-shift {
  0% {
    background-position: 0% 0%;
  }
  100% {
    background-position: 100% 100%;
  }
}


/*  Modal dialog replaces alert, confirm, prompt */
custom-modal {
    .message {
        overflow-y:scroll;
        max-height:85vh;
        white-space:normal;
        max-width:min(24em, 85vw);
    }
    &.confirm .inputDiv, &.alert .inputDiv {
        display:none;
    }
    &.alert button[value="cancel"] {
        display:none;
    }

}

/*  Cancel reservations places the "keep" button with the value "cancel" in the OK position.  */
cancel-reservation-modal button[value="cancel"] {
    float:right;
    margin-left:3px;
}
#viewEmails .message {
    overflow-y:scroll;
    max-height:85vh;
}



/* Table showing upcoming games */
table {
    border-spacing:0;
    border-color:lightgray;
    border-style:solid;
    border-width:thin 0 thin thin;
    margin-top:10px;
    width:100%;
}
tr:nth-child(odd) {
    background-color: rgba(128,0,128,0.05);
}
tr[data-special*="No game"] {
    & td {opacity:0.35;}
    & td:last-child * {display:none;}
    .tableFee {display:none;}
}

/* Filtering by day, sectional, or individual player */
table[data-day="1"] tr[data-day]:not([data-day="1"]),
table[data-day="2"] tr[data-day]:not([data-day="2"]),
table[data-day="3"] tr[data-day]:not([data-day="3"]),
table[data-day="4"] tr[data-day]:not([data-day="4"]),
table[data-day="5"] tr[data-day]:not([data-day="5"]),
table[data-day="6"] tr[data-day]:not([data-day="6"]),
table[data-day="7"] tr[data-day]:not([data-day="0"]),
table[data-day="sectional"] tr[data-day]:not([data-sectional]),
table[data-day="my"] tr[data-day]:not([data-my="1"])
 {
    display:none;
}

/* Hide overflow games (beyond 7 days) until "Show more" is clicked */
table:not(.show-all) tr.overflow {
    display:none;
}
#showMore {
    display: block;
    width: 100%;
    margin: 0;
    padding: 2px;
    border-radius: 0;
    background-color: rgba(128,0,128,0.05);
    color: gray;
    font-size: 60%;
}



/* Table cells */
td {
    border:solid lightgray;
    border-width: thin thin 0 0;
    padding:1vmin;
    vertical-align: middle;

    tr:not(:first-child) & {
        border-top: 1px solid rgba(128, 0, 128, 0.15);
    }

    /* date column */
    &:first-child {
        border-left: 4px solid transparent;
        tr:hover & {
            border-left-color:purple;
        }
        .dow {
            font-size:larger;
            font-weight: bold;
        }
       .timeCell {
            color:purple;
            margin-right:1vw;
            text-align: right;
            @media (min-width:600px) {
                display:none;
            }
           
        }
        table:not([data-day="my"]) &.sameDate div:first-child {
            display:none;
        }
    }

    /* time column, hidden on mobile */
    &:nth-child(2) {
        color:purple;
        font-size: 80%;
        @media (max-width:600px) {display:none;}
       
    }

    /* game name/description column */
    &:nth-child(3) {
        position: relative;
 
        .gameName {
            display:flex;
            flex-direction: column;
            justify-content: center;
            height:100%;
            position: relative;
            /* add some padding-right to allow for table fee when visible */
            .director & > div:not(.contextMenu),
            body:not(.director) tr:not(.overflow) & > div:not(.contextMenu) {
                padding-right:clamp(24px, 4vw, 60px);
            }
       }
        .special {
            color:purple;
        }

        .tableFee {
            position: absolute;
            top: 0;
            right: 0;
            color: purple;
            padding:1px 5px;
            font-size: 0.85em;
            body:not(.director) tr.overflow & {
                display:none;
            }
       }
   }

    &:not(:nth-child(3)) {
        text-wrap: nowrap;
    }
    /* buttons/reservation-list column */
    &:last-child {
        position:relative;
    }

}
/* td:first-child {
    border-left: 4px solid transparent;
}
tr:hover td:first-child {
    border-left-color: purple;
} */
/* tr:not(:first-child) td {
    border-top: 1px solid rgba(128, 0, 128, 0.15);
} */
/* td:not(:nth-child(3)) {
    text-wrap:nowrap;
} */
/* td:first-child .timeCell {
    color:purple;
    margin-right:1vw;
    text-align: right;
    @media (min-width:600px) {
        display:none;
    }
} */
/* td:nth-child(2) {
    color:purple;
    font-size: 80%;
    @media (max-width:600px) {display:none;}

} */
/* .director td:nth-child(3) {
    padding-right:0;
} */

/* table:not([data-day="my"]) .sameDate div:first-child {
    display:none;
} */

/* Make day of week more prominent */
/* .dow {
    font-size:larger;
    font-weight: bold;
} */


/* Game description cell */
/* td:nth-child(3) {
    position: relative;
} */
/* .gameName {
    display:flex;
    flex-direction: column;
    justify-content: center;
    height:100%;
    position: relative;
}
.special {
    color:purple;
}

.tableFee {
    position: absolute;
    top: 0;
    right: 0;
    color: purple;
    padding:1px 5px;
    font-size: 0.85em;
} */
/* body:not(.director) tr.overflow .tableFee {
    display:none;
} */
/* .director .gameName > div:not(.contextMenu),
body:not(.director) tr:not(.overflow) .gameName > div:not(.contextMenu) {
    padding-right:clamp(24px, 4vw, 60px);
} */


/* Final cell, contains buttons */
/* #gameList tr td:last-child {
    position:relative;
    text-wrap: nowrap;
 } */





.director view-reservations img:not(.director) {
    display:none;
}
body:not(.director) view-reservations img.director {
    display:none;
}


reserve-button, partner-finder-button, view-reservations, contact-button {
    font-size:clamp(10px, 2.5vmin, 13px);
    color:black;

    display:inline-block;
    cursor:pointer;
    text-align:center;
    vertical-align: bottom;

    padding:min(5px,1vmin);
    width:50px;
    max-width:10vmin;
    margin:calc(1px + .2vmin);

    border-radius: 12px;

    transition:background-color 0.7s ease;
    background-color: rgba(128,0,128,0);
    &:hover {
        background-color:rgba(128,0,128,0.2);
    }
    tr:nth-child(even) & {
        background-color: rgba(128,0,128,0.1);
        &:hover {
            background-color:rgba(128,0,128,0.2);
        }
    }

    &.reserved .not-reserved,
    &:not(.reserved) .reserved {
        display:none;
    }
    img {
        width:min(45px,7vmin);
    }
    div:first-child {
        line-height:0;
    }
}
/* tr:nth-child(even) {
    & reserve-button, & partner-finder-button, & view-reservations, & contact-button {
        background-color: rgba(128,0,128,0.1);
        &:hover {
            background-color:rgba(128,0,128,0.2);
        }
    
    }
} */
view-reservations img {
    vertical-align: middle;
    transform: translateX(1px);
}
partner-finder-button img {
    transform: translateY(-2px);
}


.director view-reservations.updated {
    background: linear-gradient(0deg, rgba(128,0,128,0), rgba(128,0,128,0.2), rgba(128,0,128,0), rgba(128,0,128,0.2));
    background-size: 100% 300%;
    animation: gradient 3s linear infinite;
}

@keyframes gradient {
	0% {
		background-position: 0% 0%;
	}
	100% {
		background-position: 0% 100%;
	}
}



/* reservation list */
reservation-list {
    padding: 0.25em 1vw; /* matches the standard popover padding (UA-default top/bottom, :popover-open's left/right) that .headerDiv's ::before bleed is calibrated to cancel */
    background:white;
    z-index:5;
    color:black;
    width:fit-content;
    white-space:nowrap;
    /* iOS Safari auto-boosts font-size per text block based on its rendered width vs. the
       viewport; the Singles row (a full-width block, unlike the grid-columned sections) can get
       boosted differently than the rest of the list, especially in landscape. Not reproducible in
       desktop simulators since it's an on-device WebKit heuristic, not a CSS media feature. */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;

    &:not([popover]) {
        display:none;
        position:absolute;
        top:90%;
        right:15px;
        padding:0;
        border:solid gray 2px;
        border-bottom-width:4px;
        border-right-width:4px;
        .empty, .headerDiv, .seatingButtons {
            display:none;
       }
    }
    /* the non-popover list is hidden above until its cell is opened - the everyday non-director
       "View list" dropdown (ViewReservations.click()'s else branch adds .open to the td) */
    td.open & {
        display:block;
        max-width:90vw;
    }

    & h3 {
        display:none;   /* only shows on print */
    }

    .NSEW {    /* only shows on print */
        display:none;
        font-weight:bold;
    }

    .rightClickMessage {
        color:gray;
        text-align: right;
        font-size: 60%;
        font-style: italic;
        background-color: #eee;
        padding-right:5px;
        .director & { display:none; }   /* directors don't need the how-to-cancel hint */
        @media (max-width:1000px)  {
            #rightclick {
                display:none;
            }
        }
        @media (min-width:1000px) {
            #mobiletap {
                display:none;
            }
        }
    }

    .directionKey {
        display:inline-block;
        .swiss & { display:none; }   /* team games have no N-S/E-W split */
        @media print {
            display:none;
        }
        .N-S, .E-W {padding: 0 2px;}
        .N-S {background-color: #eef;}
        .E-W {background-color: #efe;}
    }

    /* buttons in seating dialog */
   .print, .clipboard, .emails, .newsection {
        --button-size: clamp(20px, calc(20px + (100vmin - 600px) / 100), 38px);
        display: inline-flex;
        align-items: center;
        justify-content: center;    

        &::after {
            content: "";
            background-repeat:no-repeat;
            background-position: center;
            background-size: var(--button-size) var(--button-size);
            height: var(--button-size);
            width: calc(var(--button-size) * 1.25);
            display: inline-block; 
            vertical-align: bottom;
            padding-bottom:3px;
        }
    }

    .print::after {background-image: url('images/print.png');}
    .clipboard::after {background-image: url('images/copy.png');}
    .emails::after {background-image: url('images/mail.png');}
    .newsection::after {background-image: url('images/plus.png');}

    /* The wrapper: one section's header plus its names list. Carries all the section's data, so
       state-driven rules live here and the header only styles the label. */
    .sectionBlock {
        /* A pseudo-section bucket with nothing in it hides itself, header and all. Was three
           identical rules keyed on .singles/.unknown/.waiting; .pseudoSection (Game::sectionBlock())
           makes it one. Not extended to .deletedSection, which has its own show/hide below and
           stays visible when empty. Now a descendant test rather than a sibling one - that's what
           the wrapper buys. */
        &.pseudoSection:not(.deletedSection):has(.sectionNames.empty) {
            display:none;
        }
        &.unknown {
            body:not(.director) & { display:none; }   /* debug aid - directors only */
        }
    }

    .sectionHeader {
        padding-left:15px;
        color:gray;
        background-color: #eee;
        text-align: center;
        position:relative;

        .director & { cursor:context-menu; }   /* right-click opens the section menu */
        .sectionBlock.waiting & { font-style: italic; }

        /* Lock badge on a "only director can make reservations" section. Keyed off the data
           attribute rather than a parallel class for the same reason --section-letter-width below
           is: Game::sectionBlock() and ReservationList.editSection() already keep it current, so the
           badge appears the moment a director ticks the box, with nothing extra to keep in sync.
           The :not([...=""]) guard is required - sectionBlock() emits the attribute on every block,
           empty when unflagged, so a bare [data-director-reserve] would match all of them. */
        .sectionBlock[data-director-reserve]:not([data-director-reserve=""]) & {
            padding-right: 15px;   /* balances padding-left so the centred name stays clear of the icon */
            &::after {
                content: "";
                position: absolute;
                right: 4px;
                top: 50%;
                transform: translateY(-50%);
                width: 1em;
                height: 1em;
                background-image: url('images/lock2.png');
                background-size: contain;
                background-repeat: no-repeat;
                background-position: center;
            }
        }
   }

    /* reservations in the Deleted section just flow in order - no NS/EW column split */
    .sectionNames[data-section="Deleted"] > reservation-div {
        grid-column: auto;
    }
    /* the whole Deleted block, header and list together - one rule where it used to take two */
    .sectionBlock.deletedSection {
        display:none;
        .director & { display:block; background-color: #eee; }
    }
    /* section name lists */
    .sectionNames:not([data-section="Singles"]):not([data-section="Waiting"]) {
        min-height: 20px;
    }
    /* Defaults for the two letter custom properties, on the BLOCK rather than on .sectionNames.
       Both real values now come from the block - an inline --letter from Game::sectionBlock() /
       ReservationList.editSection(), and --section-letter-width from the rule below - and
       .sectionNames is a descendant of it, so defaulting them there would override the inherited
       value instead of standing in for a missing one. That's what it used to do harmlessly, back
       when the inline --letter was set on .sectionNames itself and won as an inline style.
       --letter must have *some* value: reservation-div::before is content: var(--letter) var(--row),
       and an undefined var makes the whole declaration invalid, dropping the table number. Defaulted
       on every block - which sections show table numbers is decided by :not(.pseudoSection) on the
       ::before rule itself, not by leaving the var undefined. */
    .sectionBlock {
        --letter: "";
        --section-letter-width:0px;
    }
    /* derived from the block's own data-letter (already kept in sync for the letter-display feature
    itself, see ReservationList.editSection()/objects.php:sectionBlock()) rather than set
    independently by JS/PHP - one source of truth instead of two paths agreeing by hand.
    Set on the wrapper and inherited, so the sibling combinator this used to need is gone. */
    .sectionBlock[data-letter]:not([data-letter=""]) {
        --section-letter-width: min(20px, 2vmin);
    }

    /* :not(.empty) matters here - :has() only checks DOM presence, not visibility, so without it a
       section still counts as "occupied" after a director logs out and leaves behind the .empty
       placeholder reservation-divs their seating grid created (hidden via display:none for
       non-directors, but still in the DOM) - "None yet" would silently never render for a section
       that's visually empty. */
    body:not(.director) & .sectionNames:not(:has(reservation-div:not(.empty))):not([data-section="Singles"])::after {
        content:"None yet";
        color:lightgray;
        padding: 0 8px;
    }

    /* The seating grid, and the swiss override of it. Both are written against this shared parent
       rather than repeating it, which is what lets the swiss rule drop its !important: spelled out
       separately the swiss selector was (0,3,1) against the seating grid's (0,4,1) and could only
       win by force, whereas here both resolve to the same specificity and plain source order
       decides. Singles is excluded at this level because it's the seating grid's own exclusion -
       and grid-template-columns is inert on Singles anyway, since nothing ever makes it a grid. */
    .sectionNames:not([data-section="Singles"]) {
        .director tr[data-seating="1"] & {
            grid-template-columns:auto auto;
            display:grid;
            grid-auto-flow: row dense;   /* backfill gaps so unseated reservations don't get pushed to a spurious extra row */
            column-gap:15px;
        }
        .director tr.swiss & {
            grid-template-columns:auto;
        }
    }


    .chart:has(.printing) > *:not(.printing) {
        display:none !important;
    }

    /* The chart is just a holder for the section blocks */
    @media screen {
        .director & .chart {
            overflow-y:scroll;
            max-height:85vh;
        }
    }

}



reservation-div.dragover, .sectionNames.dragover, .sectionNames.dragover > reservation-div {
    background-color:plum !important;
}



/* individual reservations */
reservation-div {
    --row:"0";  /* illegitimate value but the CSS will gripe if it's undefined */
    --row-num: 0;
    --res-bg: transparent;
    display:block;
    position:relative;
    grid-row-start: var(--row-num); /* sets the location in the grid */

    /* N.B. .N-S and .E-W are user preferenes, but .NS and .EW are actual assigned locations */
    &.N-S {--res-bg: #eef;}
    &.E-W {--res-bg: #efe;}
    &.NS {grid-column: 1;}
    &.EW {grid-column: 2;}


    /* The nameDiv is created by the constructor to hold just the names */
    .nameDiv {
        pointer-events: none;
        padding:0;
        text-overflow: ellipsis;
        overflow:hidden;

        /* Only the ::after rules below are screen-only - the four declarations above are not.
           They used to sit inside this wrapper too, so when printing, padding:0 and
           overflow:hidden were dropped and the ambient
           "[popover] div:where(:not([popover])) {padding:3px}" took over instead: every name was
           indented 3px past its absolutely-positioned ::before table number, and the NS and EW
           columns were pushed 6px apart (3px on each facing edge). */
        @media screen {
            &::after {color:gray;}
            /* director clicked "show MPs" checkbox */
            .director .showMPs .sectionNames:not([data-section="Deleted"]) > reservation-div:not(.empty) &::after {
                content: " (" attr(data-mps) ")";
            }
            /* user entered MPs in singles reservation */
            section-names[data-section="Singles"] > reservation-div[data-seeking-mps]:not(.empty) &::after {
                content: " (" attr(data-seeking-mps) "MP)";
            }
            &[data-display-section] {
                /* waiting and singles display section names */
                section-names[data-section="Singles"] > reservation-div:not(.empty) &::after,
                section-names[data-section="Waiting"] > reservation-div:not(.empty) &::after {
                    content: " " attr(data-display-section);
                }
                /* singles show number of MPs if present */
                /* overrides immediately preceding rule if appropriate */
                tr:not([data-sectional]) section-names[data-section="Singles"] > reservation-div[data-seeking-mps]:not(.empty) &::after {
                    content: " (" attr(data-seeking-mps) "MP)";
                }
                /* for sectionals, show both MPs (if present) and section */
                tr[data-sectional] section-names[data-section="Singles"] > reservation-div[data-seeking-mps]:not(.empty) &::after {
                    content: " (" attr(data-seeking-mps) "MP) " attr(data-display-section);
                }

            }
        }
    }

    body:not(.director) & {
        padding: 0 8px;
    }

    /* display a mail envolope icon for singles to non-directors */
    /* Rendered via ::after rather than a real appended element - a real .contactIcon span sitting
    inside reservation-div could get its markup merged into .nameDiv's innerHTML by the
    MutationObserver in ReservationDiv.connectedCallback() (timing-dependent, hard to repro),
    corrupting the name/partner text. A pseudo-element is never part of the DOM tree, so it can't
    be touched by that observer. Click handling for it lives in ReservationDiv.js (contactIconHitTest). */

    body:not(.director) section-names[data-section="Singles"] & {
        --singles-icon-size: clamp(25px, 3vw, 40px);
        padding-right: calc(4px + var(--singles-icon-size));
 
        &::after {
            content: '✉';
            color:purple;
            position: absolute;
            right: 5px;
            top: 50%;
            transform: translateY(calc(-50% - 2px));
            cursor: pointer;
            opacity: 0.8;
            line-height: 1;
            font-size: var(--singles-icon-size);
        }
        &:hover::after {
            color:black;
        }

    }

    /*  reservations default to white, becoming translucent while dragging.
        blue if N-S, green if E-W, red if wrong direction, gray if canceled, animated flash if new */
    background-color: white;

    @media screen {
        [popover] & {
            min-width: min(40vw, 300px);

            /* user preference for direction */
            tr:not(.swiss) .sectionNames:not([data-section="Deleted"]):not([data-section="Singles"]):not(.dragover) &.N-S:not(.dragover),
            tr:not(.swiss) .sectionNames:not([data-section="Deleted"]):not([data-section="Singles"]):not(.dragover) &.E-W:not(.dragover) {
                background-color: var(--res-bg);
            }
            /* mismatch between user preference and actual direction assigned */
            .sectionNames:not([data-section="Deleted"]) &.NS.E-W:not(.dragover), 
            .sectionNames:not([data-section="Deleted"]) &.EW.N-S:not(.dragover) {
                background-color: #fee !important; 
            }
            /* deleted reservation in gray */
            .sectionNames[data-section="Deleted"] > & {
                background-color: #eee;
            }
            &.just-updated {
                animation: highlight-change 3s ease-out forwards;
            }
            .sectionNames:not([data-section="Singles"]) &:not(.empty) { 
                cursor:pointer; /* all draggable except singles and empties */
            }

        }
    }

    .director & {
    
         /* leave space for table number - same real-sections-only scope as the ::before below, so
            buckets (Singles isn't even a grid) never get an indent for a number they don't show */
        .sectionBlock:not(.pseudoSection) &.NS {
            padding-left: calc(var(--section-letter-width) + clamp(15px, 20px + ((100vw - 1000px) * .04), 40px));
        }
       /*  show table numbers in left column, only in real sections (not the Singles/Waiting/Unknown/Deleted pseudo-sections) and not while dragging */
        .sectionBlock:not(.pseudoSection) &.NS:not(.dragging)::before {
            content: var(--letter) var(--row); 
            position: absolute;
            left: 0px;
        }
        .sectionNames:has(.empty.lastRow.EW) &.empty.lastRow.NS::before,
        tr.swiss &.empty.lastRow.NS::before {
            content: "";
        }
    }
}


/* briefly highlight recently changed divs on update */
@keyframes highlight-change {
    0% { background-color: rgba(128, 0, 128, 0.25); }
    100% { background-color: var(--res-bg); }
}

/*  Standardized styling for popovers */

/* The purple bar bleeds to the popover's edges via an absolutely-positioned ::before rather than
   negative margins on .headerDiv itself - negative margins on a normal-flow child feed into the
   popover's own width:fit-content calculation (see dialog, [popover] {width:fit-content} above),
   and that interaction proved unreliable across viewport sizes (see
   planning/dialog-popover-consistency.md for the debugging history). An absolutely-positioned
   pseudo-element is out of flow, so it can't distort that calculation - only the decorative
   background needs to bleed correctly, and .headerDiv's own text sizes/positions under perfectly
   ordinary fit-content rules.

   ::before rounds its own top corners to match the popover's border-radius:12px, rather than
   relying on the popover having overflow:hidden to clip square corners into round ones - that
   was tried first, but overflow:hidden also clips anything else that legitimately needs to
   overflow the popover (autocomplete <ul class=dropdown> lists in particular), which is a much
   worse problem than a possible sub-pixel gap/overshoot at the very corner from this offset math
   being an approximation. See planning/dialog-popover-consistency.md for this history too. */
div.headerDiv {
    position: relative;
    z-index: 0;   /* forces this to establish its own stacking context (position:relative alone
                     doesn't) so ::before's z-index:-1 stays contained inside it, rather than
                     escaping to whichever ancestor happens to establish one. A real popover
                     already establishes one regardless - not because of the z-index:5 on
                     `dialog, [popover]` as such, but because top-layer promotion (what makes it a
                     popover at all) forces stacking-context creation unconditionally - and it also
                     owns the white background the bar needs to sit on, so this bug was invisible
                     there purely by coincidence. A plain, unpositioned container (e.g. cancel.php's
                     .card, not a popover) has neither property, and without this the bar's own
                     text ends up on the wrong side of it - see
                     planning/dialog-popover-consistency.md's cancel.php note */
    color: white;
    /* left/right sized to clear .closeIcon's own width (min(38px,6vmin)) plus a little breathing
       room, so long header text can't run under it - see planning/dialog-popover-consistency.md.
       Scales with the same vmin unit as the icon itself, so the clearance stays proportional
       rather than becoming too little/too much padding as the icon's own size changes. */
    padding: 0 min(42px, 7vmin) 5px;
    margin-bottom:8px;
    text-align: center;
    font-weight: bold;
}
/* Caps the width of a long dynamically-inserted title - #viewEmails's copy of the game's own
   title (see CustomPopover.js's open()), and reservation-list's own title (objects.php's $hDate
   $escapedName - a director-editable game name, so unlike every other .headerDiv here it isn't
   bounded in practice) - so it can't force the popover's own width:fit-content wider than the
   screen. Scoped to the span rather than .headerDiv itself: .headerDiv's overflow must stay
   visible for its own ::before bleed (the purple bar background, which is deliberately positioned
   outside .headerDiv's box - see the comment above), so overflow:hidden can't live there.
   inline-block so overflow/text-overflow actually apply - a plain inline span ignores both. 98vw
   total minus both of .headerDiv's own left/right paddings, matching the formula above, so the
   header's rendered width (padding + this span) tops out at 98vw. white-space:nowrap is set
   explicitly rather than relying on the ambient "dialog, [popover] {white-space:nowrap}" - needed
   for the ellipsis to kick in at all, and harmless to make explicit here. */
#viewEmails .headerDiv span,
reservation-list .headerDiv span {
    display: inline-block;
    max-width: calc(98vw - (2 * min(42px, 7vmin)));
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
}
div.headerDiv::before {
    content: '';
    position: absolute;
    z-index: -1;
    top: -0.25em;
    right: -1vw;
    bottom: 0;
    left: -1vw;
    background-color: purple;
    border-radius: 10px 10px 0 0;
}
.closeIcon {
    position: absolute;
    top: -4px;
    right: 0px;
    width: min(38px, 6vmin);
    cursor: pointer;
}
/* Standardized Save/confirm button position: lower right (item 4, dialog-popover-consistency.md).
   Popovers/dialogs opt in by adding this class to their primary confirm button in markup, rather
   than each one carrying its own inline float/position styling. float rather than
   position:absolute (unlike .closeIcon) - it needs no reserved padding and naturally follows
   existing DOM order, matching #deleteButton's pre-existing float:right convention: a save button
   that's first in DOM among its floated siblings ends up rightmost, which is the position that
   already reads as "primary action" everywhere this class gets applied. */
.saveButton {
    float: right;
    /* originally custom-modal button[value="OK"]-specific (spacing against the neighboring Cancel
       button); folded in here since it's harmless for every other current .saveButton user, which
       aren't adjacent to another floated button on their left. */
    margin-left: 3px;
}
/* dynamic header titles - both states' spans sit in the markup (same pattern #newReservation
   already used above) and are toggled by a class on the popover itself, rather than JS ever
   touching .headerDiv's own children directly - textContent would wipe out .closeIcon along with
   any other text, since it replaces every child */
#newGame.new .headerDiv .editing,
#newGame:not(.new) .headerDiv .new {
    display: none;
}
#editDirector.adding .headerDiv .editing,
#editDirector:not(.adding) .headerDiv .adding {
    display: none;
}

    /* newReservation dialog */
#newReservation {

    #dateDiv {
        font-size:smaller;
        text-align: center;
        /* sides are -1vw, not a fixed px: they cancel :popover-open's padding-left/right:1vw the same
           way div.headerDiv::before's left/right:-1vw does, so the band meets the popover's inner edge
           at every viewport width (a fixed -13px only matched 1vw at 1300px, and poked ~7px outside
           the popover on phones) */
        margin: -8px -1vw 12px -1vw;
        color:black;
        background-color: rgba(128,0,128,0.10);
        border-bottom:1px rgba(128,0,128,0.2) solid;
        padding:3px;
    }
    .nameDiv {text-align:right}
    #sectionDiv {margin-bottom:6px;}
    #playerContact {
        display: grid;
        grid-template-rows: 0fr;
        opacity: 0;
        transition: grid-template-rows 0.3s ease, opacity 0.3s ease;
        overflow: hidden;

        > * {overflow: hidden;}
        div:has(> .hideText)::after {color:purple;}
        div:has(> [name="playerEmail"].hideText)::after {
            content: "Email on file";
        }
        div:has(> [name="playerPhone"].hideText)::after {
            content: "Phone on file";
        }
        div:has(> [name="playerACBL"].hideText)::after {
            content: "ACBL on file";
        }
        img {
            width: min(25px, 5vmin);
            vertical-align: middle;
        }

         &.show {
            grid-template-rows: 1fr;
            opacity: 1;
        }
    }

    &:not(.swiss) {
        .teammate, .swiss {display:none;}
    }
    &.swiss {
        #directionDiv, .notSwiss {display:none;}
    }
    &.sectional #directionDiv {display:none;}
    &.regulars {
        #needPartnerDiv, #dateDiv, .notRegulars {display:none;}
    }
    &:not(.regulars) {
        #backfillDiv, .regularsOnly {display:none;}
    }

    &.editing .new,
    &.regulars .new,
    &:not(.editing) .editing {
        display: none;
    }

    #waitlistNotice { display: none; }
    &.waitlisted #waitlistNotice { display: block; }
    #capacityWarning { display: none; }
    &.nearCapacity #capacityWarning { display: block; }
    #resOptional {display:none;}
    &.resOptional:not(.regulars) #resOptional {display:block}

    #needPartnerDiv {
        text-align: right;
        font-size:70%;
    }
    #needPartnerInfo {
        display: none;
        font-size: 70%;
        color: gray;
        text-align: right;
    }
    &:not(.swiss):not(.regulars):has(#needPartner:checked) #needPartnerInfo { display: block; }

    &.waiting {
        cursor:wait;
        & div, & button, & input {cursor:wait;}
    }
    #capacityWarning,#resOptional {
        color: purple;
        font-style: italic;
        font-size: 60%;
        padding: 2px 0 4px;
        text-align: center;
    }
    #resOptional {
        font-size: 100%;
        text-wrap: nowrap;
    }

}

/* Must stay OUTSIDE #newReservation: the same "Waitlisted" badge is rendered by cancel.php inside
   its own #reservation card, which is a different page entirely - nesting this would produce
   "#newReservation #reservation #waitlistNotice" and match nothing there. (The display: none /
   .waitlisted toggles above are correctly nested, since only the popover needs to hide it.) */
#waitlistNotice, #reservation #waitlistNotice {
    color: #7a5c00;
    font-style: italic;
    font-weight: bold;
    padding: 2px 0 4px;
}


/* Dropdowns are used in various dialogs */
.dropdown {
    position:absolute;
    white-space:nowrap;
    width:auto;
    max-height:200px;
    overflow-y:scroll;
    overflow-x:hidden;
    z-index:25;
    background:white;
    margin-top:0;
    padding-left:0;
    border:solid thin black;
    box-shadow: #606 1px 1px 2px;
    display:none;

    li {
        list-style-type: none;
        margin: 0;
        padding: 5px 15px; 
        border:solid thin black;
        border-width:1px 1px 0 0;
    }

    li:hover:not(.dropdown-header) {
        background-color:#fdf;
        cursor:pointer;
    }

    .dropdown-header {
        background-color: #f5f5f5;
        color: #666;
        font-size: 0.85em;
        font-size: 70%;
        font-weight: 500;
        cursor: default;
        border:0;
        padding:2px 15px;
    }
    .dropdown-header:hover {
        background-color: #f5f5f5;
    }
    .dropdown-header + li {
        border-top:0;
    }

    .highlighted {
        background-color: #fdf;
    }

    li.add-new {
        border-top: 2px solid purple;
        background-color: #f8f0f8;
    }
}


/* Items in the New Game / Edit Game dialog */
#newGame {
    &:not(.new) #addRegularsSpan {display:none;}
    #otherGameType {
        display:none;
    }
    &.new #deleteButton {display:none;}
}

#editClub {
    fieldset menu-frame {background-color:white; /* it's on a pink background */}
    select:disabled {color:gray;}
    #regulars > div {
        position:relative;
    }
    #clubSections > div {
        position: relative;
    }
    #regulars div.N-S {
        background-color: #eef;
    }
    #regulars div.E-W {
        background-color: #efe;
    }
    #regulars > div::after {
        color:gray;
        content: " (" attr(data-section) ")";
    }
    .delete {
        background-color: red;
    }
}

/* #newGame #addRegularsSpan {
    display:none;
}
#newGame.new #addRegularsSpan {
    display:inline;
} */
/* #deleteButton {
    background-color: red;
} */
/* #newGame.new #deleteButton {
    display:none;
} */
/* #editClub fieldset menu-frame {
    background-color: white;
}
#editClub select:disabled {
    color:gray;
} */


/* Items in the findPlayer dialog */

#findPlayer {
    max-width: 90vw;
    /* min-width:0 overrides the fieldset default of min-width:min-content, which would otherwise
    force the fieldset (and #findPlayer along with it) to grow to fit a long nowrap row instead of
    letting overflow-x scroll it */
    fieldset {display:none; clear:right; min-width:0; overflow-x:auto;}
    &:has(input[name="playerName"][data-id]:not([data-id=""])) fieldset {
        display:block;
        padding-bottom:20px;
        & div {padding:0;}
    }
    div:has(input[name="playerName"][data-id=""]) ~ div #updatePlayerInfo {
        display: none;
    }

    &:not(.updating)  #editPlayerInfo,
    &:not(.updating) #savePlayerInfo {
        display: none;
    }
    &.updating #updatePlayerInfo, 
    &.updating #playerInfo {
        display:none;
    }
}


#contactSingle:not(.swiss) .swiss {
    display:none;
}



/*  Items in the Edit Club dialog */
/* #regulars > div {
    position:relative;
}
#clubSections > div {
    position: relative;
}
#regulars div.N-S {
    background-color: #eef;
}
#regulars div.E-W {
    background-color: #efe;
}
#regulars > div::after {
    color:gray;
    content: " (" attr(data-section) ")";
}
.delete {
    background-color: red;
} */


/*  In the editSection dialog */
#editSection {
    #secondTime:not(:checked) ~ #secondTimeSpan {display:none;}
}


/* Alpha list button in date cell */
.alphaListButton {
    display: block;
    margin-top: 4px;
    padding: 0;
    cursor: pointer;
    background: none;
    border: none;
    opacity: 0.5;
    width: 20px;
    height: 20px;
    background-image: url('images/print_purple.png');
    background-repeat: no-repeat;
    background-size: contain;
    background-position: center;
}
.alphaListButton:hover {
    opacity: 1;
}

/* Alpha list popover */
#alphaList {
    max-width: 90%;
    max-height: 80vh;
    overflow-y: auto;
    overflow-x:hidden;

    #alphaListDate {
        text-align: center;
        font-style: italic;
        color: #555;
        margin-bottom: 6px;
    }
    #alphaListContent {
        margin: 10px 0;
        font-size: 90%;
    
        div {
            line-height: 1.1;
            white-space: nowrap;
        }
        .gameInfo {
            font-size: 80%;
        }
    }

}



#resHistoryList {
    overflow-y: scroll;
    max-height:80vh;

    > div {
        position:relative;
    }
}



@media print {
 * {
    transition: none !important;
}
    /* Viewport units are not reliable on paper. iOS Safari resolves vw/vmin - and print media
       queries - against the SCREEN viewport rather than the page box: on a 392px phone printing to
       Letter, a print-media (min-width:) ladder reports 375px instead of ~739px, every padding
       below halves, and the base font drops from 28.8px to 12.4px. That's the "tiny margins,
       everything compressed" phone printout. Chrome resolves them correctly against the page box,
       so this only restates what Chrome already computes - measured there at a 739px page box, so
       its output is unchanged to the sub-pixel.
       Only five declarations in the file actually reach printed output; these are them. Anything
       else using vw/vmin styles something that is hidden on paper, or is already @media screen. */
    body, input, td, th, button, select, textarea {
        font-size: 1.8rem;          /* 28.8px - the ceiling min(5.2vmin - .5rem, 1.8rem) already
                                       hits on any real page box. html is not in this selector
                                       list, so rem stays 16px and this is stable. */
    }
    :popover-open { padding-left: 7.39px; padding-right: 7.39px; }   /* was 1vw */
    td { padding: 7.39px; }                                          /* was 1vmin */
    reservation-list { padding: 0.25em 7.39px; }                     /* was 0.25em 1vw */
    /* Needs (0,3,1) to match the nested rule it overrides (reservation-list .sectionBlock
       [data-letter]:not([data-letter=""])); equal specificity, and this block is later in the
       file. The clamp() alongside it in .NS's padding-left already resolves to 15px at both page
       and phone widths, so this variable is the whole of that indent's drift. */
    reservation-list .sectionBlock[data-letter]:not([data-letter=""]) {
        --section-letter-width: 14.78px;                             /* was min(20px, 2vmin) */
    }
    /* The seating grid's column gap is a flat 15px, but print shrinks the text to 70%
       (:popover-open > div below), so on paper that same 15px reads as 0.74em against a 0.52em gap
       on screen - the columns look pushed apart. Restated in em so it tracks the font instead of
       ignoring it: 15px/28.8px is 0.52em, which lands at 10.5px against print's 20.16px text, the
       same proportion the screen has. Left in px on screen deliberately - em there would also
       shrink the gap on narrow phones, where the base font is small and the layout is not.
       Specificity must match the (0,4,2) of the nested rule it overrides (see .sectionNames:not(
       [data-section="Singles"]) inside reservation-list); equal, and this block is later. */
    .director tr[data-seating="1"] reservation-list .sectionNames:not([data-section="Singles"]) {
        column-gap: 0.52em;
    }

   div, td, td *, th, h2, img {display:none;}
    table {border:0;}
    td {border:0}
    #body,
    td:has(:popover-open),
    reservation-list:popover-open,
    reservation-list:popover-open section-names,
    /* :not(.pseudoSection) excludes the bucket blocks. Since the restructure the marker sits on the
       wrapper rather than the header, so the descendant clause is what keeps a bucket's *header*
       from printing - without it, .sectionHeader is just another div and would be shown. */
    reservation-list:popover-open div:not(.directionKey):not(.rightClickMessage):not(.pseudoSection):not(.pseudoSection *):not([popover]):not(.NSEW),
    reservation-div {
        display:block;
    }
    reservation-div * {
        display:inline;
    }
    :popover-open > div {font-size:70%;}
    :popover-open {border:0;margin-top:0;}
    /* Don't print the Deleted, Singles or Unknown buckets. Was six selectors with
       ".director div.deletedSection" repeated three times; on the wrapper it's one class each, and
       hiding the block covers its header and list together. Waiting is deliberately absent - the
       waiting list does print. */
    .director .sectionBlock.deletedSection,
    .director .sectionBlock.singles,
    .director .sectionBlock.unknown {
        display:none !important;
    }
    reservation-list:popover-open {
        box-shadow: none;
    }
    reservation-list h3 {
        display:block;
        text-align: center;
    }
    reservation-list:popover-open button {
        display: none;
    }
    reservation-list:popover-open .headerDiv {
        display: none !important;
    }
    reservation-list .NSEW {
        display:flex;
        justify-content: space-evenly;
        text-align:center;
    }
    tr:not([data-seating]) reservation-list .NSEW {
        display:none;
    }
    reservation-list .sectionHeader {
        color:black;
        background-color:white;
        padding-top:20px;
    }
    .chart {
        max-height: initial;
        overflow-y: initial;
    }
    .director .NS.E-W:not(.dragover), .director .EW.N-S:not(.dragover) {
        background-color: white !important; 
    }
    
    .director .showMPs .sectionNames > reservation-div:not(.empty)::after {
        content:"";
    }

    /* alpha list popover: show its content, hide its buttons */
    #alphaList:popover-open {
        display: block;
        border: 0;
        margin: 0;
        box-shadow: none;
        max-height: none;
        overflow: visible;
    }
    #alphaList:popover-open * {
        display: block;
    }
    #alphaList:popover-open button {
        display: none;
    }
    #alphaList:popover-open #alphaListContent div {
        font-size: 70%;
        white-space: nowrap;
    }
    #alphaList:popover-open #alphaListContent div * {
        display: inline;
    }
}

/* ── Merge duplicate players ─────────────────────────────────────────────── */
#mergeNames {
    max-width: min(1200px, 95vw);
    min-width: min(800px, 90vw);
    min-height: 500px;
    max-height: 90vh;
    overflow: auto;

    .headerDiv {
        margin-bottom: 16px;
    }

    .mergeHelpWrapper {
        position: absolute;
        bottom: 8px;
        right: 8px;
    }
    #mergeHelp {
        font-size: 0.85em;
        width: 1.6em;
        height: 1.6em;
        border-radius: 50%;
        padding: 0;
        cursor: pointer;
    }
    #mergeHelpDropdown {
        display: none;
        position: absolute;
        bottom: calc(100% + 4px);
        right: 0;
        background: white;
        border: 1px solid #ccc;
        border-radius: 4px;
        box-shadow: 0 2px 8px rgba(0,0,0,0.15);
        padding: 10px 14px;
        min-width: 220px;
        max-width: 400px;
        text-wrap:auto;
        z-index: 10;
        /* Overrides the responsive body font-size, which is calibrated for on-screen table/button
           text and runs far too large for this dense block of help copy on short/wide viewports. */
        font-size: 14px;
        &.show { display: block; }
    }

    #recent {
        padding: 3px min(8px, 1.5vmin);
        img {width: min(30px, 4vmin);}
    }

    .player-grid {
        display: grid;
        grid-template-columns: 1fr auto 1fr;
        gap: 20px;
        align-items: start;
    }

    .player {
        min-width: 0;   /* prevent grid cell overflow */
        > div:first-child { min-height: 2em; }
    }

    .merge-buttons {
        align-self: center;
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 8px;
    }


    /* Desktop: horizontal arrows */
    #_adopt::before { content: '← '; }
    #adopt_::after  { content: ' →'; }

    /* Merge buttons hidden by default; CSS :has() shows them when both panels have a player */
    #_adopt, #adopt_ { visibility: hidden; pointer-events: none; }
    &:has(#merge .playerInfo[data-player]:not([data-player="0"])):has(#merge2 .playerInfo[data-player]:not([data-player="0"])) {
        #_adopt, #adopt_ { visibility: visible; pointer-events: auto; }
    }

    .playerInfo input { max-width: 100%; box-sizing: border-box; }
    .playerInfo > div:not(.editing) > input { display: none; }
    .editing {
        .edit, .value { display: none; }
    }
    .playerInfo:not([data-player]),
    .playerInfo[data-player="0"] { display: none; }

    .nicknames, .email, .phone, .acbl {
        height: 45px;
        position: relative;
        padding-left: 10px;
        margin-bottom: 18px;
        &::before {
            content: attr(data-cat);
            color: white;
            background-color: purple;
            font-size: medium;
            padding: 2px 4px;
            position: absolute;
            top: -17px;
            left: -10px;
        }
    }

    .value::before { content: none; }
    .value:empty::before {
        content: "None listed ";
        color: lightgray;
    }

    /* shown while a field save is in flight - the real (old) text stays put underneath,
       untouched, until the save is confirmed, so there's nothing to revert on failure */
    .value.waiting {
        visibility: hidden;
        position: relative;
        display: inline-block;
    }
    .value.waiting::after {
        content: "Updating…";
        visibility: visible;
        position: absolute;
        left: 0;
        top: 0;
        white-space: nowrap;
        font-style: italic;
        color: gray;
    }

    #recentNames {
        position: absolute;   /* positioned via JS getBoundingClientRect() in clickRecent */
        background: white;
        border: 1px solid #ccc;
        border-radius: 4px;
        box-shadow: 0 2px 8px rgba(0,0,0,0.15);
        min-width: 200px;
        max-height: 300px;
        overflow-y: auto;
        z-index: 1000;
        display: none;
        &.show { display: block; }
        .dropdown-item {
            padding: 8px 12px;
            cursor: pointer;
            border-bottom: 1px solid #eee;
            &:last-child { border-bottom: none; }
            &:hover { background-color: #f0f0f0; }
        }
    }

    @media (max-width: 600px) {
        .player-grid {
            grid-template-columns: 1fr;
            gap:0;
        }
        .merge-buttons {
            flex-direction: row;
            justify-content: center;
        }
        .player > div:first-child {margin-bottom:1.3vh;}
        /* Mobile: vertical arrows (↑ = top panel survives, ↓ = bottom panel survives) */
        #_adopt::before { content: '↑ '; }
        #adopt_::after  { content: ' ↓'; }
        .nicknames, .email, .phone, .acbl {
            height: 32px;
            margin-bottom: 0.5vh;
            &::before { font-size: x-small; top:-12px;}
        }
    }
}