
/* The base text size on screen, shared with the places that have to restore it (calendar.css's seating-chart
   popover and reservation form): it scales with the viewport's short side, up to 1.8rem (28.8px).
   Landscape gets an 18px floor: a phone held sideways is only ~390px tall, which made the base ~12px - and iOS
   then "autosized" some text blocks up to ~19px and not others (see text-size-adjust below), so the list's rows
   came out in mixed sizes. Not in portrait yet: on phones held upright the action buttons take nearly half the
   row, and 18px text squeezed the game names until words broke mid-word on 360px phones (planning doc, phone
   pass - narrower action buttons first, then extend the floor). Only screens whose short side is under ~500px
   are affected either way; tablets and desktops are already above 18px. */
:root {
    --base-font: min(5.2vmin - 0.5rem, 1.8rem);
    @media (orientation: landscape) {
        --base-font: clamp(18px, 5.2vmin - 0.5rem, 1.8rem);
    }
}
/* iOS Safari enlarges some blocks of text on its own ("text autosizing"), judged per block, especially in
   landscape - which made neighbouring rows different sizes. Off everywhere, so text is the size the CSS says. */
html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}
body, input,td,th,button, select, textarea {
    /* Print value first, screen override nested - Safari ignores a nested @media print (see the
       Printing block below). 1.8rem (28.8px) is the ceiling --base-font already reaches on any real
       page box; it's stated in absolute units because vmin is unreliable on paper. */
    font-size: 1.8rem;
    font-family:sans-serif;
    @media screen { font-size:var(--base-font); }
}
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;
    /* Print values first, screen overrides nested (Safari ignores a nested @media print).
       7.39px is 1vw at a 739px page box, in absolute units because vw is unreliable on paper. */
    border: 0;
    padding-left: 7.39px;
    padding-right: 7.39px;
    @media screen {
        border: 2px solid #333;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
        padding-left: 1vw;
        padding-right: 1vw;
    }
}
/* Print-only, with nothing to override on screen - so these can't be inverted, and have to be a
   top-level @media print. */
@media print {
    /* absolute, not the UA's fixed: iOS Safari sizes a fixed element (like vw) against the phone
       SCREEN when printing, so the printed popover's width:100% came out ~390px on a ~740px page.
       An absolute top-layer element is laid out against the page instead (measured with a probe
       page on an iPhone). Desktop browsers lay both out against the page, so nothing changes there. */
    :popover-open { position: absolute; margin-top: 0; }
    :popover-open > div { font-size: 70%; }   /* shrink the printed popover's content one step */
}


/* ── Printing ────────────────────────────────────────────────────────────────
   Only two things are ever printable: the open reservation-list (seating chart) and the open
   #alphaList. With neither open, the page prints blank. This block decides WHAT prints; how the
   printed things look lives with each element's own rule.

   Two Safari constraints shape everything print-related in this file:
   - Safari (18.6) never applies an @media print nested inside a style rule - with or without &.
     Only top-level @media print works. A nested @media screen does work, so print-specific
     values are written print-first: the bare declaration is the print value, and a nested
     @media screen holds the screen value. Print-only rules with no screen value to invert (hides,
     mostly) go in a top-level @media print - this block, or a small one right after their rule.
   - Viewport units are unreliable on paper. iOS Safari resolves vw/vmin - and print media
     queries - against the SCREEN viewport rather than the page box, so a 392px phone printing
     to Letter halves every vw-based padding and shrinks the base font from 28.8px to 12.4px.
     The print-first values are those vw/vmin expressions measured at a 739px page box, in
     absolute units (e.g. 7.39px for 1vw).

   The hide: one rule, everything except the printable popover, its ancestors and its contents.
   The six :not() clauses are, in order, for each popover: its ancestor chain (via :has), the
   popover itself, and everything inside it. Nothing inside the printed subtree is touched, so
   its own display rules (the seating grid's display:grid, etc.) carry over unchanged.
   Deliberately no hide-then-restore: the restore would have to be !important to beat the hide,
   and !important outranks every normal author display rule - it flattened the seating grid to
   inline and stacked the NS/EW columns. */
@media print {
    * { transition: none !important; }

    body *:not(:has(reservation-list:popover-open)):not(reservation-list:popover-open):not(reservation-list:popover-open *):not(:has(#alphaList:popover-open)):not(#alphaList:popover-open):not(#alphaList:popover-open *) {
        display: none !important;
    }

    /* Safari prints a blank page when the document itself is 0px tall, even with a popover open.
       That's the alpha-list case: everything in normal flow is hidden and body has margin:0, so
       html/body/#body all collapse to nothing (the reservation list escapes this only because its
       table ancestors keep some height). Chrome prints the top layer regardless. */
    body { min-height: 1px; }

    /* Screen chrome inside the printable popovers. The seating chart's .headerDiv is NOT hidden -
       it doubles as the printed title (restyled in the print block after reservation-list).
       .seatingButtons is the container - hiding only its buttons left an empty strip. */
    #alphaList .headerDiv,
    .seatingButtons { display: none !important; }

    /* No bucket prints from the seating chart - Singles, Waiting, Unknown and Deleted alike.
       !important is required, not decorative: .sectionBlock.deletedSection's own
       ".director & {display:block}" is (0,3,1) and would otherwise out-specify this.
       #alphaList is unaffected - its content is built in JS by ReservationList.updatePrintList()
       as plain divs with no .sectionBlock markup at all, so waiting players still print there,
       which is the intent. */
    .sectionBlock.pseudoSection { display: none !important; }
}


/* 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;

    > div.transparent {
        position:absolute;
        bottom:0;
        right:0;
        height:100%;
        pointer-events: none;
        padding:0 !important;
        &::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;
    }
}
/* Only where there's no mouse or trackpad - phones, tablets - e.g. instructions to "tap" something that a mouse
   user would get by hovering. Keyed to the input device, not the screen width: an iPad has no hover, and a phone in
   landscape is wider than any phone breakpoint. Hidden only on devices whose main pointer can hover precisely - the
   same test the calendar uses to reveal a game's icons on hover (calendar.css) - so a device that's hard to classify
   shows it rather than hides it. */
@media (hover:hover) and (pointer:fine) {
    .touchOnly {
        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);

    &:focus {
        outline:none;
        outline-style: double;
        outline-offset: -1px;
        outline-color:#a64da6;
        color:black;
    }
    &.red-border:focus {
        outline-color: red;
    }
    &:not(.red-border) {
        border-color:purple;
    }
    &.hideText {
        display: none;
    }
    &::placeholder {
        color:lightgray;
    }
    &:not([type]):disabled {
        opacity:0.5;
        outline-color:gray;
        border-color:gray;
    }
    &:invalid {
        border-color:red;
        outline-color:red;
    }


    /* Checkbox scaling */
    &[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, #menuItems and #updateStatus 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 #helpLink and
       #menuLink, and neither actually needs 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;
}

/* Director-only update status - the "updates may have stopped" warning and "Updated 28s ago" - on one line
   under the help/menu icons, right-aligned to them. It used to be two flex items BESIDE the icons, which
   widened #menuHolder leftward: the text by its length, and the warning by its full width even while
   invisible (it's hidden with opacity, so it keeps its space). #menuHolder's white background covered
   whatever that extra width landed on - on narrower windows, the end of the page title. #menuHolder
   (position:fixed) is the containing block. #menuItems and #help, which open downward from the same spot,
   are z-index:10 and cover it while they're open. */
#updateStatus {
    position: absolute;
    top: 100%;
    right: 0;
    display: flex;
    align-items: center;
    white-space: nowrap;

    /* restated: the display:flex above, on an id, would otherwise outweigh the general
       body:not(.director) .directorOnly {display:none} rule and show it to everyone */
    body:not(.director) & { display: none; }
}

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

    & img {
        /* the height of the status line's text, not the icons' 35px: it sits beside "Updated 28s ago" now */
        width:20px;
        height:20px;
        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 0 4px 1vmin;       /* none on the right, so the text ends flush with the menu icon above it */
    /* position: see #updateStatus */

    @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;
    /* Runs once per page load. pabc.js adds the class on animationend - without it the animation
       replays whenever the element is re-rendered, e.g. after cancelling a print (the print block
       hides it with display:none, which cancels the animation). */
    &.animationDone { animation: none; }
}
@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;
    /* Same one-shot treatment as #logo above. The background-position is the animation's own end
       state, which "forwards" was holding - without restating it here the gradient would snap back
       to 0% 0% the moment the animation is switched off. */
    &.animationDone {
        animation: none;
        background-position: 100% 100%;
    }
}

/* 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;
    margin-top:10px;
    width:100%;
    /* Screen-only: on paper the table has no border (Safari ignores a nested @media print, so
       the border is scoped to screen instead). Last in the rule deliberately: declarations
       following a nested @media are hoisted into an implicit rule placed AFTER it. */
    @media screen {
        border-color:lightgray;
        border-style:solid;
        border-width:thin 0 thin thin;
    }
}
tr:nth-child(odd) {
    background-color: rgba(128,0,128,0.05);
}
/* .noGame: "No game" or "Closed" in the description - set by Game::tableRow() (objects.php isClosed()) */
.game.noGame {
    /* Every cell dimmed except the name cell, whose contents are dimmed instead: the director's right-click
       "Edit game" menu is appended inside it (showGameEditMenu() in pabc.js), and nothing inside an element
       with reduced opacity can render any more opaque than it - so dimming the cell itself left the menu
       faint too. Dimming the cell's contents individually leaves the menu out. This also covers the
       calendar view, where that cell is display:contents and couldn't be dimmed as a whole anyway. */
    & td:not(.nameCell) {opacity:0.35;}
    & td.nameCell > :not(.gameName),
    & .gameName > :not(.contextMenu) {opacity:0.35;}
    /* ...and that cell's own borders, which its opacity used to dim along with everything else: the same
       colors at 0.35 of their alpha, which composites identically (the cell has no background of its own).
       lightgray is 211,211,211; the non-first rows' top border is rgba(128,0,128,0.15) (the td rule below). */
    & td.nameCell {border-color:rgba(211, 211, 211, 0.35);}
    &:not(:first-child) td.nameCell {border-top-color:rgba(128, 0, 128, 0.0525);}
    & td.actionCell * {display:none;}
    .tableFee {display:none;}
}

/* Filtering by day or sectional. List view only: in the calendar these filters have no visible effect, but stay
   set on #gameList, so they apply again when the list shows (plan decision 34). Filtering by player
   ("My reservations") is a rule filterMyReservations() (pabc.js) generates. */
#gameList:not(.calendar)[data-day="1"] .game:not([data-day="1"]),
#gameList:not(.calendar)[data-day="2"] .game:not([data-day="2"]),
#gameList:not(.calendar)[data-day="3"] .game:not([data-day="3"]),
#gameList:not(.calendar)[data-day="4"] .game:not([data-day="4"]),
#gameList:not(.calendar)[data-day="5"] .game:not([data-day="5"]),
#gameList:not(.calendar)[data-day="6"] .game:not([data-day="6"]),
#gameList:not(.calendar)[data-day="7"] .game:not([data-day="0"]),
#gameList:not(.calendar)[data-day="sectional"] .game:not([data-sectional])
 {
    display:none;
}

/* Hide overflow games (beyond 7 days) until "Show more" is clicked. List view only - the calendar shows every game,
   and .show-all stays list state that survives a view switch. */
#gameList:not(.calendar):not(.show-all) .game.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 {
    /* Print value first, screen overrides nested (Safari ignores a nested @media print). 7.39px
       is 1vmin at a 739px page box, in absolute units because vmin is unreliable on paper. */
    padding: 7.39px;
    vertical-align: middle;
    /* Must follow the declarations above, not precede them: anything bare that comes after a
       nested @media is hoisted into an implicit rule placed after it, and at equal specificity
       would win. Everything below this point is a nested sub-rule, so nothing gets hoisted past it.
       Note the border scoping only covers td's OWN border - the more specific nested rules below
       (e.g. tr:not(:first-child) & {border-top}) still apply on paper, as they always have. */
    @media screen {
        border:solid lightgray;
        border-width: thin thin 0 0;
        padding:1vmin;
    }

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

    /* date cell */
    &.dateCell {
        border-left: 4px solid transparent;
        .game:hover & {
            border-left-color:purple;
        }
        .dow {
            font-size:larger;
            font-weight: bold;
            /* narrow screens: the day on its own line above the date, so the date column needs only the width
               of "Sep 29" rather than "Tue Sep 29" - more room for the game name. Same breakpoint as the time
               cell's move into .phoneTime below. */
            @media (max-width:800px) { display:block; }
        }
       .phoneTime {
            color:purple;
            margin-right:1vw;
            text-align: right;
            /* a game's start times are one <span> each (Game::tableRow(), ReservationList.updateGameTimes());
               one per line here, as the <br> they replaced did. calendar.css runs them together instead.
               The <wbr> between them is that inline layout's line-break opportunity; here, with the spans
               blocks, it isn't needed - and left displayed it still sits in a line box of its own between
               the two blocks, adding a blank line. */
            span { display:block; }
            wbr { display:none; }
            /* 801, not 800: the time column below hides at max-width:800px, and one number for both would
               hide both copies at that exact width (as 600/600 used to, at 600px). */
            @media (min-width:801px) {
                display:none;
            }
           
        }
        table:not([data-day="my"]) &.sameDate div:first-child {
            display:none;
        }
    }

    /* time cell, hidden on narrow screens, where .phoneTime shows the times under the date instead.
       800px, not the 600px this used to be: the base font is already 23-29px from ~600px up, and between
       600 and ~800 the date, time and button columns (all nowrap) took so much of the row that the game-name
       column was squeezed to 60-90px - and body's overflow-wrap:anywhere then let the table break names
       mid-word. With the time moved under the date and the day stacked (.dow above), the name column gets
       about three times the width. */
    &.timeCell {
        color:purple;
        font-size: 80%;
        span { display:block; }   /* one start time per line - see .phoneTime above */
        wbr { display:none; }
        @media (max-width:800px) {display:none;}
       
    }

    /* game name/description cell */
    &.nameCell {
        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) .game: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) .game.overflow & {
                display:none;
            }
       }
   }

    &:not(.nameCell) {
        text-wrap: nowrap;
    }
    /* buttons/reservation-list cell */
    &.actionCell {
        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;
}


/* Narrow screens (the same 800px breakpoint as the date/time columns above): no "Partner?" button, to give
   the game name more room. Nothing is lost - the Reserve button's form has a "Need a partner" checkbox.
   #gameList in the selector because the rule just below sets display:inline-block on the same element at the
   same weight and comes later, so a bare partner-finder-button selector here would lose to it. */
@media (max-width:800px) {
    #gameList partner-finder-button { 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);
        }
    }

    /* The "Reserved" checkmark (.reserved: this browser's reservedGames cookie - markReservedGames() in pabc.js -
       or a reservation it just made) shows only in its own right: never for directors (plan decision 29 - a
       director's browser reserves for other people, so nearly every game would be marked), and not while
       "My reservations" is on, which marks the chosen player's games itself. */
    body:not(.director) #gameList:not([data-day="my"]) &.reserved .not-reserved,
    &:not(.reserved) .reserved,
    :is(body.director, #gameList[data-day="my"]) & .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) */
    .actionCell.open & {
        display:block;
        max-width:90vw;
    }

    /* Print-only column headings, built by ReservationList.connectedCallback(). Print layout
       first, hidden on screen - Safari ignores a nested @media print, so this is inverted. */
    .NSEWheader {
        display:flex;
        justify-content: space-evenly;
        text-align:center;
        font-weight:bold;
        @media screen { display:none; }
        /* a game with no seating chart has no NS/EW split to label */
        .game:not([data-seating]) & { display:none; }
        /* nor does a team game - same condition .directionKey uses just below for the on-screen key
           this is the printed equivalent of. */
        .game.swiss & { display:none; }
    }

    .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 */
        /* not printed - it's inside .seatingButtons, hidden in the Printing block near the top */
        .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 */
        }
        /* print: every .pseudoSection bucket is hidden - see the Printing block near the top */
    }

    .sectionHeader {
        padding-left:15px;
        padding-top: 20px;   /* on paper this gap is the divider between sections */
        text-align: center;
        position:relative;
        /* Print values first, screen overrides nested - Safari ignores a nested @media print.
           Grey-on-grey is a screen affordance; on paper it wastes toner and reads faintly, so print
           just inherits reservation-list's black on no background. 3px is the ambient
           [popover] div padding this would otherwise have. */
        @media screen {
            color:gray;
            background-color: #eee;
            padding-top: 3px;
        }

        .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=""]) {
        /* Print value first, screen override nested (Safari ignores a nested @media print).
           2vmin resolves against the screen viewport even when printing (iOS Safari), so print
           pins the value it has at the 739px page width. 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. */
        --section-letter-width: 14.78px;
        /* On screen it's viewport-based, but never narrower than 0.6em - in which em resolves on the reservation row
           using the variable (a custom property is substituted where it's used), i.e. it follows the text size.
           Needed since landscape phones got an 18px text floor (--base-font) while 2vmin stayed ~8px there, which
           left the table number ("A12") running into the name. Where 2vmin is already the larger - desktop,
           portrait phones - nothing changes. */
        @media screen { --section-letter-width: max(min(20px, 2vmin), 0.6em); }
    }

    /* :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 .game[data-seating="1"] & {
            display:grid;
            grid-auto-flow: row dense;   /* backfill gaps so unseated reservations don't get pushed to a spurious extra row */
            /* Print values first, screen overrides nested (Safari ignores a nested @media print).
               Columns: on paper the page width is fixed, so the columns split it evenly, and the
               0 minimum stops a long name from widening its column past the page - it's clipped
               with .nameDiv's ellipsis instead. On screen the columns size to the names.
               Gap: print shrinks the text to 70% (:popover-open > div), so a flat 15px would read
               as 0.74em on paper against about 0.5em on screen (15px/28.8px) - the columns look
               pushed apart. Stated in em so it keeps that proportion (about 10px in practice).
               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. */
            grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
            column-gap: 0.5em;
            @media screen {
                grid-template-columns:auto auto;
                column-gap:15px;
            }
        }
        .director .game.swiss & {
            grid-template-columns: minmax(0, 1fr);
            @media screen { grid-template-columns:auto; }
        }
    }


    /* ReservationList.printSection() tags one block .printing for the duration of window.print().
       Not wrapped in @media print: nested here, Safari would ignore it (see the Printing block near
       the top), and it's harmless on screen since the class only exists during window.print().
       !important because this is only (0,3,0), and has to beat e.g.
       .director .sectionBlock.deletedSection's display:block at (0,3,1). */
    .chart:has(.printing) > *:not(.printing) {
        display:none !important;
    }

    /* The chart is just a holder for the section blocks. The popover as a whole is capped to the
       viewport and the chart takes whatever height is left after the title bar and buttons, rather
       than the chart alone getting max-height:85vh - that left the chrome to fit in the other 15%,
       which it doesn't on a short window, so the list spilled off both ends of the screen.
       :popover-open, not [popover]: an author display on a closed popover would override the UA's
       display:none and show it. */
    @media screen {
        .director &:popover-open {
            display: flex;
            flex-direction: column;
            max-height: calc(100dvh - 20px);
        }
        .director & .chart {
            overflow-y:scroll;
            flex: 1 1 auto;
            min-height: 0;   /* flex items won't shrink below their content without this */
        }
    }

}
/* On paper the seating chart spans the page, so the title, NS/EW headings and section names centre
   on the page and the seating grid's 1fr columns (above) split its full width. Top-level because
   Safari ignores a nested @media print, and there's no screen value here to invert - on screen the
   width comes from the general [popover] {width:fit-content}, which is also why this needs
   [popover] to outrank it. */
@media print {
    reservation-list[popover] {
        box-sizing: border-box;
        width: 100%;
        max-width: none;
    }

    /* The purple title bar doubles as the printed title - black bold centred text at the size the
       old separate <h3> had. "div.headerDiv" to outrank the general div.headerDiv rule further down
       the file, and :popover-open > div's 70%. */
    reservation-list div.headerDiv {
        color: black;
        font-size: 1.17em;
        padding: 0;
        margin: 1em 0;
    }
    reservation-list div.headerDiv::before,
    reservation-list .closeIcon { display: none; }
}



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 */
                .game: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 */
                .game[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"] & {
        /* room for the icon below: its 1.2em width, the 5px it's set in from the right edge, and a 5px gap */
        padding-right: calc(10px + 1.2em);
 
        /* An image, not the ✉ character it used to be: iOS draws U+2709 as a color emoji, which ignores
           CSS color, so it wasn't reliably purple there. The PNG is transparent apart from its strokes, so
           brightness(0) on hover turns just the strokes black, as the character used to.
           Sized from the row's own text (em, inherited from the reservation-div): one line tall, width at the
           image's proportions. The character used a viewport-based 25-40px font size, but a glyph is drawn well
           inside its font size and an image fills its box - so at that size it was taller than the row and got
           cut off in small-text lists like the calendar's dropdown. */
        &::after {
            content: '';
            position: absolute;
            right: 5px;
            top: 50%;
            transform: translateY(-50%);
            width: 1.2em;
            height: 1em;                   /* 1.2 : 1 is the image's own proportions (512 x 425) */
            background: url(images/mail_purple.png) center / contain no-repeat;
            cursor: pointer;
            opacity: 0.8;
        }
        &:hover::after {
            filter: brightness(0);
        }

    }

    /*  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 */
            .game:not(.swiss) .sectionNames:not([data-section="Deleted"]):not([data-section="Singles"]):not(.dragover) &.N-S:not(.dragover),
            .game:not(.swiss) .sectionNames:not([data-section="Deleted"]):not([data-section="Singles"]):not(.dragover) &.E-W:not(.dragover) {
                background-color: var(--res-bg);
            }
            /* 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 */
            }

        }
    }

    /* Mismatch between the player's stated direction preference and the seat actually assigned.
       Screen-only: on screen it needs to shout, on paper the pair reads as plain white like every
       other row (reservation-div's own background-color:white, since all the tints are screen-only).
       [popover] is spelled out because it used to be inherited from the enclosing "[popover] &" -
       dropping it would both widen the selector and change its specificity, which matters here
       since this competes with the direction tints just above. */
    [popover] .sectionNames:not([data-section="Deleted"]) &.NS.E-W:not(.dragover),
    [popover] .sectionNames:not([data-section="Deleted"]) &.EW.N-S:not(.dragover) {
        @media screen { background-color: #fee !important; }
    }

    .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 {
            /* Print value first, screen override nested (Safari ignores a nested @media print).
               On paper the clamp() bottoms out at 15px, too narrow for a two-digit number at the
               printed font size; 1.3em leaves "12" a few px clear of the name at print's 20.16px.
               The screen clamp() is scaled by the same factor (1.3/1.2 - it was 1.2em at desktop
               width, 35px against 28.8px text) so both keep the same proportion. */
            padding-left: calc(var(--section-letter-width) + 1.3em);
            @media screen {
                /* never less than the 1.3em print uses, so a two-digit number still fits where the text is larger
                   than the viewport-based clamp expects (landscape phones' 18px floor); elsewhere the clamp wins */
                padding-left: calc(var(--section-letter-width) + max(clamp(15px, 20px + ((100vw - 1000px) * .04), 40px) * 1.3 / 1.2, 1.3em));
            }
        }
       /*  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,
        .game.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 (built in
   ReservationList.connectedCallback() from the row's date and game name - director-editable, 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. On paper the cap is the header's own
   width instead, i.e. the page: 98vw is a screen measurement, and iOS resolves it against the
   phone screen when printing. The cap can't simply be dropped for print - without one the span
   grows to fit and the ellipsis never appears.
   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: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
    @media screen { max-width: calc(98vw - (2 * min(42px, 7vmin))); }
}
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;
        &:hover {background-color: #f5f5f5}
        & + li {border-top:0;}
    }
    /* .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%;
    /* screen-only scroll box - on paper the whole list prints */
    @media screen {
        max-height: 80vh;
        overflow-y: auto;
        overflow-x:hidden;
    }

    /* Print values first, screen overrides nested (Safari ignores a nested @media print). The
       printed sizes are absolute, taken from the standalone print window the Print button used to
       open before it became a plain window.print(). "inherit" is the screen value because on
       screen nothing declares a size here - the date inherits the popover's, and the rows inherit
       #alphaListContent's 90%. */
    #alphaListDate {
        text-align: center;
        font-style: italic;
        color: #555;
        font-size: 32px;
        margin-bottom: 8px;
        @media screen {
            font-size: inherit;
            margin-bottom: 6px;
        }
    }
    #alphaListContent {
        margin: 10px 0;
        font-size: 90%;

        div {
            white-space: nowrap;
            font-size: 22.4px;
            line-height: 1.4;
            @media screen {
                font-size: inherit;
                line-height: 1.1;
            }
        }
        .gameInfo {
            font-size: 75%;
            @media screen { font-size: 80%; }
        }
    }

}
/* Page geometry for the printed alpha list - Cmd-P and the Print button (window.print()) alike.
   The Printing block near the top keeps this popover and hides its .headerDiv, and :popover-open
   drops the border and shadow. Kept here rather than inverted into the rule above because on
   screen these come from :popover-open (the popover's own padding and centring), which restating
   here would duplicate. Spanning the page is also what centres the date on the page rather than
   on the popover's fit-content width. Top-level because Safari ignores a nested @media print. */
@media print {
    #alphaList {
        margin: 0;
        box-sizing: border-box;
        width: 100%;
        max-width: none;
        padding: 20px;
    }
    #alphaList button { display: none; }
}



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

    > div {
        position:relative;
    }
}

/* ── 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;}
        }
    }
}